utils.js 11 KB
/*!
 * Casper is a navigation utility for PhantomJS.
 *
 * Documentation: http://casperjs.org/
 * Repository:    http://github.com/n1k0/casperjs
 *
 * Copyright (c) 2011-2012 Nicolas Perriault
 *
 * Part of source code is Copyright Joyent, Inc. and other Node contributors.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*global CasperError console exports phantom require*/

(function(exports) {
    "use strict";

    /**
     * Provides a better typeof operator equivalent, able to retrieve the array
     * type.
     *
     * @param  mixed  input
     * @return String
     * @see    http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
     */
    function betterTypeOf(input) {
        try {
            return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
        } catch (e) {
            return typeof input;
        }
    }
    exports.betterTypeOf = betterTypeOf;

    /**
     * Dumps a JSON representation of passed value to the console. Used for
     * debugging purpose only.
     *
     * @param  Mixed  value
     */
    function dump(value) {
        console.log(serialize(value));
    }
    exports.dump = dump;

    /**
     * Returns the file extension in lower case.
     *
     * @param  String  file  File path
     * @return string
     */
    function fileExt(file) {
        try {
            return file.split('.').pop().toLowerCase().trim();
        } catch(e) {
            return '';
        }
    }
    exports.fileExt = fileExt;

    /**
     * Takes a string and append blanks until the pad value is reached.
     *
     * @param  String  text
     * @param  Number  pad   Pad value (optional; default: 80)
     * @return String
     */
    function fillBlanks(text, pad) {
        pad = pad || 80;
        if (text.length < pad) {
            text += new Array(pad - text.length + 1).join(' ');
        }
        return text;
    }
    exports.fillBlanks = fillBlanks;

    /**
     * Formats a string with passed parameters. Ported from nodejs `util.format()`.
     *
     * @return String
     */
    function format(f) {
        var i = 1;
        var args = arguments;
        var len = args.length;
        var str = String(f).replace(/%[sdj%]/g, function _replace(x) {
            if (i >= len) return x;
            switch (x) {
            case '%s':
                return String(args[i++]);
            case '%d':
                return Number(args[i++]);
            case '%j':
                return JSON.stringify(args[i++]);
            case '%%':
                return '%';
            default:
                return x;
            }
        });
        for (var x = args[i]; i < len; x = args[++i]) {
            if (x === null || typeof x !== 'object') {
                str += ' ' + x;
            } else {
                str += '[obj]';
            }
        }
        return str;
    }
    exports.format = format;

    /**
     * Retrieves the value of an Object foreign property using a dot-separated
     * path string.
     *
     * Beware, this function doesn't handle object key names containing a dot.
     *
     * @param  Object  obj   The source object
     * @param  String  path  Dot separated path, eg. "x.y.z"
     */
    function getPropertyPath(obj, path) {
        if (!isObject(obj) || !isString(path)) {
            return undefined;
        }
        var value = obj;
        path.split('.').forEach(function(property) {
            if (typeof value === "object" && property in value) {
                value = value[property];
            } else {
                value = undefined;
                return;
            }
        });
        return value;
    }
    exports.getPropertyPath = getPropertyPath;

    /**
     * Inherit the prototype methods from one constructor into another.
     *
     * @param {function} ctor Constructor function which needs to inherit the
     *     prototype.
     * @param {function} superCtor Constructor function to inherit prototype from.
     */
    function inherits(ctor, superCtor) {
        ctor.super_ = ctor.__super__ = superCtor;
        ctor.prototype = Object.create(superCtor.prototype, {
            constructor: {
                value: ctor,
                enumerable: false,
                writable: true,
                configurable: true
            }
        });
    }
    exports.inherits = inherits;

    /**
     * Checks if value is a javascript Array
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isArray(value) {
        return Array.isArray(value) || isType(value, "array");
    }
    exports.isArray = isArray;

    /**
     * Checks if passed argument is an instance of Capser object.
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isCasperObject(value) {
        return value instanceof require('casper').Casper;
    }
    exports.isCasperObject = isCasperObject;

    /**
     * Checks if value is a phantomjs clipRect-compatible object
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isClipRect(value) {
        return isType(value, "cliprect") || (
            isObject(value) &&
            isNumber(value.top) && isNumber(value.left) &&
            isNumber(value.width) && isNumber(value.height)
        );
    }
    exports.isClipRect = isClipRect;

    /**
     * Checks if value is a javascript Function
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isFunction(value) {
        return isType(value, "function");
    }
    exports.isFunction = isFunction;

    /**
     * Checks if a file is apparently javascript compatible (.js or .coffee).
     *
     * @param  String  file  Path to the file to test
     * @return Boolean
     */
    function isJsFile(file) {
        var ext = fileExt(file);
        return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
    }
    exports.isJsFile = isJsFile;

    /**
     * Checks if the provided value is null
     *
     * @return Boolean
     */
    function isNull(value) {
        return isType(value, "null");
    }
    exports.isNull = isNull;

    /**
     * Checks if value is a javascript Number
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isNumber(value) {
        return isType(value, "number");
    }
    exports.isNumber = isNumber;

    /**
     * Checks if value is a javascript Object
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isObject(value) {
        var objectTypes = ["array", "object", "qtruntimeobject"];
        return objectTypes.indexOf(betterTypeOf(value)) >= 0;
    }
    exports.isObject = isObject;

    /**
     * Checks if value is a javascript String
     *
     * @param  mixed  value
     * @return Boolean
     */
    function isString(value) {
        return isType(value, "string");
    }
    exports.isString = isString;

    /**
     * Shorthands for checking if a value is of the given type. Can check for
     * arrays.
     *
     * @param  mixed   what      The value to check
     * @param  String  typeName  The type name ("string", "number", "function", etc.)
     * @return Boolean
     */
    function isType(what, typeName) {
        if (typeof typeName !== "string" || !typeName) {
            throw new CasperError("You must pass isType() a typeName string");
        }
        return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
    }
    exports.isType = isType;

    /**
     * Checks if the provided value is undefined
     *
     * @return Boolean
     */
    function isUndefined(value) {
        return isType(value, "undefined");
    }
    exports.isUndefined = isUndefined;

    /**
     * Checks if the provided var is a WebPage instance
     *
     * @param  mixed  what
     * @return Boolean
     */
    function isWebPage(what) {
        return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage';
    }
    exports.isWebPage = isWebPage;

    /**
     * Object recursive merging utility.
     *
     * @param  Object  origin  the origin object
     * @param  Object  add     the object to merge data into origin
     * @return Object
     */
    function mergeObjects(origin, add) {
        for (var p in add) {
            try {
                if (add[p].constructor === Object) {
                    origin[p] = mergeObjects(origin[p], add[p]);
                } else {
                    origin[p] = add[p];
                }
            } catch(e) {
              origin[p] = add[p];
            }
        }
        return origin;
    }
    exports.mergeObjects = mergeObjects;

    /**
     * Creates an (SG|X)ML node element.
     *
     * @param  String  name        The node name
     * @param  Object  attributes  Optional attributes
     * @return HTMLElement
     */
    function node(name, attributes) {
        var _node = document.createElement(name);
        for (var attrName in attributes) {
            var value = attributes[attrName];
            if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
                _node.setAttribute(attrName, value);
            }
        }
        return _node;
    }
    exports.node = node;

    /**
     * Serializes a value using JSON.
     *
     * @param  Mixed  value
     * @return String
     */
    function serialize(value) {
        if (isArray(value)) {
            value = value.map(function _map(prop) {
                return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
            });
        }
        return JSON.stringify(value, null, 4);
    }
    exports.serialize = serialize;

    /**
     * Returns unique values from an array.
     *
     * Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8
     *
     * @param  Array  array
     * @return Array
     */
    function unique(array) {
        var o = {},
            r = [];
        for (var i = 0, len = array.length; i !== len; i++) {
            var d = array[i];
            if (o[d] !== 1) {
                o[d] = 1;
                r[r.length] = d;
            }
        }
        return r;
    }
    exports.unique = unique;
})(exports);