<commit_before>/*===========================
Template7 Template engine
===========================*/
window.Template7 = (function () {
    'use strict';
    function isArray(arr) {
        return Object.prototype.toString.apply(arr) === '[object Array]';
    }
    function isObject(obj) {
        return obj instanceof Object;
    }
    function isFunction(func) {
        return typeof func === 'function';
    }
    var cache = {};
    function helperToSlices(string) {
        var helperParts = string.replace(/[{}#}]/g, '').split(' ');
        var slices = [];
        var shiftIndex, i, j;
        for (i = 0; i < helperParts.length; i++) {
            var part = helperParts[i];
            if (i === 0) slices.push(part);
            else {
                if (part.indexOf('"') === 0) {
                    // Plain String
                    if (part.match(/"/g).length === 2) {
                        // One word string
                        slices.push(part);
                    }
                    else {
                        // Find closed Index
                        shiftIndex = 0;
                        for (j = i + 1; j < helperParts.length; j++) {
                            part += ' ' + helperParts[j];
                            if (helperParts[j].indexOf('"') >= 0) {
                                shiftIndex = j;
                                slices.push(part);
                                break;
                            }
                        }
                        if (shiftIndex) i = shiftIndex;
                    }
                }
                else {
                    if (part.indexOf('=') > 0) {
                        // Hash
                        var hashParts = part.split('=');
                        var hashName = hashParts[0];
                        var hashContent = hashParts[1];
                        if (hashContent.match(/"/g).length !== 2) {
                            shiftIndex = 0;
                            for (j = i + 1; j < helperParts.length; j++) {
                                hashContent += ' ' + helperParts[j];
                                if (helperParts[j].indexOf('"') >= 0) {
                                    shiftIndex = j;
                                    break;
                                }
                            }
                            if (shiftIndex) i = shiftIndex;
                        }
                        var hash = [hashName, hashContent.replace(/"/g,'')];
                        slices.push(hash);
                    }
                    else {
                        // Plain variable
                        slices.push(part);
                    }
                }
            }
        }
        return slices;
    }
    function stringToBlocks(string) {
        var blocks = [], i, j, k;
        if (!string) return [];
        var _blocks = string.split(/({{[^{^}]*}})/);
        for (i = 0; i < _blocks.length; i++) {
            var block = _blocks[i];
            if (block === '') continue;
            if (block.indexOf('{{') < 0) {
                blocks.push({
                    type: 'plain',
                    content: block
                });
            }
            else {
                if (block.indexOf('{/') >= 0) {
                    continue;
                }
                if (block.indexOf('{#') < 0 && block.indexOf(' ') < 0 && block.indexOf('else') < 0) {
                    // Simple variable
                    blocks.push({
                        type: 'variable',
                        contextName: block.replace(/[{}]/g, '')
                    });
                    continue;
                }
                // Helpers
                var helperSlices = helperToSlices(block);
                var helperName = helperSlices[0];
                var isPartial = helperName === '>';
                var helperContext = [];
                var helperHash = {};
                for (j = 1; j < helperSlices.length; j++) {
                    var slice = helperSlices[j];
                    if (isArray(slice)) {
                        // Hash
                        helperHash[slice[0]] = slice[1] === 'false' ? false : slice[1];
                    }
                    else {
                        helperContext.push(slice);
                    }
                }

                if (block.indexOf('{#') >= 0) {
                    // Condition/Helper
                    var helperStartIndex = i;
                    var helperContent = '';
                    var elseContent = '';
                    var toSkip = 0;
                    var shiftIndex;
                    var foundClosed = false, foundElse = false, foundClosedElse = false, depth = 0;
                    for (j = i + 1; j < _blocks.length; j++) {
                        if (_blocks[j].indexOf('{{#') >= 0) {
                            depth ++;
                        }
                        if (_blocks[j].indexOf('{{/') >= 0) {
                            depth --;
                        }
                        if (_blocks[j].indexOf('{{#' + helperName) >= 0) {
                            helperContent += _blocks[j];
                            if (foundElse) elseContent += _blocks[j];
                            toSkip ++;
                        }
                        else if (_blocks[j].indexOf('{{/' + helperName) >= 0) {
                            if (toSkip > 0) {
                                toSkip--;
                                helperContent += _blocks[j];
                                if (foundElse) elseContent += _blocks[j];
                            }
                            else {
                                shiftIndex = j;
                                foundClosed = true;
                                break;
                            }
                        }
                        else if (_blocks[j].indexOf('else') >= 0 && depth === 0) {
                            foundElse = true;
                        }
                        else {
                            if (!foundElse) helperContent += _blocks[j];
                            if (foundElse) elseContent += _blocks[j];
                        }

                    }
                    if (foundClosed) {
                        if (shiftIndex) i = shiftIndex;
                        blocks.push({
                            type: 'helper',
                            helperName: helperName,
                            contextName: helperContext,
                            content: helperContent,
                            inverseContent: elseContent,
                            hash: helperHash
                        });
                    }
                }
                else if (block.indexOf(' ') > 0) {
                    if (isPartial) {
                        helperName = '_partial';
                        if (helperContext[0]) helperContext[0] = '"' + helperContext[0].replace(/"|'/g, '') + '"';
                    }
                    blocks.push({
                        type: 'helper',
                        helperName: helperName,
                        contextName: helperContext,
                        hash: helperHash
                    });
                }
            }
        }
        return blocks;
    }
    var Template7 = function (template) {
        var t = this;
        t.template = template;

        function getCompileFn(block, depth) {
            if (block.content) return compile(block.content, depth);
            else return function () {return ''; };
        }
        function getCompileInverse(block, depth) {
            if (block.inverseContent) return compile(block.inverseContent, depth);
            else return function () {return ''; };
        }
        function getCompileVar(name, ctx) {
            var variable, parts, levelsUp = 0, initialCtx = ctx;
            if (name.indexOf('../') === 0) {
                levelsUp = name.split('../').length - 1;
                var newDepth = ctx.split('_')[1] - levelsUp;
                ctx = 'ctx_' + (newDepth >= 1 ? newDepth : 1);
                parts = name.split('../')[levelsUp].split('.');
            }
            else if (name.indexOf('@global') === 0) {
                ctx = 'Template7.global';
                parts = name.split('@global.')[1].split('.');
            }
            else if (name.indexOf('@root') === 0) {
                ctx = 'root';
                parts = name.split('@root.')[1].split('.');
            }
            else {
                parts = name.split('.');
            }
            variable = ctx;
            for (var i = 0; i < parts.length; i++) {
                var part = parts[i];
                if (part.indexOf('@') === 0) {
                    if (i > 0) {
                        variable += '[(data && data.' + part.replace('@', '') + ')]';
                    }
                    else {
                        variable = '(data && data.' + name.replace('@', '') + ')';
                    }
                }
                else {
                    if (isFinite(part)) {
                        variable += '[' + part + ']';
                    }
                    else {
                        if (part.indexOf('this') === 0) {
                            variable = part.replace('this', ctx);
                        }
                        else {
                            variable += '.' + part;
                        }
                    }
                }
            }

            return variable;
        }
        function getCompiledArguments(contextArray, ctx) {
            var arr = [];
            for (var i = 0; i < contextArray.length; i++) {
                if (contextArray[i].indexOf('"') === 0) arr.push(contextArray[i]);
                else {
                    arr.push(getCompileVar(contextArray[i], ctx));
                }
            }

            return arr.join(', ');
        }
        function compile(template, depth) {
            depth = depth || 1;
            template = template || t.template;
            if (typeof template !== 'string') {
                throw new Error('Template7: Template must be a string');
            }
            var blocks = stringToBlocks(template);
            if (blocks.length === 0) {
                return function () { return ''; };
            }
            var ctx = 'ctx_' + depth;
            var resultString = '';
            if (depth === 1) {
                resultString += '(function (' + ctx + ', data, root) {\n';
            }
            else {
                resultString += '(function (' + ctx + ', data) {\n';
            }
            if (depth === 1) {
                resultString += 'function isArray(arr){return Object.prototype.toString.apply(arr) === \'[object Array]\';}\n';
                resultString += 'function isFunction(func){return (typeof func === \'function\');}\n';
                resultString += 'function c(val, ctx) {if (typeof val !== "undefined" && val !== null) {if (isFunction(val)) {return val.call(ctx);} else return val;} else return "";}\n';
                resultString += 'root = root || ctx_1 || {};\n';
            }
            resultString += 'var r = \'\';\n';
            var i, j, context;
            for (i = 0; i < blocks.length; i++) {
                var block = blocks[i];
                // Plain block
                if (block.type === 'plain') {
                    resultString += 'r +=\'' + (block.content).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/'/g, '\\' + '\'') + '\';';
                    continue;
                }
                var variable, compiledArguments;
                // Variable block
                if (block.type === 'variable') {
                    variable = getCompileVar(block.contextName, ctx);
                    resultString += 'r += c(' + variable + ', ' + ctx + ');';
                }
                // Helpers block
                if (block.type === 'helper') {
                    if (block.helperName in t.helpers) {
                        compiledArguments = getCompiledArguments(block.contextName, ctx);

                        resultString += 'r += (Template7.helpers.' + block.helperName + ').call(' + ctx + ', ' + (compiledArguments && (compiledArguments + ', ')) +'{hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth + 1) + ', inverse: ' + getCompileInverse(block, depth + 1) + ', root: root});';

                    }
                    else {
                        if (block.contextName.length > 0) {
                            throw new Error('Template7: Missing helper: "' + block.helperName + '"');
                        }
                        else {
                            variable = getCompileVar(block.helperName, ctx);
                            resultString += 'if (' + variable + ') {';
                            resultString += 'if (isArray(' + variable + ')) {';
                            resultString += 'r += (Template7.helpers.each).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: root});';
                            resultString += '}else {';
                            resultString += 'r += (Template7.helpers.with).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: root});';
                            resultString += '}}';
                        }
                    }
                }
            }
            resultString += '\nreturn r;})';
            return eval.call(window, resultString);
        }
        t.compile = function (template) {
            if (!t.compiled) {
                t.compiled = compile(template);
            }
            return t.compiled;
        };
    };
    Template7.prototype = {
        options: {},
        partials: {},
        helpers: {
            '_partial' : function (partialName, options) {
                var p = Template7.prototype.partials[partialName];
                if (!p || (p && !p.template)) return '';
                if (!p.compiled) {
                    p.compiled = t7.compile(p.template);
                }
                var ctx = this;
                for (var hashName in options.hash) {
                    ctx[hashName] = options.hash[hashName];
                }
                return p.compiled(ctx, options.data, options.root);
            },
            'escape': function (context, options) {
                if (typeof context !== 'string') {
                    throw new Error('Template7: Passed context to "escape" helper should be a string');
                }
                return context
                        .replace(/&/g, '&')
                        .replace(/</g, '<')
                        .replace(/>/g, '>')
                        .replace(/"/g, '"');
            },
            'if': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                if (context) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            },
            'unless': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                if (!context) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            },
            'each': function (context, options) {
                var ret = '', i = 0;
                if (isFunction(context)) { context = context.call(this); }
                if (isArray(context)) {
                    if (options.hash.reverse) {
                        context = context.reverse();
                    }
                    for (i = 0; i < context.length; i++) {
                        ret += options.fn(context[i], {first: i === 0, last: i === context.length - 1, index: i});
                    }
                    if (options.hash.reverse) {
                        context = context.reverse();
                    }
                }
                else {
                    for (var key in context) {
                        i++;
                        ret += options.fn(context[key], {key: key});
                    }
                }
                if (i > 0) return ret;
                else return options.inverse(this);
            },
            'with': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                return options.fn(context);
            },
            'join': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                return context.join(options.hash.delimiter || options.hash.delimeter);
            },
            'js': function (expression, options) {
                var func;
                if (expression.indexOf('return')>=0) {
                    func = '(function(){'+expression+'})';
                }
                else {
                    func = '(function(){return ('+expression+')})';
                }
                return eval.call(this, func).call(this);
            },
            'js_compare': function (expression, options) {
                var func;
                if (expression.indexOf('return')>=0) {
                    func = '(function(){'+expression+'})';
                }
                else {
                    func = '(function(){return ('+expression+')})';
                }
                var condition = eval.call(this, func).call(this);
                if (condition) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            }
        }
    };
    var t7 = function (template, data) {
        if (arguments.length === 2) {
            var instance = new Template7(template);
            var rendered = instance.compile()(data);
            instance = null;
            return (rendered);
        }
        else return new Template7(template);
    };
    t7.registerHelper = function (name, fn) {
        Template7.prototype.helpers[name] = fn;
    };
    t7.unregisterHelper = function (name) {
        Template7.prototype.helpers[name] = undefined;
        delete Template7.prototype.helpers[name];
    };
    t7.registerPartial = function (name, template) {
        Template7.prototype.partials[name] = {template: template};
    };
    t7.unregisterPartial = function (name, template) {
        if (Template7.prototype.partials[name]) {
            Template7.prototype.partials[name] = undefined;
            delete Template7.prototype.partials[name];
        }
    };

    t7.compile = function (template, options) {
        var instance = new Template7(template, options);
        return instance.compile();
    };

    t7.options = Template7.prototype.options;
    t7.helpers = Template7.prototype.helpers;
    t7.partials = Template7.prototype.partials;
    return t7;
})();<commit_msg>Remove extra 'use strict'<commit_after>/*===========================
Template7 Template engine
===========================*/
window.Template7 = (function () {
    function isArray(arr) {
        return Object.prototype.toString.apply(arr) === '[object Array]';
    }
    function isObject(obj) {
        return obj instanceof Object;
    }
    function isFunction(func) {
        return typeof func === 'function';
    }
    var cache = {};
    function helperToSlices(string) {
        var helperParts = string.replace(/[{}#}]/g, '').split(' ');
        var slices = [];
        var shiftIndex, i, j;
        for (i = 0; i < helperParts.length; i++) {
            var part = helperParts[i];
            if (i === 0) slices.push(part);
            else {
                if (part.indexOf('"') === 0) {
                    // Plain String
                    if (part.match(/"/g).length === 2) {
                        // One word string
                        slices.push(part);
                    }
                    else {
                        // Find closed Index
                        shiftIndex = 0;
                        for (j = i + 1; j < helperParts.length; j++) {
                            part += ' ' + helperParts[j];
                            if (helperParts[j].indexOf('"') >= 0) {
                                shiftIndex = j;
                                slices.push(part);
                                break;
                            }
                        }
                        if (shiftIndex) i = shiftIndex;
                    }
                }
                else {
                    if (part.indexOf('=') > 0) {
                        // Hash
                        var hashParts = part.split('=');
                        var hashName = hashParts[0];
                        var hashContent = hashParts[1];
                        if (hashContent.match(/"/g).length !== 2) {
                            shiftIndex = 0;
                            for (j = i + 1; j < helperParts.length; j++) {
                                hashContent += ' ' + helperParts[j];
                                if (helperParts[j].indexOf('"') >= 0) {
                                    shiftIndex = j;
                                    break;
                                }
                            }
                            if (shiftIndex) i = shiftIndex;
                        }
                        var hash = [hashName, hashContent.replace(/"/g,'')];
                        slices.push(hash);
                    }
                    else {
                        // Plain variable
                        slices.push(part);
                    }
                }
            }
        }
        return slices;
    }
    function stringToBlocks(string) {
        var blocks = [], i, j, k;
        if (!string) return [];
        var _blocks = string.split(/({{[^{^}]*}})/);
        for (i = 0; i < _blocks.length; i++) {
            var block = _blocks[i];
            if (block === '') continue;
            if (block.indexOf('{{') < 0) {
                blocks.push({
                    type: 'plain',
                    content: block
                });
            }
            else {
                if (block.indexOf('{/') >= 0) {
                    continue;
                }
                if (block.indexOf('{#') < 0 && block.indexOf(' ') < 0 && block.indexOf('else') < 0) {
                    // Simple variable
                    blocks.push({
                        type: 'variable',
                        contextName: block.replace(/[{}]/g, '')
                    });
                    continue;
                }
                // Helpers
                var helperSlices = helperToSlices(block);
                var helperName = helperSlices[0];
                var isPartial = helperName === '>';
                var helperContext = [];
                var helperHash = {};
                for (j = 1; j < helperSlices.length; j++) {
                    var slice = helperSlices[j];
                    if (isArray(slice)) {
                        // Hash
                        helperHash[slice[0]] = slice[1] === 'false' ? false : slice[1];
                    }
                    else {
                        helperContext.push(slice);
                    }
                }

                if (block.indexOf('{#') >= 0) {
                    // Condition/Helper
                    var helperStartIndex = i;
                    var helperContent = '';
                    var elseContent = '';
                    var toSkip = 0;
                    var shiftIndex;
                    var foundClosed = false, foundElse = false, foundClosedElse = false, depth = 0;
                    for (j = i + 1; j < _blocks.length; j++) {
                        if (_blocks[j].indexOf('{{#') >= 0) {
                            depth ++;
                        }
                        if (_blocks[j].indexOf('{{/') >= 0) {
                            depth --;
                        }
                        if (_blocks[j].indexOf('{{#' + helperName) >= 0) {
                            helperContent += _blocks[j];
                            if (foundElse) elseContent += _blocks[j];
                            toSkip ++;
                        }
                        else if (_blocks[j].indexOf('{{/' + helperName) >= 0) {
                            if (toSkip > 0) {
                                toSkip--;
                                helperContent += _blocks[j];
                                if (foundElse) elseContent += _blocks[j];
                            }
                            else {
                                shiftIndex = j;
                                foundClosed = true;
                                break;
                            }
                        }
                        else if (_blocks[j].indexOf('else') >= 0 && depth === 0) {
                            foundElse = true;
                        }
                        else {
                            if (!foundElse) helperContent += _blocks[j];
                            if (foundElse) elseContent += _blocks[j];
                        }

                    }
                    if (foundClosed) {
                        if (shiftIndex) i = shiftIndex;
                        blocks.push({
                            type: 'helper',
                            helperName: helperName,
                            contextName: helperContext,
                            content: helperContent,
                            inverseContent: elseContent,
                            hash: helperHash
                        });
                    }
                }
                else if (block.indexOf(' ') > 0) {
                    if (isPartial) {
                        helperName = '_partial';
                        if (helperContext[0]) helperContext[0] = '"' + helperContext[0].replace(/"|'/g, '') + '"';
                    }
                    blocks.push({
                        type: 'helper',
                        helperName: helperName,
                        contextName: helperContext,
                        hash: helperHash
                    });
                }
            }
        }
        return blocks;
    }
    var Template7 = function (template) {
        var t = this;
        t.template = template;

        function getCompileFn(block, depth) {
            if (block.content) return compile(block.content, depth);
            else return function () {return ''; };
        }
        function getCompileInverse(block, depth) {
            if (block.inverseContent) return compile(block.inverseContent, depth);
            else return function () {return ''; };
        }
        function getCompileVar(name, ctx) {
            var variable, parts, levelsUp = 0, initialCtx = ctx;
            if (name.indexOf('../') === 0) {
                levelsUp = name.split('../').length - 1;
                var newDepth = ctx.split('_')[1] - levelsUp;
                ctx = 'ctx_' + (newDepth >= 1 ? newDepth : 1);
                parts = name.split('../')[levelsUp].split('.');
            }
            else if (name.indexOf('@global') === 0) {
                ctx = 'Template7.global';
                parts = name.split('@global.')[1].split('.');
            }
            else if (name.indexOf('@root') === 0) {
                ctx = 'root';
                parts = name.split('@root.')[1].split('.');
            }
            else {
                parts = name.split('.');
            }
            variable = ctx;
            for (var i = 0; i < parts.length; i++) {
                var part = parts[i];
                if (part.indexOf('@') === 0) {
                    if (i > 0) {
                        variable += '[(data && data.' + part.replace('@', '') + ')]';
                    }
                    else {
                        variable = '(data && data.' + name.replace('@', '') + ')';
                    }
                }
                else {
                    if (isFinite(part)) {
                        variable += '[' + part + ']';
                    }
                    else {
                        if (part.indexOf('this') === 0) {
                            variable = part.replace('this', ctx);
                        }
                        else {
                            variable += '.' + part;
                        }
                    }
                }
            }

            return variable;
        }
        function getCompiledArguments(contextArray, ctx) {
            var arr = [];
            for (var i = 0; i < contextArray.length; i++) {
                if (contextArray[i].indexOf('"') === 0) arr.push(contextArray[i]);
                else {
                    arr.push(getCompileVar(contextArray[i], ctx));
                }
            }

            return arr.join(', ');
        }
        function compile(template, depth) {
            depth = depth || 1;
            template = template || t.template;
            if (typeof template !== 'string') {
                throw new Error('Template7: Template must be a string');
            }
            var blocks = stringToBlocks(template);
            if (blocks.length === 0) {
                return function () { return ''; };
            }
            var ctx = 'ctx_' + depth;
            var resultString = '';
            if (depth === 1) {
                resultString += '(function (' + ctx + ', data, root) {\n';
            }
            else {
                resultString += '(function (' + ctx + ', data) {\n';
            }
            if (depth === 1) {
                resultString += 'function isArray(arr){return Object.prototype.toString.apply(arr) === \'[object Array]\';}\n';
                resultString += 'function isFunction(func){return (typeof func === \'function\');}\n';
                resultString += 'function c(val, ctx) {if (typeof val !== "undefined" && val !== null) {if (isFunction(val)) {return val.call(ctx);} else return val;} else return "";}\n';
                resultString += 'root = root || ctx_1 || {};\n';
            }
            resultString += 'var r = \'\';\n';
            var i, j, context;
            for (i = 0; i < blocks.length; i++) {
                var block = blocks[i];
                // Plain block
                if (block.type === 'plain') {
                    resultString += 'r +=\'' + (block.content).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/'/g, '\\' + '\'') + '\';';
                    continue;
                }
                var variable, compiledArguments;
                // Variable block
                if (block.type === 'variable') {
                    variable = getCompileVar(block.contextName, ctx);
                    resultString += 'r += c(' + variable + ', ' + ctx + ');';
                }
                // Helpers block
                if (block.type === 'helper') {
                    if (block.helperName in t.helpers) {
                        compiledArguments = getCompiledArguments(block.contextName, ctx);

                        resultString += 'r += (Template7.helpers.' + block.helperName + ').call(' + ctx + ', ' + (compiledArguments && (compiledArguments + ', ')) +'{hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth + 1) + ', inverse: ' + getCompileInverse(block, depth + 1) + ', root: root});';

                    }
                    else {
                        if (block.contextName.length > 0) {
                            throw new Error('Template7: Missing helper: "' + block.helperName + '"');
                        }
                        else {
                            variable = getCompileVar(block.helperName, ctx);
                            resultString += 'if (' + variable + ') {';
                            resultString += 'if (isArray(' + variable + ')) {';
                            resultString += 'r += (Template7.helpers.each).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: root});';
                            resultString += '}else {';
                            resultString += 'r += (Template7.helpers.with).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: root});';
                            resultString += '}}';
                        }
                    }
                }
            }
            resultString += '\nreturn r;})';
            return eval.call(window, resultString);
        }
        t.compile = function (template) {
            if (!t.compiled) {
                t.compiled = compile(template);
            }
            return t.compiled;
        };
    };
    Template7.prototype = {
        options: {},
        partials: {},
        helpers: {
            '_partial' : function (partialName, options) {
                var p = Template7.prototype.partials[partialName];
                if (!p || (p && !p.template)) return '';
                if (!p.compiled) {
                    p.compiled = t7.compile(p.template);
                }
                var ctx = this;
                for (var hashName in options.hash) {
                    ctx[hashName] = options.hash[hashName];
                }
                return p.compiled(ctx, options.data, options.root);
            },
            'escape': function (context, options) {
                if (typeof context !== 'string') {
                    throw new Error('Template7: Passed context to "escape" helper should be a string');
                }
                return context
                        .replace(/&/g, '&')
                        .replace(/</g, '<')
                        .replace(/>/g, '>')
                        .replace(/"/g, '"');
            },
            'if': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                if (context) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            },
            'unless': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                if (!context) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            },
            'each': function (context, options) {
                var ret = '', i = 0;
                if (isFunction(context)) { context = context.call(this); }
                if (isArray(context)) {
                    if (options.hash.reverse) {
                        context = context.reverse();
                    }
                    for (i = 0; i < context.length; i++) {
                        ret += options.fn(context[i], {first: i === 0, last: i === context.length - 1, index: i});
                    }
                    if (options.hash.reverse) {
                        context = context.reverse();
                    }
                }
                else {
                    for (var key in context) {
                        i++;
                        ret += options.fn(context[key], {key: key});
                    }
                }
                if (i > 0) return ret;
                else return options.inverse(this);
            },
            'with': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                return options.fn(context);
            },
            'join': function (context, options) {
                if (isFunction(context)) { context = context.call(this); }
                return context.join(options.hash.delimiter || options.hash.delimeter);
            },
            'js': function (expression, options) {
                var func;
                if (expression.indexOf('return')>=0) {
                    func = '(function(){'+expression+'})';
                }
                else {
                    func = '(function(){return ('+expression+')})';
                }
                return eval.call(this, func).call(this);
            },
            'js_compare': function (expression, options) {
                var func;
                if (expression.indexOf('return')>=0) {
                    func = '(function(){'+expression+'})';
                }
                else {
                    func = '(function(){return ('+expression+')})';
                }
                var condition = eval.call(this, func).call(this);
                if (condition) {
                    return options.fn(this, options.data);
                }
                else {
                    return options.inverse(this, options.data);
                }
            }
        }
    };
    var t7 = function (template, data) {
        if (arguments.length === 2) {
            var instance = new Template7(template);
            var rendered = instance.compile()(data);
            instance = null;
            return (rendered);
        }
        else return new Template7(template);
    };
    t7.registerHelper = function (name, fn) {
        Template7.prototype.helpers[name] = fn;
    };
    t7.unregisterHelper = function (name) {
        Template7.prototype.helpers[name] = undefined;
        delete Template7.prototype.helpers[name];
    };
    t7.registerPartial = function (name, template) {
        Template7.prototype.partials[name] = {template: template};
    };
    t7.unregisterPartial = function (name, template) {
        if (Template7.prototype.partials[name]) {
            Template7.prototype.partials[name] = undefined;
            delete Template7.prototype.partials[name];
        }
    };

    t7.compile = function (template, options) {
        var instance = new Template7(template, options);
        return instance.compile();
    };

    t7.options = Template7.prototype.options;
    t7.helpers = Template7.prototype.helpers;
    t7.partials = Template7.prototype.partials;
    return t7;
})();<|endoftext|>


<commit_before>/**
 * @fileoverview Update view and control tree data
 * @author NHN Ent. FE dev team.<dl_javascript@nhnent.com>
 */
'use strict';

var util = require('./util'),
    nodeStates = require('./states').node;

var lastId = 0,
    snippet = tui.util,
    extend = snippet.extend,
    keys = snippet.keys,
    forEach = snippet.forEach,
    map = snippet.map,
    filter = snippet.filter,
    inArray = snippet.inArray,
    RESERVED_PROPERTIES = [
        'id',
        'parentId',
        'childIds',
        'state'
    ];

/**
 * @typedef node
 * @type {object}
 * @property {*} id - Node id
 * @property {*} parentId - Parent id
 * @property {Array.<*>} childIds - Child Ids
 * @property {number} state - OPENED or CLOSED
 * @property {object} data - Node data
 */

/**
 * Tree model
 * @constructor TreeModel
 * @param {string} defaultState - Default state of node
 * @param {Array} data - Data
 **/
var TreeModel = tui.util.defineClass(/** @lends TreeModel.prototype */{ /* eslint-disable */
    init: function(nodeDefaultState, data) {/*eslint-enable*/
        nodeDefaultState = nodeDefaultState + '';
        /**
         * Default state of node
         * @type {String}
         */
        this.nodeDefaultState = nodeStates[nodeDefaultState.toUpperCase()] || nodeStates.CLOSED;

        /**
         * A buffer
         * @type {null}
         */
        this.buffer = null;

        /**
         * Root node
         * @type {node}
         */
        this.rootNode = this._makeNode({
            state: nodeStates.OPENED
        }, null);

        /**
         * Node container
         * - Nodes are grouped by depth same as index in this container.
         * @type {object.<*, node>}
         */
        this.treeHash = {};

        this._setData(data);
    },

    /**
     * Set model with tree data
     * @param {Array} data - Tree data
     */
    _setData: function(data) {
        var root = this.rootNode;

        this.treeHash[root.id] = root;
        this._makeTreeHash(data, root);
    },

    /**
     * Change hierarchy data to hash list.
     * @param {Array} data - Tree data
     * @param {node} parent - Parent node id
     * @private
     */
    _makeTreeHash: function(data, parent) {
        var parentId = parent.id,
            childIds = parent.childIds;

        forEach(data, function(datum) {
            var node = this._makeNode(datum, parentId),
                nodeId = node.id;

            childIds.push(nodeId);
            this.treeHash[nodeId] = node;

            this._makeTreeHash(node.children, node);
            delete node.children;
        }, this);
    },

    /**
     * Create node
     * @param {object} nodeData - Datum of node
     * @param {*} parentId - Parent id
     * @return {node} Node
     */
    _makeNode: function(nodeData, parentId) {
        return extend({
            id: this._makeId(),
            parentId: parentId,
            childIds: [],
            state: this.nodeDefaultState
        }, nodeData);
    },

    _ruleOutReservedProperties: function(props) {
        return filter(props, function(prop, key) {
            return inArray(key, RESERVED_PROPERTIES) === -1;
        });
    },

    /**
     * Make and return node ID
     * @private
     * @return {number} id
     */
    _makeId: function() {
        lastId += 1;
        return lastId;
    },

    /**
     * Get children
     * @param {*} nodeId - Node id
     * @return {Array.<node>} children
     */
    getChildren: function(nodeId) {
        var node = this.getNode(nodeId);
        if (!node) {
            return;
        }

        return map(node.childIds, function(childId) {
            return this.getNode(childId);
        }, this);
    },

    /**
     * Get the number of nodes
     * @returns {number} The number of nodes
     */
    getCount: function() {
        return keys(this.treeHash).length;
    },

    /**
     * Get last depth
     * @returns {number} The last depth
     */
    getLastDepth: function() {
        var depths = map(this.treeHash, function(node) {
            return this.getDepth(node.id);
        }, this);

        return Math.max.apply(null, depths);
    },

    /**
     * Find node
     * @param {*} id - A node id to find
     * @return {node|undefined} Node
     */
    getNode: function(id) {
        return this.treeHash[id];
    },

    /**
     * Get depth from node id
     * @param {*} id - A node id to find
     * @return {number|undefined} Depth
     */
    getDepth: function(id) {
        var node = this.getNode(id),
            depth = 0,
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        while (parent) {
            depth += 1;
            parent = this.getNode(parent.parentId);
        }

        return depth;
    },

    /**
     * Remove a node with children
     * - The update event will be fired.
     * @param {*} id - Node id to remove
     * @param {boolean} [isSilent] - If true, it doesn't trigger the 'update' event
     */
    remove: function(id, isSilent) {
        var node = this.getNode(id),
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        forEach(node.childIds, function(childId) {
            this.remove(childId, true);
        }, this);

        util.removeItemFromArray(parent.childIds, id);
        delete this.treeHash[id];

        if (!isSilent) {
            this.fire('update', parent);
        }
    },

    /**
     * Add node(s)
     * - If the parentId is falsy, the node will be appended to rootNode.
     * - The update event will be fired
     * @param {Array|object} data Raw-data
     * @param {*} parentId Parent id
     */
    add: function(data, parentId) {
        var parent = this.getNode(parentId) || this.rootNode;

        data = [].concat(data);
        this._makeTreeHash(data, parent);
        this.fire('update', parent);
    },

    /**
     * Set properties of a node
     * @param {*} id Node id
     * @param {object} props Properties
     */
    set: function(id, props) {
        var node = this.getNode(id);

        props = this._ruleOutReservedProperties(props);
        if (!node || !props || snippet.isEmpty(props)) {
            return;
        }

        extend(node, props); // Update properties
        this.fire('update', node);
    },

    /**
     * Move a node to new parent's child
     * @param {*} nodeId - Node id
     * @param {*} newParentId - New parent id
     */
    move: function(nodeId, newParentId) {
        var getNode = this.getNode,
            node = getNode(id),
            originalParent = getNode(node.parentId),
            newParent = getNode(newParentId) || this.rootNode;

        if (!node) {
            return;
        }

        util.removeItemFromArray(originalParent.childIds, nodeId);
        newParent.childIds.push(nodeId);

        this.fire('move', node, originalParent, newParent);
    },

    /**
     * Sort nodes
     * @param {Function} comparator - Comparator function
     */
    sort: function(comparator) {
        this.each(function(node) {
            var children = this.getChildren(node.id);

            if (children.length > 1) {
                children.sort(comparator);

                node.childIds = map(children, function(child) {
                    return child.id;
                });
            }
        });
    },

    /**
     * Traverse this tree iterating over all nodes.
     * @param {Function} iteratee - Iteratee function
     * @param {object} [context] - Context of iteratee
     */
    each: function(iteratee, context) {
        context = context || this;

        forEach(this.treeHash, function() {
            iteratee.apply(context, arguments);
        });
    }
});

tui.util.CustomEvents.mixin(TreeModel);
module.exports = TreeModel;
<commit_msg>style: treeModel.js  comments, rename: makeNode -> createNode<commit_after>/**
 * @fileoverview Update view and control tree data
 * @author NHN Ent. FE dev team.<dl_javascript@nhnent.com>
 */
'use strict';

var util = require('./util'),
    nodeStates = require('./states').node;

var lastId = 0,
    snippet = tui.util,
    extend = snippet.extend,
    keys = snippet.keys,
    forEach = snippet.forEach,
    map = snippet.map,
    filter = snippet.filter,
    inArray = snippet.inArray,
    RESERVED_PROPERTIES = [
        'id',
        'parentId',
        'childIds',
        'state'
    ];

/**
 * @typedef node
 * @type {object}
 * @property {*} id - Node id
 * @property {*} parentId - Parent id
 * @property {Array.<*>} childIds - Child Ids
 * @property {number} state - OPENED or CLOSED
 * @property {object} data - Node data
 */

/**
 * Tree model
 * @constructor TreeModel
 * @param {string} defaultState - Default state of node
 * @param {Array} data - Data
 **/
var TreeModel = tui.util.defineClass(/** @lends TreeModel.prototype */{ /* eslint-disable */
    init: function(nodeDefaultState, data) {/*eslint-enable*/
        nodeDefaultState = nodeDefaultState + '';
        /**
         * Default state of node
         * @type {String}
         */
        this.nodeDefaultState = nodeStates[nodeDefaultState.toUpperCase()] || nodeStates.CLOSED;

        /**
         * A buffer
         * @type {null}
         */
        this.buffer = null;

        /**
         * Root node
         * @type {node}
         */
        this.rootNode = this._createNode({
            state: nodeStates.OPENED
        }, null);

        /**
         * Tree hash having all nodes
         * @type {object.<*, node>}
         */
        this.treeHash = {};

        this._setData(data);
    },

    /**
     * Set model with tree data
     * @param {Array} data - Tree data
     */
    _setData: function(data) {
        var root = this.rootNode;

        this.treeHash[root.id] = root;
        this._makeTreeHash(data, root);
    },

    /**
     * Make tree hash from data and parentNode
     * @param {Array} data - Tree data
     * @param {node} parent - Parent node id
     * @private
     */
    _makeTreeHash: function(data, parent) {
        var parentId = parent.id,
            childIds = parent.childIds;

        forEach(data, function(datum) {
            var node = this._createNode(datum, parentId),
                nodeId = node.id;

            childIds.push(nodeId);
            this.treeHash[nodeId] = node;

            this._makeTreeHash(node.children, node);
            delete node.children;
        }, this);
    },

    /**
     * Create node
     * @param {object} nodeData - Datum of node
     * @param {*} parentId - Parent id
     * @return {node} Node
     * @todo : return new Node(nodeData, parentId)
     */
    _createNode: function(nodeData, parentId) {
        return extend({
            id: this._makeId(),
            parentId: parentId,
            childIds: [],
            state: this.nodeDefaultState
        }, nodeData);
    },

    /**
     * Rule out reserved properties
     * @param {object} props - Raw properties
     * @returns {Object} Properties without reserved properties
     * @private
     */
    _ruleOutReservedProperties: function(props) {
        return filter(props, function(prop, key) {
            return inArray(key, RESERVED_PROPERTIES) === -1;
        });
    },

    /**
     * Make and return node ID
     * @private
     * @return {number} id
     */
    _makeId: function() {
        lastId += 1;
        return lastId;
    },

    /**
     * Get children
     * @param {*} nodeId - Node id
     * @return {Array.<node>} children
     */
    getChildren: function(nodeId) {
        var node = this.getNode(nodeId);
        if (!node) {
            return [];
        }

        return map(node.childIds, function(childId) {
            return this.getNode(childId);
        }, this);
    },

    /**
     * Get the number of nodes
     * @returns {number} The number of nodes
     */
    getCount: function() {
        return keys(this.treeHash).length;
    },

    /**
     * Get last depth
     * @returns {number} The last depth
     */
    getLastDepth: function() {
        var depths = map(this.treeHash, function(node) {
            return this.getDepth(node.id);
        }, this);

        return Math.max.apply(null, depths);
    },

    /**
     * Find node
     * @param {*} id - A node id to find
     * @return {node|undefined} Node
     */
    getNode: function(id) {
        return this.treeHash[id];
    },

    /**
     * Get depth from node id
     * @param {*} id - A node id to find
     * @return {number|undefined} Depth
     * //@todo : default return value?
     */
    getDepth: function(id) {
        var node = this.getNode(id),
            depth = 0,
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        while (parent) {
            depth += 1;
            parent = this.getNode(parent.parentId);
        }

        return depth;
    },

    /**
     * Remove a node with children.
     * - The update event will be fired with parent node.
     * @param {*} id - Node id to remove
     * @param {boolean} [isSilent] - If true, it doesn't trigger the 'update' event
     */
    remove: function(id, isSilent) {
        var node = this.getNode(id),
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        forEach(node.childIds, function(childId) {
            this.remove(childId, true);
        }, this);

        util.removeItemFromArray(parent.childIds, id);
        delete this.treeHash[id];

        if (!isSilent) {
            this.fire('update', parent);
        }
    },

    /**
     * Add node(s).
     * - If the parentId is falsy, the node will be appended to rootNode.
     * - The update event will be fired with parent node.
     * @param {Array|object} data - Raw-data
     * @param {*} parentId - Parent id
     */
    add: function(data, parentId) {
        var parent = this.getNode(parentId) || this.rootNode;

        data = [].concat(data);
        this._makeTreeHash(data, parent);
        this.fire('update', parent);
    },

    /**
     * Set properties of a node
     * @param {*} id Node id
     * @param {object} props Properties
     */
    set: function(id, props) {
        var node = this.getNode(id);

        props = this._ruleOutReservedProperties(props);
        if (!node || !props || snippet.isEmpty(props)) {
            return;
        }

        extend(node, props); // Update properties
        this.fire('update', node);
    },

    /**
     * Move a node to new parent's child
     * @param {*} nodeId - Node id
     * @param {*} newParentId - New parent id
     */
    move: function(nodeId, newParentId) {
        var getNode = this.getNode,
            node = getNode(id),
            originalParent = getNode(node.parentId),
            newParent = getNode(newParentId) || this.rootNode;

        if (!node) {
            return;
        }

        util.removeItemFromArray(originalParent.childIds, nodeId);
        newParent.childIds.push(nodeId);

        this.fire('move', node, originalParent, newParent);
    },

    /**
     * Sort nodes
     * @param {Function} comparator - Comparator function
     */
    sort: function(comparator) {
        this.each(function(node) {
            var children = this.getChildren(node.id);

            if (children.length > 1) {
                children.sort(comparator);

                node.childIds = map(children, function(child) {
                    return child.id;
                });
            }
        });
    },

    /**
     * Traverse this tree iterating over all nodes.
     * @param {Function} iteratee - Iteratee function
     * @param {object} [context] - Context of iteratee
     */
    each: function(iteratee, context) {
        context = context || this;

        forEach(this.treeHash, function() {
            iteratee.apply(context, arguments);
        });
    }
});

tui.util.CustomEvents.mixin(TreeModel);
module.exports = TreeModel;
<|endoftext|>


<commit_before>/**
 * @fileoverview Update view and control tree data
 * @author NHN Ent. FE dev team.<dl_javascript@nhnent.com>
 */
'use strict';

var util = require('./util'),
    nodeStates = require('./states').node;

var lastId = 0,
    snippet = tui.util,
    extend = snippet.extend,
    keys = snippet.keys,
    forEach = snippet.forEach,
    map = snippet.map,
    filter = snippet.filter,
    inArray = snippet.inArray,
    RESERVED_PROPERTIES = [
        'id',
        'parentId',
        'childIds',
        'state'
    ];

/**
 * @typedef node
 * @type {object}
 * @property {*} id - Node id
 * @property {*} parentId - Parent id
 * @property {Array.<*>} childIds - Child Ids
 * @property {number} state - OPENED or CLOSED
 * @property {object} data - Node data
 */

/**
 * Tree model
 * @constructor TreeModel
 * @param {string} defaultState - Default state of node
 * @param {Array} data - Data
 **/
var TreeModel = tui.util.defineClass(/** @lends TreeModel.prototype */{ /* eslint-disable */
    init: function(nodeDefaultState, data) {/*eslint-enable*/
        nodeDefaultState = nodeDefaultState + '';
        /**
         * Default state of node
         * @type {String}
         */
        this.nodeDefaultState = nodeStates[nodeDefaultState.toUpperCase()] || nodeStates.CLOSED;

        /**
         * A buffer
         * @type {null}
         */
        this.buffer = null;

        /**
         * Root node
         * @type {node}
         */
        this.rootNode = this._createNode({
            state: nodeStates.OPENED
        }, null);

        /**
         * Tree hash having all nodes
         * @type {object.<*, node>}
         */
        this.treeHash = {};

        this._setData(data);
    },

    /**
     * Set model with tree data
     * @param {Array} data - Tree data
     */
    _setData: function(data) {
        var root = this.rootNode;

        this.treeHash[root.id] = root;
        this._makeTreeHash(data, root);
    },

    /**
     * Make tree hash from data and parentNode
     * @param {Array} data - Tree data
     * @param {node} parent - Parent node id
     * @private
     */
    _makeTreeHash: function(data, parent) {
        var parentId = parent.id,
            childIds = parent.childIds;

        forEach(data, function(datum) {
            var node = this._createNode(datum, parentId),
                nodeId = node.id;

            childIds.push(nodeId);
            this.treeHash[nodeId] = node;

            this._makeTreeHash(node.children, node);
            delete node.children;
        }, this);
    },

    /**
     * Create node
     * @param {object} nodeData - Datum of node
     * @param {*} parentId - Parent id
     * @return {node} Node
     * @todo : return new Node(nodeData, parentId)
     */
    _createNode: function(nodeData, parentId) {
        return extend({
            id: this._makeId(),
            parentId: parentId,
            childIds: [],
            state: this.nodeDefaultState
        }, nodeData);
    },

    /**
     * Rule out reserved properties
     * @param {object} props - Raw properties
     * @returns {Object} Properties without reserved properties
     * @private
     */
    _ruleOutReservedProperties: function(props) {
        return filter(props, function(prop, key) {
            return inArray(key, RESERVED_PROPERTIES) === -1;
        });
    },

    /**
     * Make and return node ID
     * @private
     * @return {number} id
     */
    _makeId: function() {
        lastId += 1;
        return lastId;
    },

    /**
     * Get children
     * @param {*} nodeId - Node id
     * @return {Array.<node>} children
     */
    getChildren: function(nodeId) {
        var node = this.getNode(nodeId);
        if (!node) {
            return [];
        }

        return map(node.childIds, function(childId) {
            return this.getNode(childId);
        }, this);
    },

    /**
     * Get the number of nodes
     * @returns {number} The number of nodes
     */
    getCount: function() {
        return keys(this.treeHash).length;
    },

    /**
     * Get last depth
     * @returns {number} The last depth
     */
    getLastDepth: function() {
        var depths = map(this.treeHash, function(node) {
            return this.getDepth(node.id);
        }, this);

        return Math.max.apply(null, depths);
    },

    /**
     * Find node
     * @param {*} id - A node id to find
     * @return {node|undefined} Node
     */
    getNode: function(id) {
        return this.treeHash[id];
    },

    /**
     * Get depth from node id
     * @param {*} id - A node id to find
     * @return {number|undefined} Depth
     * //@todo : default return value?
     */
    getDepth: function(id) {
        var node = this.getNode(id),
            depth = 0,
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        while (parent) {
            depth += 1;
            parent = this.getNode(parent.parentId);
        }

        return depth;
    },

    /**
     * Remove a node with children.
     * - The update event will be fired with parent node.
     * @param {*} id - Node id to remove
     * @param {boolean} [isSilent] - If true, it doesn't trigger the 'update' event
     */
    remove: function(id, isSilent) {
        var node = this.getNode(id),
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        forEach(node.childIds, function(childId) {
            this.remove(childId, true);
        }, this);

        util.removeItemFromArray(parent.childIds, id);
        delete this.treeHash[id];

        if (!isSilent) {
            this.fire('update', parent);
        }
    },

    /**
     * Add node(s).
     * - If the parentId is falsy, the node will be appended to rootNode.
     * - The update event will be fired with parent node.
     * @param {Array|object} data - Raw-data
     * @param {*} parentId - Parent id
     */
    add: function(data, parentId) {
        var parent = this.getNode(parentId) || this.rootNode;

        data = [].concat(data);
        this._makeTreeHash(data, parent);
        this.fire('update', parent);
    },

    /**
     * Set properties of a node
     * @param {*} id Node id
     * @param {object} props Properties
     */
    set: function(id, props) {
        var node = this.getNode(id);

        props = this._ruleOutReservedProperties(props);
        if (!node || !props || snippet.isEmpty(props)) {
            return;
        }

        extend(node, props); // Update properties
        this.fire('update', node);
    },

    /**
     * Move a node to new parent's child
     * @param {*} nodeId - Node id
     * @param {*} newParentId - New parent id
     */
    move: function(nodeId, newParentId) {
        var getNode = this.getNode,
            node = getNode(id),
            originalParent = getNode(node.parentId),
            newParent = getNode(newParentId) || this.rootNode;

        if (!node) {
            return;
        }

        util.removeItemFromArray(originalParent.childIds, nodeId);
        newParent.childIds.push(nodeId);

        this.fire('move', node, originalParent, newParent);
    },

    /**
     * Sort nodes
     * @param {Function} comparator - Comparator function
     */
    sort: function(comparator) {
        this.each(function(node) {
            var children = this.getChildren(node.id);

            if (children.length > 1) {
                children.sort(comparator);

                node.childIds = map(children, function(child) {
                    return child.id;
                });
            }
        });
    },

    /**
     * Traverse this tree iterating over all nodes.
     * @param {Function} iteratee - Iteratee function
     * @param {object} [context] - Context of iteratee
     */
    each: function(iteratee, context) {
        context = context || this;

        forEach(this.treeHash, function() {
            iteratee.apply(context, arguments);
        });
    }
});

tui.util.CustomEvents.mixin(TreeModel);
module.exports = TreeModel;
<commit_msg>refactor: treemodel.js method rename: makeId -> createId<commit_after>/**
 * @fileoverview Update view and control tree data
 * @author NHN Ent. FE dev team.<dl_javascript@nhnent.com>
 */
'use strict';

var util = require('./util'),
    nodeStates = require('./states').node;

var lastId = 0,
    snippet = tui.util,
    extend = snippet.extend,
    keys = snippet.keys,
    forEach = snippet.forEach,
    map = snippet.map,
    filter = snippet.filter,
    inArray = snippet.inArray,
    RESERVED_PROPERTIES = [
        'id',
        'parentId',
        'childIds',
        'state'
    ];

/**
 * @typedef node
 * @type {object}
 * @property {*} id - Node id
 * @property {*} parentId - Parent id
 * @property {Array.<*>} childIds - Child Ids
 * @property {number} state - OPENED or CLOSED
 * @property {object} data - Node data
 */

/**
 * Tree model
 * @constructor TreeModel
 * @param {string} defaultState - Default state of node
 * @param {Array} data - Data
 **/
var TreeModel = tui.util.defineClass(/** @lends TreeModel.prototype */{ /* eslint-disable */
    init: function(nodeDefaultState, data) {/*eslint-enable*/
        nodeDefaultState = nodeDefaultState + '';
        /**
         * Default state of node
         * @type {String}
         */
        this.nodeDefaultState = nodeStates[nodeDefaultState.toUpperCase()] || nodeStates.CLOSED;

        /**
         * A buffer
         * @type {null}
         */
        this.buffer = null;

        /**
         * Root node
         * @type {node}
         */
        this.rootNode = this._createNode({
            state: nodeStates.OPENED
        }, null);

        /**
         * Tree hash having all nodes
         * @type {object.<*, node>}
         */
        this.treeHash = {};

        this._setData(data);
    },

    /**
     * Set model with tree data
     * @param {Array} data - Tree data
     */
    _setData: function(data) {
        var root = this.rootNode;

        this.treeHash[root.id] = root;
        this._makeTreeHash(data, root);
    },

    /**
     * Make tree hash from data and parentNode
     * @param {Array} data - Tree data
     * @param {node} parent - Parent node id
     * @private
     */
    _makeTreeHash: function(data, parent) {
        var parentId = parent.id,
            childIds = parent.childIds;

        forEach(data, function(datum) {
            var node = this._createNode(datum, parentId),
                nodeId = node.id;

            childIds.push(nodeId);
            this.treeHash[nodeId] = node;

            this._makeTreeHash(node.children, node);
            delete node.children;
        }, this);
    },

    /**
     * Create node
     * @param {object} nodeData - Datum of node
     * @param {*} parentId - Parent id
     * @return {node} Node
     * @todo : return new Node(nodeData, parentId)
     */
    _createNode: function(nodeData, parentId) {
        return extend({
            id: this._createId(),
            parentId: parentId,
            childIds: [],
            state: this.nodeDefaultState
        }, nodeData);
    },

    /**
     * Rule out reserved properties
     * @param {object} props - Raw properties
     * @returns {Object} Properties without reserved properties
     * @private
     */
    _ruleOutReservedProperties: function(props) {
        return filter(props, function(prop, key) {
            return inArray(key, RESERVED_PROPERTIES) === -1;
        });
    },

    /**
     * Return new node ID
     * @private
     * @return {number} id
     */
    _createId: function() {
        lastId += 1;
        return lastId;
    },

    /**
     * Get children
     * @param {*} nodeId - Node id
     * @return {Array.<node>} children
     */
    getChildren: function(nodeId) {
        var node = this.getNode(nodeId);
        if (!node) {
            return [];
        }

        return map(node.childIds, function(childId) {
            return this.getNode(childId);
        }, this);
    },

    /**
     * Get the number of nodes
     * @returns {number} The number of nodes
     */
    getCount: function() {
        return keys(this.treeHash).length;
    },

    /**
     * Get last depth
     * @returns {number} The last depth
     */
    getLastDepth: function() {
        var depths = map(this.treeHash, function(node) {
            return this.getDepth(node.id);
        }, this);

        return Math.max.apply(null, depths);
    },

    /**
     * Find node
     * @param {*} id - A node id to find
     * @return {node|undefined} Node
     */
    getNode: function(id) {
        return this.treeHash[id];
    },

    /**
     * Get depth from node id
     * @param {*} id - A node id to find
     * @return {number|undefined} Depth
     * //@todo : default return value?
     */
    getDepth: function(id) {
        var node = this.getNode(id),
            depth = 0,
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        while (parent) {
            depth += 1;
            parent = this.getNode(parent.parentId);
        }

        return depth;
    },

    /**
     * Remove a node with children.
     * - The update event will be fired with parent node.
     * @param {*} id - Node id to remove
     * @param {boolean} [isSilent] - If true, it doesn't trigger the 'update' event
     */
    remove: function(id, isSilent) {
        var node = this.getNode(id),
            parent;

        if (!node) {
            return;
        }

        parent = this.getNode(node.parentId);
        forEach(node.childIds, function(childId) {
            this.remove(childId, true);
        }, this);

        util.removeItemFromArray(parent.childIds, id);
        delete this.treeHash[id];

        if (!isSilent) {
            this.fire('update', parent);
        }
    },

    /**
     * Add node(s).
     * - If the parentId is falsy, the node will be appended to rootNode.
     * - The update event will be fired with parent node.
     * @param {Array|object} data - Raw-data
     * @param {*} parentId - Parent id
     */
    add: function(data, parentId) {
        var parent = this.getNode(parentId) || this.rootNode;

        data = [].concat(data);
        this._makeTreeHash(data, parent);
        this.fire('update', parent);
    },

    /**
     * Set properties of a node
     * @param {*} id Node id
     * @param {object} props Properties
     */
    set: function(id, props) {
        var node = this.getNode(id);

        props = this._ruleOutReservedProperties(props);
        if (!node || !props || snippet.isEmpty(props)) {
            return;
        }

        extend(node, props); // Update properties
        this.fire('update', node);
    },

    /**
     * Move a node to new parent's child
     * @param {*} nodeId - Node id
     * @param {*} newParentId - New parent id
     */
    move: function(nodeId, newParentId) {
        var getNode = this.getNode,
            node = getNode(id),
            originalParent = getNode(node.parentId),
            newParent = getNode(newParentId) || this.rootNode;

        if (!node) {
            return;
        }

        util.removeItemFromArray(originalParent.childIds, nodeId);
        newParent.childIds.push(nodeId);

        this.fire('move', node, originalParent, newParent);
    },

    /**
     * Sort nodes
     * @param {Function} comparator - Comparator function
     */
    sort: function(comparator) {
        this.each(function(node) {
            var children = this.getChildren(node.id);

            if (children.length > 1) {
                children.sort(comparator);

                node.childIds = map(children, function(child) {
                    return child.id;
                });
            }
        });
    },

    /**
     * Traverse this tree iterating over all nodes.
     * @param {Function} iteratee - Iteratee function
     * @param {object} [context] - Context of iteratee
     */
    each: function(iteratee, context) {
        context = context || this;

        forEach(this.treeHash, function() {
            iteratee.apply(context, arguments);
        });
    }
});

tui.util.CustomEvents.mixin(TreeModel);
module.exports = TreeModel;
<|endoftext|>


<commit_before>// Vex Flow Notation
// Implements key signatures
//
// Requires vex.js.

/**
 * @constructor
 */
Vex.Flow.KeySignature = function(keySpec) {
  if (arguments.length > 0) this.init(keySpec);
}

Vex.Flow.KeySignature.prototype = new Vex.Flow.StaveModifier();
Vex.Flow.KeySignature.prototype.constructor = Vex.Flow.KeySignature;
Vex.Flow.KeySignature.superclass = Vex.Flow.StaveModifier.prototype;

Vex.Flow.KeySignature.prototype.init = function(key_spec) {
  Vex.Flow.KeySignature.superclass.init();

  this.glyphFontScale = 38;
  this.accList = Vex.Flow.keySignature(key_spec);
}

Vex.Flow.KeySignature.prototype.addAccToStave = function(stave, acc) {
  var glyph = new Vex.Flow.Glyph(acc.glyphCode, this.glyphFontScale);
  this.placeGlyphOnLine(glyph, stave, acc.line)
  stave.addGlyph(glyph);
}

Vex.Flow.KeySignature.prototype.addModifier = function(stave) {
  this.convertAccLines(stave.clef, this.accList[0].glyphCode);
  for (var i = 0; i < this.accList.length; ++i) {
    this.addAccToStave(stave, this.accList[i]);
  }
}

Vex.Flow.KeySignature.prototype.addToStave = function(stave, firstGlyph) {
  if (this.accList.length == 0)
    return this;

  if (!firstGlyph) {
    stave.addGlyph(this.makeSpacer(this.padding));
  }

  this.addModifier(stave);
  return this;
}

Vex.Flow.KeySignature.prototype.convertAccLines = function(clef, code) {
  var offset = 0.0; //if clef === "treble"
  var tenorSharps;
  var isTenorSharps = ((clef === "tenor") && (code === "v18")) ? true : false;

  switch (clef) {
    case "bass":
      offset = 1;
      break;
    case "alto":
      offset = 0.5;
      break;
    case "tenor":
      if (!isTenorSharps) {
        offset = -0.5;
      }
      break;
  }
  if (isTenorSharps) {
    tenorSharps = [3, 1, 2.5, 0.5, 2, 0, 1.5];
    for (var i = 0; i < this.accList.length; ++i) {
      this.accList[i].line = tenorSharps[i];
    }
  }
  else {
    if (clef != "treble") {
      for (var i = 0; i < this.accList.length; ++i) {
        this.accList[i].line += offset;
      }
    }
  }
}
<commit_msg>Added some clarifying comments.<commit_after>// Vex Flow Notation
// Implements key signatures
//
// Requires vex.js.

/**
 * @constructor
 */
Vex.Flow.KeySignature = function(keySpec) {
  if (arguments.length > 0) this.init(keySpec);
}

Vex.Flow.KeySignature.prototype = new Vex.Flow.StaveModifier();
Vex.Flow.KeySignature.prototype.constructor = Vex.Flow.KeySignature;
Vex.Flow.KeySignature.superclass = Vex.Flow.StaveModifier.prototype;

Vex.Flow.KeySignature.prototype.init = function(key_spec) {
  Vex.Flow.KeySignature.superclass.init();

  this.glyphFontScale = 38;
  this.accList = Vex.Flow.keySignature(key_spec);
}

Vex.Flow.KeySignature.prototype.addAccToStave = function(stave, acc) {
  var glyph = new Vex.Flow.Glyph(acc.glyphCode, this.glyphFontScale);
  this.placeGlyphOnLine(glyph, stave, acc.line)
  stave.addGlyph(glyph);
}

Vex.Flow.KeySignature.prototype.addModifier = function(stave) {
  this.convertAccLines(stave.clef, this.accList[0].glyphCode);
  for (var i = 0; i < this.accList.length; ++i) {
    this.addAccToStave(stave, this.accList[i]);
  }
}

Vex.Flow.KeySignature.prototype.addToStave = function(stave, firstGlyph) {
  if (this.accList.length == 0)
    return this;

  if (!firstGlyph) {
    stave.addGlyph(this.makeSpacer(this.padding));
  }

  this.addModifier(stave);
  return this;
}

Vex.Flow.KeySignature.prototype.convertAccLines = function(clef, code) {
  var offset = 0.0; // if clef === "treble"
  var tenorSharps;
  var isTenorSharps = ((clef === "tenor") && (code === "v18")) ? true : false;

  switch (clef) {
    case "bass":
      offset = 1;
      break;
    case "alto":
      offset = 0.5;
      break;
    case "tenor":
      if (!isTenorSharps) {
        offset = -0.5;
      }
      break;
  }

  // Special-case for TenorSharps
  if (isTenorSharps) {
    tenorSharps = [3, 1, 2.5, 0.5, 2, 0, 1.5];
    for (var i = 0; i < this.accList.length; ++i) {
      this.accList[i].line = tenorSharps[i];
    }
  }
  else {
    if (clef != "treble") {
      for (var i = 0; i < this.accList.length; ++i) {
        this.accList[i].line += offset;
      }
    }
  }
}
<|endoftext|>


<commit_before>;(function ( $, window, document, undefined ) {

	var o                    = {};
	var pluginName           = 'KistPostimg';
	var pluginDomNamespace   = 'kist-postimg';
	var pluginClassNamespace = 'KistPostimg';
	var pluginEventNamespace = 'kist.postimg';

	var PluginModule = function ( element, options ) {

		this._element  = element;
		this.settings  = $.extend( {}, this.defaults, options );

	};

	/**
	 * Default options
	 *
	 * @type {Object}
	 */
	o.defaults = {
		threshold: 300,
		scrollTimeout: 300,
		loadType: 'postpone'
	};

	o.domRefs = {
		windowEl: $(window)
	};

	/**
	 * Initialize plugin
	 *
	 * @return {Plugin}
	 */
	o.init = function () {

		this.checkPostImgStateDfd = $.Deferred();

		this.getDomRefs();

		// Does the current collection have any images which are already defined
		// as postimg images? If that’s true, filter those images and return
		// only those which are candidtes for postimg resolution.
		// Otherwise, if there is no image returned, don’t run any event setup
		// or image parsing methods.
		$.when( this.checkPostImgState() ).done($.proxy( function () {

			this.bindUiActions();
			this.fetchPostponedImages();
			this.fetchLazyLoadedImages();

		}, this));

		return this;

	};

	/**
	 * Get DOM references
	 *
	 * @return {Plugin}
	 */
	o.getDomRefs = function () {

		this.domRefs          = $.extend({}, this.domRefs);
		this.domRefs.imagesEl = $( this._element );

	};

	o.bindUiActions = function () {

		this.domRefs.windowEl.on( 'scroll.' + pluginEventNamespace, $.debounce(this.settings.scrollTimeout, $.proxy( this.fetchPostponedImages, this )));
		this.domRefs.windowEl.on( 'resize.' + pluginEventNamespace, $.debounce(this.settings.scrollTimeout, $.proxy( this.fetchPostponedImages, this )));

	};

	/**
	 * Check if images already have attached postimg state
	 *
	 * @return {Promise}
	 */
	o.checkPostImgState = function () {

		this.domRefs.imagesEl.each($.proxy( function ( mIndex, mElement ) {

			var element = $(mElement);

			if ( Boolean( element.data('isPostImgAlreadySet') ) === false ) {

				element.data('isPostImgAlreadySet', true);
				element.addClass( pluginClassNamespace );

			} else {

				this.removeFromImageCollection( element );

			}

		}, this));

		if ( this.domRefs.imagesEl.length !== 0 ) {
			this.checkPostImgStateDfd.resolve();
		} else {
			this.checkPostImgStateDfd.reject();
		}

		return this.checkPostImgStateDfd.promise();

	};

	/**
	 * Fetch and parse images based on viewport size
	 *
	 * @return {Ui}
	 */
	o.fetchPostponedImages = function () {

		// Don’t do anything if we’re not dealing with postpone collection
		if ( this.settings.loadType !== 'postpone' ) {
			return;
		}

		// Parse through visible images
		this.parseImages( this.domRefs.imagesEl.KistInView('getElementsInView', this.settings.threshold) );

	};

	/**
	 * Fetch and parse lazyloaded images
	 *
	 * @return {Ui}
	 */
	o.fetchLazyLoadedImages = function () {

		// Don’t do anything if we’re not dealing with lazyload collection
		if ( this.settings.loadType !== 'lazyload' ) {
			return;
		}

		// Parse through images
		this.parseImages( this.domRefs.imagesEl );

	};

	/**
	 * Parse images
	 *
	 * @param  {$Object} arrImages
	 *
	 * @return {Ui}
	 */
	o.parseImages = function ( arrImages ) {

		arrImages.each($.proxy( function ( index, element ) {

			var imageEl = $(element);

			$.loadImage( imageEl.data('src') ).done($.proxy( function () {

				imageEl
					.attr('src', imageEl.data('src'))
					.attr('alt', imageEl.data('alt'))
					.removeAttr('width').removeAttr('height')
					.addClass( pluginClassNamespace + '--is-loaded' );

				this.domRefs.imagesEl = this.domRefs.imagesEl.not( imageEl );

			}, this));

		}, this));

	};

	/**
	 * Add to image collection
	 *
	 * @param  {$Object} pCollection
	 *
	 * @return {Array}
	 */
	o.addToImageCollection = function ( pCollection ) {

		this.domRefs['imagesEl'] = this.domRefs['imagesEl'].add( $(pCollection) );

	};

	/**
	 * Remove from image collection
	 *
	 * @param  {$Object} pCollection
	 *
	 * @return {Array}
	 */
	o.removeFromImageCollection = function ( pCollection ) {

		this.domRefs['imagesEl'] = this.domRefs['imagesEl'].not( $(pCollection) );

	};

	$.extend( PluginModule.prototype, o );

	$[ pluginName ]           = {};
	$[ pluginName ].instances = [];
	$[ pluginName ].defaults  = PluginModule.prototype.defaults;

	/**
	 * Fetch images for all plugin instances
	 *
	 * @return {Ui}
	 */
	$[ pluginName ].fetchAllImages = function () {

		$.each( this.instances, function ( index, instance ) {

			instance.fetchPostponedImages();
			instance.fetchLazyLoadedImages();

		});

	};

	$.fn[ pluginName ] = function ( options ) {

		$[ pluginName ].instances.push( new PluginModule( this, options ).init() );

		return this;

	};

})( jQuery, window, document );
<commit_msg>Refactor filtering and remove unecessary methods<commit_after>;(function ( $, window, document, undefined ) {

	var o                    = {};
	var pluginName           = 'KistPostimg';
	var pluginDomNamespace   = 'kist-postimg';
	var pluginClassNamespace = 'KistPostimg';
	var pluginEventNamespace = 'kist.postimg';

	var PluginModule = function ( element, options ) {

		this._element  = element;
		this.settings  = $.extend( {}, this.defaults, options );

	};

	/**
	 * Default options
	 *
	 * @type {Object}
	 */
	o.defaults = {
		threshold: 300,
		scrollTimeout: 300,
		loadType: 'postpone'
	};

	o.domRefs = {
		windowEl: $(window)
	};

	/**
	 * Initialize plugin
	 *
	 * @return {Plugin}
	 */
	o.init = function () {

		this.checkPostImgStateDfd = $.Deferred();

		this.getDomRefs();

		// Does the current collection have any images which are already defined
		// as postimg images? If that’s true, filter those images and return
		// only those which are candidtes for postimg resolution.
		// Otherwise, if there is no image returned, don’t run any event setup
		// or image parsing methods.
		$.when( this.checkPostImgState() ).done($.proxy( function () {

			this.bindUiActions();
			this.fetchPostponedImages();
			this.fetchLazyLoadedImages();

		}, this));

		return this;

	};

	/**
	 * Get DOM references
	 *
	 * @return {Plugin}
	 */
	o.getDomRefs = function () {

		this.domRefs          = $.extend({}, this.domRefs);
		this.domRefs.imagesEl = $( this._element );

	};

	o.bindUiActions = function () {

		this.domRefs.windowEl.on( 'scroll.' + pluginEventNamespace, $.debounce(this.settings.scrollTimeout, $.proxy( this.fetchPostponedImages, this )));
		this.domRefs.windowEl.on( 'resize.' + pluginEventNamespace, $.debounce(this.settings.scrollTimeout, $.proxy( this.fetchPostponedImages, this )));

	};

	/**
	 * Check if images already have attached postimg state
	 *
	 * @return {Promise}
	 */
	o.checkPostImgState = function () {

		// Filter only images which are not postimg images
		this.domRefs.imagesEl = this.domRefs.imagesEl.filter(function () { return Boolean( $(this).data('isPostImgAlreadySet') ) === false; });

		// Assign data to those images
		this.domRefs.imagesEl
			.data('isPostImgAlreadySet', true)
			.addClass( pluginClassNamespace );

		if ( this.domRefs.imagesEl.length !== 0 ) {
			this.checkPostImgStateDfd.resolve();
		} else {
			this.checkPostImgStateDfd.reject();
		}

		return this.checkPostImgStateDfd.promise();

	};

	/**
	 * Fetch and parse images based on viewport size
	 *
	 * @return {Ui}
	 */
	o.fetchPostponedImages = function () {

		// Don’t do anything if we’re not dealing with postpone collection
		if ( this.settings.loadType !== 'postpone' ) {
			return;
		}

		// Parse through visible images
		this.parseImages( this.domRefs.imagesEl.KistInView('getElementsInView', this.settings.threshold) );

	};

	/**
	 * Fetch and parse lazyloaded images
	 *
	 * @return {Ui}
	 */
	o.fetchLazyLoadedImages = function () {

		// Don’t do anything if we’re not dealing with lazyload collection
		if ( this.settings.loadType !== 'lazyload' ) {
			return;
		}

		// Parse through images
		this.parseImages( this.domRefs.imagesEl );

	};

	/**
	 * Parse images
	 *
	 * @param  {$Object} arrImages
	 *
	 * @return {Ui}
	 */
	o.parseImages = function ( arrImages ) {

		arrImages.each($.proxy( function ( index, element ) {

			var imageEl = $(element);

			$.loadImage( imageEl.data('src') ).done($.proxy( function () {

				imageEl
					.attr('src', imageEl.data('src'))
					.attr('alt', imageEl.data('alt'))
					.removeAttr('width').removeAttr('height')
					.addClass( pluginClassNamespace + '--is-loaded' );

				this.domRefs.imagesEl = this.domRefs.imagesEl.not( imageEl );

			}, this));

		}, this));

	};

	$.extend( PluginModule.prototype, o );

	$[ pluginName ]           = {};
	$[ pluginName ].instances = [];
	$[ pluginName ].defaults  = PluginModule.prototype.defaults;

	/**
	 * Fetch images for all plugin instances
	 *
	 * @return {Ui}
	 */
	$[ pluginName ].fetchAllImages = function () {

		$.each( this.instances, function ( index, instance ) {

			instance.fetchPostponedImages();
			instance.fetchLazyLoadedImages();

		});

	};

	$.fn[ pluginName ] = function ( options ) {

		$[ pluginName ].instances.push( new PluginModule( this, options ).init() );

		return this;

	};

})( jQuery, window, document );
<|endoftext|>


<commit_before>'use strict';

var _ = require('underscore'),
    inspect = require('util').inspect;

/**
 * Run multiple behat feature files in parallel.
 *
 * Example usage:
 *
 * var behat = new BehatTask({
 *     files: ['feature1.feature', 'feature2.feature'],
 *     log: console.log,
 *     bin: 'behat',
 *     flags: '--tags @wip',
 *     executor: new ParallelExec(5),
 *     numRetries: 0
 * })
 *
 * @param {Object} options
 */
function BehatTask (options) {
    var tasks = {},
        failedTasks = {},
        startTime;

    /**
     * Create a behat command for each file and run it using the executor
     */
    function run() {
        startTime = +new Date();
        options.log.subhead('Found ' + options.files.length + ' feature file(s). Running ' + options.maxProcesses + ' at a time.');

        _.each(options.files, addTask);

        options.executor.on('startedTask', taskStarted);
        options.executor.on('finishedTask', taskFinished);
        options.executor.on('finished', finish);
        options.executor.start();
    }

    /**
     * Send an individual feature file to be run
     *
     * @param {String} file
     */
    function addTask (file) {
        var configOpt = options.config ? '-c ' + options.config : '',
            filePath = options.baseDir ? options.baseDir + file : file,
            cmd = [options.bin, configOpt, options.flags, filePath].join(' ');

        tasks[cmd] = file;
        options.executor.addTask(cmd);
    }

    /**
     * Tell the user we've started a new task
     *
     * @param  {string} task
     */
    function taskStarted (task) {
        options.log.writeln('Started: ' + task);
    }

    /**
     * Process the result of the task
     *
     * @param {string} task
     * @param {Object} err
     * @param {string} stdout
     * @param {string} stderr
     */
    function taskFinished (task, err, stdout, stderr) {
        var file = tasks[task],
            output = stdout ? stdout.split('\n') : [];

        if (err && (err.code === 13 || err.killed)) {
            options.log.writeln('Timeout: ' + file + ' - adding to the back of the queue.');
            options.executor.addTask(task);
        }
        else if (err && err.code === 1) {
            options.log.error('Failed: ' + file + ' - ' + output[output.length - 4] + ' in ' + output[output.length - 2] + '\n' + stdout);
            taskPendingOrFailed(task);
        }
        else if (err) {
            options.log.error('Error: ' + file + ' - ' + err + stdout);
        }
        else {
            options.log.ok('Completed: ' + file + ' - ' + output[output.length - 4] + ' in ' + output[output.length - 2]);

            if (output[output.length - 4].indexOf('pending') > -1) {
                taskPendingOrFailed(task);
            }
        }

        if (options.debug) {
            if (err) options.log.writeln('\nerr: \n' + inspect(err));
            if (stderr) options.log.writeln('\nstderr: \n' + stderr);
            if (stdout) options.log.writeln('\nstdout: \n' + stdout);
        }
    }

    /**
     * Add the given task to the fail list and retry if options.numRetries is specified
     *
     * @param  {string} task
     */
    function taskPendingOrFailed (task) {
        failedTasks[task] = _.has(failedTasks, task) ? failedTasks[task] + 1 : 0;

        if (failedTasks[task] < options.numRetries) {
            options.log.writeln('Retrying: ' + tasks[task] + ' ' + (failedTasks[task] + 1) + ' of ' + options.numRetries + ' time(s)');
            options.executor.addTask(task);
        }
        else {
            options.fail.warn('Scenario failed');
        }
    }

    /**
     * Output the final run time and emit the finished event
     */
    function finish () {
        var totalTime = Math.floor((new Date() - startTime) / 1000);

        options.log.writeln('\nFinished in ' + Math.floor(totalTime / 60) + 'm' + totalTime % 60 + 's');
        options.done();
    }

    this.run = run;
}

module.exports = BehatTask;
<commit_msg>Removing extraneous output on test fail.<commit_after>'use strict';

var _ = require('underscore'),
    inspect = require('util').inspect;

/**
 * Run multiple behat feature files in parallel.
 *
 * Example usage:
 *
 * var behat = new BehatTask({
 *     files: ['feature1.feature', 'feature2.feature'],
 *     log: console.log,
 *     bin: 'behat',
 *     flags: '--tags @wip',
 *     executor: new ParallelExec(5),
 *     numRetries: 0
 * })
 *
 * @param {Object} options
 */
function BehatTask (options) {
    var tasks = {},
        failedTasks = {},
        startTime;

    /**
     * Create a behat command for each file and run it using the executor
     */
    function run() {
        startTime = +new Date();
        options.log.subhead('Found ' + options.files.length + ' feature file(s). Running ' + options.maxProcesses + ' at a time.');

        _.each(options.files, addTask);

        options.executor.on('startedTask', taskStarted);
        options.executor.on('finishedTask', taskFinished);
        options.executor.on('finished', finish);
        options.executor.start();
    }

    /**
     * Send an individual feature file to be run
     *
     * @param {String} file
     */
    function addTask (file) {
        var configOpt = options.config ? '-c ' + options.config : '',
            filePath = options.baseDir ? options.baseDir + file : file,
            cmd = [options.bin, configOpt, options.flags, filePath].join(' ');

        tasks[cmd] = file;
        options.executor.addTask(cmd);
    }

    /**
     * Tell the user we've started a new task
     *
     * @param  {string} task
     */
    function taskStarted (task) {
        options.log.writeln('Started: ' + task);
    }

    /**
     * Process the result of the task
     *
     * @param {string} task
     * @param {Object} err
     * @param {string} stdout
     * @param {string} stderr
     */
    function taskFinished (task, err, stdout, stderr) {
        var file = tasks[task],
            output = stdout ? stdout.split('\n') : [];

        if (err && (err.code === 13 || err.killed)) {
            options.log.writeln('Timeout: ' + file + ' - adding to the back of the queue.');
            options.executor.addTask(task);
        }
        else if (err && err.code === 1) {
            options.log.error('Failed: ' + file + ' - ' + output[output.length - 4] + ' in ' + output[output.length - 2]);
            taskPendingOrFailed(task);
        }
        else if (err) {
            options.log.error('Error: ' + file + ' - ' + err + stdout);
        }
        else {
            options.log.ok('Completed: ' + file + ' - ' + output[output.length - 4] + ' in ' + output[output.length - 2]);

            if (output[output.length - 4].indexOf('pending') > -1) {
                taskPendingOrFailed(task);
            }
        }

        if (options.debug) {
            if (err) options.log.writeln('\nerr: \n' + inspect(err));
            if (stderr) options.log.writeln('\nstderr: \n' + stderr);
            if (stdout) options.log.writeln('\nstdout: \n' + stdout);
        }
    }

    /**
     * Add the given task to the fail list and retry if options.numRetries is specified
     *
     * @param  {string} task
     */
    function taskPendingOrFailed (task) {
        failedTasks[task] = _.has(failedTasks, task) ? failedTasks[task] + 1 : 0;

        if (failedTasks[task] < options.numRetries) {
            options.log.writeln('Retrying: ' + tasks[task] + ' ' + (failedTasks[task] + 1) + ' of ' + options.numRetries + ' time(s)');
            options.executor.addTask(task);
        }
        else {
            options.fail.warn('Scenario failed');
        }
    }

    /**
     * Output the final run time and emit the finished event
     */
    function finish () {
        var totalTime = Math.floor((new Date() - startTime) / 1000);

        options.log.writeln('\nFinished in ' + Math.floor(totalTime / 60) + 'm' + totalTime % 60 + 's');
        options.done();
    }

    this.run = run;
}

module.exports = BehatTask;
<|endoftext|>