airbug/bugcore

View on GitHub
libraries/bugcore/js/src/util/ArgUtil.js

Summary

Maintainability
B
5 hrs
Test Coverage
/*
 * Copyright (c) 2016 airbug Inc. http://airbug.com
 *
 * bugcore may be freely distributed under the MIT license.
 */


//-------------------------------------------------------------------------------
// Annotations
//-------------------------------------------------------------------------------

//@Export('ArgUtil')

//@Require('ArgumentBug')
//@Require('Bug')
//@Require('Class')
//@Require('Obj')
//@Require('ObjectUtil')
//@Require('TypeUtil')


//-------------------------------------------------------------------------------
// Context
//-------------------------------------------------------------------------------

require('bugpack').context("*", function(bugpack) {

    //-------------------------------------------------------------------------------
    // BugPack
    //-------------------------------------------------------------------------------

    var ArgumentBug     = bugpack.require('ArgumentBug');
    var Bug             = bugpack.require('Bug');
    var Class           = bugpack.require('Class');
    var Obj             = bugpack.require('Obj');
    var ObjectUtil      = bugpack.require('ObjectUtil');
    var TypeUtil        = bugpack.require('TypeUtil');


    //-------------------------------------------------------------------------------
    // Declare Class
    //-------------------------------------------------------------------------------

    /**
     * @class
     * @extends {Obj}
     */
    var ArgUtil = Class.extend(Obj, {
        _name: "ArgUtil"
    });


    //-------------------------------------------------------------------------------
    // Public Static Methods
    //-------------------------------------------------------------------------------

    /**
     * @static
     * @param {*} rawArgs
     * @param {Array.<{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }>=} descriptions
     * @return {Object}
     */
    ArgUtil.process = function(rawArgs, descriptions) {
        var args                = ArgUtil.processArgs(rawArgs);
        var argsObject          = {};
        var descriptionResults  = ArgUtil.processDescriptions(args, descriptions);
        var descriptionArgMap   = descriptionResults.descriptionArgMap;
        var unusedDescriptions  = descriptionResults.unusedDescriptions;

        ArgUtil.processDescriptionArgMap(args, argsObject, descriptionArgMap);
        ArgUtil.processUnusedDescriptions(args, argsObject, unusedDescriptions);
        return argsObject;
    };

    /**
     * @private
     * @param {*} rawArgs
     * @return {Array.<*>}
     */
    ArgUtil.processArgs = function(rawArgs) {
        var args = ArgUtil.toArray(rawArgs);
        while (TypeUtil.isUndefined(args[args.length - 1]) && args.length > 0) {
            args.pop();
        }
        return args;
    };

    /**
     * @static
     * @param {Array.<*>} args
     * @param {Array.<{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }>} descriptions
     * @return {{
     *      usedDescriptions: Array.<{
     *          name: string,
     *          type: string=,
     *          optional: boolean=,
     *          default: *=
     *      }>,
     *      unusedDescriptions: Array.<{
     *          name: string,
     *          type: string=,
     *          optional: boolean=,
     *          default: *=
     *      }>
     * }}
     */
    ArgUtil.processDescriptions = function(args, descriptions) {
        ArgUtil.validateDescriptions(descriptions, args.length);

        var argsCopy                = ([]).concat(args);
        var descriptionsCopy        = ([]).concat(descriptions);
        var descriptionArgMap       = {};
        var unusedDescriptions      = [];
        var notFoundDescriptions    = [];
        while (descriptionsCopy.length > 0) {
            var description = descriptionsCopy.shift();
            if (descriptionsCopy.length > argsCopy.length - 1) {
                if (description.optional) {
                    unusedDescriptions.push(description);
                } else {
                    if (argsCopy.length > 0) {
                        descriptionArgMap[description.name] = {
                            arg: argsCopy.shift(),
                            description: description
                        };
                    } else {
                        notFoundDescriptions.push(description);
                    }
                }
            } else {
                if (argsCopy.length > 0) {
                    descriptionArgMap[description.name] = {
                        arg: argsCopy.shift(),
                        description: description
                    };
                } else {
                    notFoundDescriptions.push(description);
                }
            }
        }

        if (notFoundDescriptions.length > 0) {
            var throwable = null;
            if (notFoundDescriptions.length === 1) {
                throwable = new ArgumentBug(ArgumentBug.ILLEGAL, notFoundDescriptions[0].name, undefined, "Argument missing");
            } else {
                var missingThrowables = [];
                notFoundDescriptions.forEach(function(notFoundDescription) {
                    missingThrowables.push(new ArgumentBug(ArgumentBug.ILLEGAL, notFoundDescription.name, undefined, "argument missing"));
                });
                throwable = new Bug("MultipleIllegalArguments", {}, "Multiple arguments missing", missingThrowables);
            }
            throw throwable;
        }
        /*if (args.length === descriptions.length) {
            usedDescriptions = usedDescriptions.concat(descriptions);
        } else {

            var neededDescriptions      = [];
            for (var i = descriptions.length - 1; i >= 0; i--) {
                var description = descriptions[i];

                //This arg is not present
                if (i >= args.length) {
                    if (!description.optional) {
                        neededDescriptions.push(description);
                    } else {
                        unusedDescriptions.unshift(description);
                    }
                } else {
                    var arg = args[i];
                    if (neededDescriptions.length > 0) {
                        var found = false;
                        while (neededDescriptions.length > 0 && !found) {
                            var neededDescription = neededDescriptions.shift();
                            if (ArgUtil.checkTypeMatch(arg, neededDescription)) {
                                found = true;
                                usedDescriptions.unshift(neededDescription);
                                if (!description.optional) {
                                    neededDescriptions.push(description);
                                }  else {
                                    unusedDescriptions.unshift(description);
                                }
                            }
                        }
                    } else {
                        usedDescriptions.unshift(description);
                    }
                }
            }

        }*/
        return {
            descriptionArgMap: descriptionArgMap,
            unusedDescriptions: unusedDescriptions
        };
    };

    /**
     * @static
     * @param {*} rawArgs
     * @return {Array.<*>}
     */
    ArgUtil.toArray = function(rawArgs) {
        return Array.prototype.slice.call(rawArgs, 0);
    };


    //-------------------------------------------------------------------------------
    // Private Static Methods
    //-------------------------------------------------------------------------------

    /**
     * @static
     * @private
     * @param {*} arg
     * @param {{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }} description
     * @return {boolean}
     */
    ArgUtil.checkTypeMatch = function(arg, description) {
        var validType   = true;
        if (description.type) {
            var type        = TypeUtil.toType(arg);
            if (TypeUtil.isArray(description.type)) {
                validType = (description.type.indexOf(type) >= 0);
            } else {
                validType = (type === description.type);
            }
        }
        return validType;
    };

    /**
     * @static
     * @private
     * @param {Array.<*>} args
     * @param {Object} argsObject
     * @param {Array.<{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }>} unusedDescriptions
     */
    ArgUtil.processUnusedDescriptions = function(args, argsObject, unusedDescriptions) {
        for (var i = unusedDescriptions.length - 1; i >= 0; i--) {
            var unusedDescription   = unusedDescriptions[i];
            if (unusedDescription.default) {
                argsObject[unusedDescription.name] = unusedDescription.default;
            } else {
                argsObject[unusedDescription.name] = undefined;
            }
        }
    };

    /**
     * @static
     * @private
     * @param {Array.<*>} args
     * @param {Object} argsObject
     * @param {Array.<{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }>} descriptionArgMap
     */
    ArgUtil.processDescriptionArgMap = function(args, argsObject, descriptionArgMap) {
        ObjectUtil.forIn(descriptionArgMap, function(name, value) {
            ArgUtil.setArgOnArgsObject(value.arg, argsObject, value.description);
        });




        /*
         var neededDescriptions      = [];

         for (var i = usedDescriptions.length - 1; i >= 0; i--) {
            var arg             = args[i];
            if (TypeUtil.isUndefined(arg)) {

            }
        }

        for (var i = usedDescriptions.length - 1; i >= 0; i--) {
            var arg = args[i];
            var usedDescription = usedDescriptions[i];

            if (neededDescriptions.length > 0) {

            }
            if (ArgUtil.checkTypeMatch(arg, usedDescription)) {
                ArgUtil.setArgOnArgsObject(arg, argsObject, usedDescription);
            } else {
                if (!usedDescription.optional) {
                    neededDescriptions.push(usedDescription);
                } else {

                }
            }
            if (neededDescriptions.length > 0) {
                var found = false;
                while (neededDescriptions.length > 0 && !found) {
                    var neededDescription = neededDescriptions.shift();
                    if (ArgUtil.checkTypeMatch(arg, neededDescription)) {
                        found = true;
                        usedDescriptions.unshift(neededDescription);
                        if (!description.optional) {
                            neededDescriptions.push(description);
                        }  else {
                            unusedDescriptions.unshift(description);
                        }
                    } else {
                        notFoundDescriptions.push(neededDescription);
                    }
                }
            } else {
                usedDescriptions.unshift(description);
            }

        }*/
    };

    /**
     * @static
     * @private
     * @param {*} arg
     * @param {Object} argsObject
     * @param {{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *=
     * }} description
     */
    ArgUtil.setArgOnArgsObject = function(arg, argsObject, description) {
        var validType = ArgUtil.checkTypeMatch(arg, description);
        if (!validType) {
            throw new ArgumentBug(ArgumentBug.ILLEGAL, description.name, arg, "Argument type does not match. Must be of type '" + description.type + "'");
        }
        argsObject[description.name] = arg;
    };

    /**
     * @static
     * @private
     * @param {Array.<{
     *      name: string,
     *      type: string=,
     *      optional: boolean=,
     *      default: *
     * }>=} descriptions
     * @param {number} expectedNumber
     */
    ArgUtil.validateDescriptions = function(descriptions, expectedNumber) {
        if (!TypeUtil.isArray(descriptions)) {
            throw new ArgumentBug(ArgumentBug.ILLEGAL, "descriptions", descriptions, "parameter must be an Array");
        }

        //TODO BRN: Is this something that we want to support? Should we have to declare all args?
        if (expectedNumber > descriptions.length) {
            throw new  ArgumentBug(ArgumentBug.ILLEGAL, "descriptions", descriptions,
                "Too few descriptions. Number of descriptions must be equal to or greater than the number of arguments");
        }

        for (var i = descriptions.length - 1; i >= 0; i--) {
            var description = descriptions[i];
            if (!TypeUtil.isObject(description)) {
                throw new ArgumentBug(ArgumentBug.ILLEGAL, "descriptions", descriptions, "descriptions Array must only contain description objects");
            }
            if (!TypeUtil.isString(description.name)) {
                throw new ArgumentBug(ArgumentBug.ILLEGAL, "descriptions", descriptions, "description objects must have a name");
            }
        }
    };


    //-------------------------------------------------------------------------------
    // Exports
    //-------------------------------------------------------------------------------

    bugpack.export('ArgUtil', ArgUtil);
});