<commit_before>//========================================================================================
// Globals
//========================================================================================
var util = require('util');

var Parser = function() {
    this.varName = '';
    this.type = '';
    this.options = {};
    this.next = null;
    this.head = null;
    this.compiled = null;
};

var PRIMITIVE_TYPES = {
    'UInt8'    : 1,
    'UInt16LE' : 2,
    'UInt16BE' : 2,
    'UInt32LE' : 4,
    'UInt32BE' : 4,
    'Int8'     : 1,
    'Int16LE'  : 2,
    'Int16BE'  : 2,
    'Int32LE'  : 4,
    'Int32BE'  : 4,
    'FloatLE'  : 4,
    'FloatBE'  : 4,
    'DoubleLE' : 8,
    'DoubleBE' : 8
};

var SPECIAL_TYPES = {
    'String'   : null,
    'Array'    : null,
    'Skip'     : null,
    'Choice'   : null
};

// Converts Parser's method names to internal type names
var NAME_MAP = {};
Object.keys(PRIMITIVE_TYPES)
    .concat(Object.keys(SPECIAL_TYPES))
    .forEach(function(type) {
        NAME_MAP[type.toLowerCase()] = type;
    });

//========================================================================================
// Parser builder functions
//========================================================================================

Parser.start = function() {
    return new Parser();
};

Parser.prototype.setNextParser = function(type, varName, options) {
    var parser = new Parser();

    parser.type = NAME_MAP[type];
    parser.varName = varName;
    parser.options = options || parser.options;

    if (this.head) {
        this.head.next = parser;
    } else {
        this.next = parser;
    }
    this.head = parser;

    return this;
};

Object.keys(PRIMITIVE_TYPES)
    .forEach(function(type) {
        Parser.prototype[type.toLowerCase()] = function(varName, options) {
            return this.setNextParser(type.toLowerCase(), varName, options);
        };
    });

Parser.prototype.skip = function(length) {
    return this.setNextParser('skip', '', {length: length});
};

Parser.prototype.string = function(varName, options) {
    if (!options.length) {
        throw new Error('Length option of string is not defined.');
    }
    options.encoding = options.encoding || 'utf8';

    return this.setNextParser('string', varName, options);
};

Parser.prototype.array = function(varName, options) {
    if (!options.length) {
        throw new Error('Length option of array is not defined.');
    }
    if (!options.type) {
        throw new Error('Type option of array is not defined.');
    }
    if (typeof options.type === 'string' && Object.keys(PRIMITIVE_TYPES).indexOf(NAME_MAP[options.type]) < 0) {
        throw new Error('Specified primitive type "' + options.type + '" is not supported.');
    }

    return this.setNextParser('array', varName, options);
};

Parser.prototype.choice = function(varName, options) {
    if (!options.tag) {
        throw new Error('Tag option of array is not defined.');
    }
    if (!options.choices) {
        throw new Error('Choices option of array is not defined.');
    }
    Object.keys(options.choices).forEach(function(key) {
        if (isNaN(parseInt(key, 10))) {
            throw new Error('Key of choices must be a number.');
        }
        if (!options.choices[key]) {
            throw new Error('Choice ' + key + ' of ' + varName + ' is not defined.');
        }

        if (typeof options.choices[key] === 'string' && Object.keys(PRIMITIVE_TYPES).indexOf(NAME_MAP[options.choices[key]]) < 0) {
            throw new Error('Specified primitive type "' +  options.choices[key] + '" is not supported.');
        }
    });

    return this.setNextParser('choice', varName, options);
};

Parser.prototype.endianess = function(endianess) {
    if (endianess !== 'little' && endianess !== 'big') {
        throw new Error('Invalid endianess: ' + endianess);
    }
    var re = endianess === 'little' ? /(.+)LE/ : /(.+)BE/;

    Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
        var matched = type.match(re);
        if (matched) {
            var funcName = matched[1].toLowerCase();
            Parser.prototype[funcName] = Parser.prototype[type.toLowerCase()];
        }
    });

    return this;
};

//========================================================================================
// Parsers
//========================================================================================

// Follow the parser chain till the root and start parsing from there
Parser.prototype.parse = function(buffer) {
    if (!Buffer.isBuffer(buffer)) {
        throw new Error('argument "buffer" is not a Buffer object');
    }

    if (this.compiled) {
        return this.compiled(buffer);
    }

    return this.next._parse({
        buffer: buffer,
        iterator: 0,
        vars: {}
    }).vars;
};

// Start parsing from this parser
Parser.prototype._parse = function(ctx) {
    return this['_parse' + this.type](ctx);
};

Parser.prototype.parseNext = function(ctx) {
    return this.next ? this.next._parse(ctx) : ctx;
};

Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
    Parser.prototype['_parse' + type] = function(ctx) {
        ctx.vars[this.varName] = ctx.buffer['read' + type](ctx.iterator);
        ctx.iterator += PRIMITIVE_TYPES[type];

        return this.parseNext(ctx);
    };
});

Parser.prototype._parseSkip = function(ctx) {
    ctx.iterator += this.options.length;

    return this.parseNext(ctx);
};

Parser.prototype.evalField = function(val, ctx) {
    if (typeof val === 'number') {
        return val;
    } else if (typeof val === 'string') {
        if (val in ctx.vars) {
            return ctx.vars[val];
        } else {
            throw new Error('Field ' + val + ' is not yet defined.');
        }
    } else if (typeof val === 'function') {
        return val.call(ctx.vars);
    }
};

Parser.prototype._parseString = function(ctx) {
    var encoding = this.options.encoding;
    var length = this.evalField(this.options.length, ctx);

    ctx.vars[this.varName] = ctx.buffer.toString(encoding, ctx.iterator, ctx.iterator + length);
    ctx.iterator += length;

    return this.parseNext(ctx);
};

Parser.prototype._parseArray = function(ctx) {
    var length = this.evalField(this.options.length, ctx);
    var type = this.options.type;
    var i, data, tmp;

    ctx.vars[this.varName] = [];
    if (typeof type === 'string') {
        for (i = 0; i < length ; i++) {
            tmp = this['_parse' + NAME_MAP[type]].call(
                new Parser(), {
                buffer: ctx.buffer,
                iterator: ctx.iterator,
                vars: {}
            });
            ctx.iterator = tmp.iterator;
            ctx.vars[this.varName].push(tmp.vars['']);
        }
    } else if (type instanceof Parser) {
        for (i = 0; i < length ; i++) {
            tmp = type.next._parse({
                buffer: ctx.buffer,
                iterator: ctx.iterator,
                vars: {}
            });
            ctx.iterator = tmp.iterator;
            ctx.vars[this.varName].push(tmp.vars);
        }
    }

    return this.parseNext(ctx);
};

Parser.prototype._parseChoice = function(ctx) {
    var tag = this.evalField(this.options.tag, ctx);
    var type = this.options.choices[tag];
    var tmp;

    if (typeof type === 'string') {
        tmp = this['_parse' + NAME_MAP[type]].call(
            new Parser(), {
            buffer: ctx.buffer,
            iterator: ctx.iterator,
            vars: {}
        });
        ctx.iterator = tmp.iterator;
        ctx.vars[this.varName] = tmp.vars[''];
    } else if (type instanceof Parser) {
        tmp = type.next._parse({
            buffer: ctx.buffer,
            iterator: ctx.iterator,
            vars: {}
        });
        ctx.iterator = tmp.iterator;
        ctx.vars[this.varName] = tmp.vars;
    }

    return this.parseNext(ctx);
};

//========================================================================================
// JIT code generators
//========================================================================================

Parser.prototype.compile = function() {
    var code =  'var vars = {};\n' +
                'var iterator = 0;\n' +
                this.next.generate({code: '', scopes: []}).code +
                'return vars;';

    this.compiled = new Function('buffer', code);
};

// Call code generator for this parser
Parser.prototype.generate = function(ctx) {
    return this['generate' + this.type](ctx);
};

// Recursively call code generators and append results
Parser.prototype.generateNextParser = function(ctx) {
    if (this.next) {
        ctx = this.next.generate(ctx);
    }

    return ctx;
};

Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
    Parser.prototype['generate' + type] = function(ctx) {
        ctx.code += util.format('%s = buffer.read%s(iterator);\n', this.generateVariable(ctx, this.varName), type);
        ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[type]);

        return this.generateNextParser(ctx);
    };
});

Parser.prototype.generateVariable = function(ctx, name) {
    var arr = ['vars'];
    Array.prototype.push.apply(arr, ctx.scopes);
    if (name) {
        arr.push(name);
    }

    return arr.join('.');
};

Parser.prototype.generateOption = function(ctx, val) {
    if (typeof val === 'number') {
        return val.toString();
    } else if (typeof val === 'string') {
        return this.generateVariable(ctx, val);
    } else if (typeof val === 'function') {
        return util.format('(%s).call(%s)', val, this.generateVariable(ctx));
    }
};

Parser.prototype.generateSkip = function(ctx) {
    var length = this.generateOption(ctx, this.options.length);
    ctx.code += util.format('iterator += %s;\n', length);

    return this.generateNextParser(ctx);
};

Parser.prototype.generateString = function(ctx) {
    ctx.code += util.format('%s = buffer.toString(\'%s\', iterator, iterator + %s);\n',
                                this.generateVariable(ctx, this.varName),
                                this.options.encoding,
                                this.generateOption(ctx, this.options.length)
                            );
    ctx.code += util.format('iterator += %s;\n', this.generateOption(ctx, this.options.length));

    return this.generateNextParser(ctx);
};

Parser.prototype.generateArray = function(ctx) {
    var length = this.generateOption(ctx, this.options.length);
    var type = this.options.type;
    var tmp;
    var counter = 'i' + ctx.scopes.length.toString();
    var lhs = this.generateVariable(ctx, this.varName);

    ctx.code += util.format('%s = [];\n', lhs);
    ctx.code += util.format('for (var %s = 0; %s < %s; %s++) {\n', counter, counter, length, counter);
    if (typeof type === 'string') {
        ctx.code += util.format('%s.push(buffer.read%s(iterator));\n', lhs, NAME_MAP[type]);
        ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[NAME_MAP[type]]);
    } else if (type instanceof Parser) {
        ctx.code += util.format('%s.push({});\n', lhs);

        ctx.scopes.push(this.varName + '[' + counter + ']');
        type.next.generate(ctx);
        ctx.scopes.pop();
    }
    ctx.code += '}\n';

    return this.generateNextParser(ctx);
};

Parser.prototype.generateChoice = function(ctx) {
    var tag = this.generateOption(ctx, this.options.tag);
    var type = this.options.choices[tag];
    var lhs = this.generateVariable(ctx, this.varName);
    var tmp;

    ctx.code += util.format('%s = {};\n', lhs);
    ctx.code += util.format('switch(%s) {\n', tag);
    Object.keys(this.options.choices).forEach(function(tag) {
        var type = this.options.choices[tag];

        ctx.code += util.format('case %d:\n', tag);
        if (typeof type === 'string') {
            ctx.code += util.format('%s = buffer.read%s(iterator);\n', lhs, NAME_MAP[type]);
            ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[NAME_MAP[type]]);
        } else if (type instanceof Parser) {
            ctx.scopes.push(this.varName);
            type.next.generate(ctx);
            ctx.scopes.pop();
        }
        ctx.code += 'break;\n';
    }, this);
    ctx.code += '}\n';

    return this.generateNextParser(ctx);
};

//========================================================================================
// Utilities
//========================================================================================

Parser.prototype.sizeOf = function() {
    var size = NaN;

    if (Object.keys(PRIMITIVE_TYPES).indexOf(this.type) >= 0) {
        size = PRIMITIVE_TYPES[this.type];

    // if this is a fixed length string
    } else if (this.type === 'String' && typeof this.options.length === 'number') {
        size = this.options.length;

    // if this is a fixed length array
    } else if (this.type === 'Array' && typeof this.options.length === 'number') {
        var elementSize = NaN;
        if (typeof this.options.type === 'string'){
            elementSize = PRIMITIVE_TYPES[NAME_MAP[this.options.type]];
        } else if (this.options.type instanceof Parser) {
            elementSize = this.options.type.sizeOf();
        }
        size = this.options.length * elementSize;

    // if this a skip
    } else if (this.type === 'Skip') {
        size = this.options.length;

    } else if (!this.type) {
        size = 0;
    }

    if (this.next) {
        size += this.next.sizeOf();
    }

    return size;
};

//========================================================================================
// Exports
//========================================================================================

exports.Parser = Parser;
<commit_msg>Introduced Context class<commit_after>//========================================================================================
// Globals
//========================================================================================
var util = require('util');

var Parser = function() {
    this.varName = '';
    this.type = '';
    this.options = {};
    this.next = null;
    this.head = null;
    this.compiled = null;
};

var PRIMITIVE_TYPES = {
    'UInt8'    : 1,
    'UInt16LE' : 2,
    'UInt16BE' : 2,
    'UInt32LE' : 4,
    'UInt32BE' : 4,
    'Int8'     : 1,
    'Int16LE'  : 2,
    'Int16BE'  : 2,
    'Int32LE'  : 4,
    'Int32BE'  : 4,
    'FloatLE'  : 4,
    'FloatBE'  : 4,
    'DoubleLE' : 8,
    'DoubleBE' : 8
};

var SPECIAL_TYPES = {
    'String'   : null,
    'Array'    : null,
    'Skip'     : null,
    'Choice'   : null
};

// Converts Parser's method names to internal type names
var NAME_MAP = {};
Object.keys(PRIMITIVE_TYPES)
    .concat(Object.keys(SPECIAL_TYPES))
    .forEach(function(type) {
        NAME_MAP[type.toLowerCase()] = type;
    });

//========================================================================================
// Parser builder functions
//========================================================================================

Parser.start = function() {
    return new Parser();
};

Parser.prototype.setNextParser = function(type, varName, options) {
    var parser = new Parser();

    parser.type = NAME_MAP[type];
    parser.varName = varName;
    parser.options = options || parser.options;

    if (this.head) {
        this.head.next = parser;
    } else {
        this.next = parser;
    }
    this.head = parser;

    return this;
};

Object.keys(PRIMITIVE_TYPES)
    .forEach(function(type) {
        Parser.prototype[type.toLowerCase()] = function(varName, options) {
            return this.setNextParser(type.toLowerCase(), varName, options);
        };
    });

Parser.prototype.skip = function(length) {
    return this.setNextParser('skip', '', {length: length});
};

Parser.prototype.string = function(varName, options) {
    if (!options.length) {
        throw new Error('Length option of string is not defined.');
    }
    options.encoding = options.encoding || 'utf8';

    return this.setNextParser('string', varName, options);
};

Parser.prototype.array = function(varName, options) {
    if (!options.length) {
        throw new Error('Length option of array is not defined.');
    }
    if (!options.type) {
        throw new Error('Type option of array is not defined.');
    }
    if (typeof options.type === 'string' && Object.keys(PRIMITIVE_TYPES).indexOf(NAME_MAP[options.type]) < 0) {
        throw new Error('Specified primitive type "' + options.type + '" is not supported.');
    }

    return this.setNextParser('array', varName, options);
};

Parser.prototype.choice = function(varName, options) {
    if (!options.tag) {
        throw new Error('Tag option of array is not defined.');
    }
    if (!options.choices) {
        throw new Error('Choices option of array is not defined.');
    }
    Object.keys(options.choices).forEach(function(key) {
        if (isNaN(parseInt(key, 10))) {
            throw new Error('Key of choices must be a number.');
        }
        if (!options.choices[key]) {
            throw new Error('Choice ' + key + ' of ' + varName + ' is not defined.');
        }

        if (typeof options.choices[key] === 'string' && Object.keys(PRIMITIVE_TYPES).indexOf(NAME_MAP[options.choices[key]]) < 0) {
            throw new Error('Specified primitive type "' +  options.choices[key] + '" is not supported.');
        }
    });

    return this.setNextParser('choice', varName, options);
};

Parser.prototype.endianess = function(endianess) {
    if (endianess !== 'little' && endianess !== 'big') {
        throw new Error('Invalid endianess: ' + endianess);
    }
    var re = endianess === 'little' ? /(.+)LE/ : /(.+)BE/;

    Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
        var matched = type.match(re);
        if (matched) {
            var funcName = matched[1].toLowerCase();
            Parser.prototype[funcName] = Parser.prototype[type.toLowerCase()];
        }
    });

    return this;
};

//========================================================================================
// Parsers
//========================================================================================

// Follow the parser chain till the root and start parsing from there
Parser.prototype.parse = function(buffer) {
    if (!Buffer.isBuffer(buffer)) {
        throw new Error('argument "buffer" is not a Buffer object');
    }

    if (this.compiled) {
        return this.compiled(buffer);
    }

    return this.next._parse({
        buffer: buffer,
        iterator: 0,
        vars: {}
    }).vars;
};

// Start parsing from this parser
Parser.prototype._parse = function(ctx) {
    return this['_parse' + this.type](ctx);
};

Parser.prototype.parseNext = function(ctx) {
    return this.next ? this.next._parse(ctx) : ctx;
};

Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
    Parser.prototype['_parse' + type] = function(ctx) {
        ctx.vars[this.varName] = ctx.buffer['read' + type](ctx.iterator);
        ctx.iterator += PRIMITIVE_TYPES[type];

        return this.parseNext(ctx);
    };
});

Parser.prototype._parseSkip = function(ctx) {
    ctx.iterator += this.options.length;

    return this.parseNext(ctx);
};

Parser.prototype.evalField = function(val, ctx) {
    if (typeof val === 'number') {
        return val;
    } else if (typeof val === 'string') {
        if (val in ctx.vars) {
            return ctx.vars[val];
        } else {
            throw new Error('Field ' + val + ' is not yet defined.');
        }
    } else if (typeof val === 'function') {
        return val.call(ctx.vars);
    }
};

Parser.prototype._parseString = function(ctx) {
    var encoding = this.options.encoding;
    var length = this.evalField(this.options.length, ctx);

    ctx.vars[this.varName] = ctx.buffer.toString(encoding, ctx.iterator, ctx.iterator + length);
    ctx.iterator += length;

    return this.parseNext(ctx);
};

Parser.prototype._parseArray = function(ctx) {
    var length = this.evalField(this.options.length, ctx);
    var type = this.options.type;
    var i, data, tmp;

    ctx.vars[this.varName] = [];
    if (typeof type === 'string') {
        for (i = 0; i < length ; i++) {
            tmp = this['_parse' + NAME_MAP[type]].call(
                new Parser(), {
                buffer: ctx.buffer,
                iterator: ctx.iterator,
                vars: {}
            });
            ctx.iterator = tmp.iterator;
            ctx.vars[this.varName].push(tmp.vars['']);
        }
    } else if (type instanceof Parser) {
        for (i = 0; i < length ; i++) {
            tmp = type.next._parse({
                buffer: ctx.buffer,
                iterator: ctx.iterator,
                vars: {}
            });
            ctx.iterator = tmp.iterator;
            ctx.vars[this.varName].push(tmp.vars);
        }
    }

    return this.parseNext(ctx);
};

Parser.prototype._parseChoice = function(ctx) {
    var tag = this.evalField(this.options.tag, ctx);
    var type = this.options.choices[tag];
    var tmp;

    if (typeof type === 'string') {
        tmp = this['_parse' + NAME_MAP[type]].call(
            new Parser(), {
            buffer: ctx.buffer,
            iterator: ctx.iterator,
            vars: {}
        });
        ctx.iterator = tmp.iterator;
        ctx.vars[this.varName] = tmp.vars[''];
    } else if (type instanceof Parser) {
        tmp = type.next._parse({
            buffer: ctx.buffer,
            iterator: ctx.iterator,
            vars: {}
        });
        ctx.iterator = tmp.iterator;
        ctx.vars[this.varName] = tmp.vars;
    }

    return this.parseNext(ctx);
};

//========================================================================================
// JIT code generators
//========================================================================================

var Context = function() {
    this.code = '';
    this.scopes = [];
};

Context.prototype.generateVariable = function(name) {
    var arr = ['vars'];
    Array.prototype.push.apply(arr, this.scopes);
    if (name) {
        arr.push(name);
    }

    return arr.join('.');
};

Context.prototype.generateOption = function(val) {
    if (typeof val === 'number') {
        return val.toString();
    } else if (typeof val === 'string') {
        return this.generateVariable(val);
    } else if (typeof val === 'function') {
        return util.format('(%s).call(%s)', val, this.generateVariable());
    }
};

Context.prototype.pushCode = function(code) {
    var re = /{\d+}/g;
    var matches = code.match(re);
    var params = Array.prototype.slice.call(arguments, 1);

    if (matches) {
        matches.forEach(function(match) {
            var index = parseInt(match.substr(1, match.length - 2), 10);
            code = code.replace(match, params[index]);
        });
    }

    this.code += code + '\n';
};

Parser.prototype.compile = function() {
    var ctx = new Context();

    ctx.pushCode('var vars = {};');
    ctx.pushCode('var iterator = 0;');
    this.next.generate(ctx);
    ctx.pushCode('return vars;');

    this.compiled = new Function('buffer', ctx.code);
};

// Call code generator for this parser
Parser.prototype.generate = function(ctx) {
    return this['generate' + this.type](ctx);
};

// Recursively call code generators and append results
Parser.prototype.generateNextParser = function(ctx) {
    if (this.next) {
        ctx = this.next.generate(ctx);
    }

    return ctx;
};

Object.keys(PRIMITIVE_TYPES).forEach(function(type) {
    Parser.prototype['generate' + type] = function(ctx) {
        ctx.pushCode('{0} = buffer.read{1}(iterator);', ctx.generateVariable(this.varName), type);
        ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[type]);

        return this.generateNextParser(ctx);
    };
});

Parser.prototype.generateSkip = function(ctx) {
    var length = ctx.generateOption(this.options.length);
    ctx.code += util.format('iterator += %s;\n', length);

    return this.generateNextParser(ctx);
};

Parser.prototype.generateString = function(ctx) {
    ctx.code += util.format('%s = buffer.toString(\'%s\', iterator, iterator + %s);\n',
                                ctx.generateVariable(this.varName),
                                this.options.encoding,
                                ctx.generateOption(this.options.length)
                            );
    ctx.code += util.format('iterator += %s;\n', ctx.generateOption(this.options.length));

    return this.generateNextParser(ctx);
};

Parser.prototype.generateArray = function(ctx) {
    var length = ctx.generateOption(this.options.length);
    var type = this.options.type;
    var tmp;
    var counter = 'i' + ctx.scopes.length.toString();
    var lhs = ctx.generateVariable(this.varName);

    ctx.code += util.format('%s = [];\n', lhs);
    ctx.code += util.format('for (var %s = 0; %s < %s; %s++) {\n', counter, counter, length, counter);
    if (typeof type === 'string') {
        ctx.code += util.format('%s.push(buffer.read%s(iterator));\n', lhs, NAME_MAP[type]);
        ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[NAME_MAP[type]]);
    } else if (type instanceof Parser) {
        ctx.code += util.format('%s.push({});\n', lhs);

        ctx.scopes.push(this.varName + '[' + counter + ']');
        type.next.generate(ctx);
        ctx.scopes.pop();
    }
    ctx.code += '}\n';

    return this.generateNextParser(ctx);
};

Parser.prototype.generateChoice = function(ctx) {
    var tag = ctx.generateOption(this.options.tag);
    var type = this.options.choices[tag];
    var lhs = ctx.generateVariable(this.varName);
    var tmp;

    ctx.code += util.format('%s = {};\n', lhs);
    ctx.code += util.format('switch(%s) {\n', tag);
    Object.keys(this.options.choices).forEach(function(tag) {
        var type = this.options.choices[tag];

        ctx.code += util.format('case %d:\n', tag);
        if (typeof type === 'string') {
            ctx.code += util.format('%s = buffer.read%s(iterator);\n', lhs, NAME_MAP[type]);
            ctx.code += util.format('iterator += %d;\n', PRIMITIVE_TYPES[NAME_MAP[type]]);
        } else if (type instanceof Parser) {
            ctx.scopes.push(this.varName);
            type.next.generate(ctx);
            ctx.scopes.pop();
        }
        ctx.code += 'break;\n';
    }, this);
    ctx.code += '}\n';

    return this.generateNextParser(ctx);
};

//========================================================================================
// Utilities
//========================================================================================

Parser.prototype.sizeOf = function() {
    var size = NaN;

    if (Object.keys(PRIMITIVE_TYPES).indexOf(this.type) >= 0) {
        size = PRIMITIVE_TYPES[this.type];

    // if this is a fixed length string
    } else if (this.type === 'String' && typeof this.options.length === 'number') {
        size = this.options.length;

    // if this is a fixed length array
    } else if (this.type === 'Array' && typeof this.options.length === 'number') {
        var elementSize = NaN;
        if (typeof this.options.type === 'string'){
            elementSize = PRIMITIVE_TYPES[NAME_MAP[this.options.type]];
        } else if (this.options.type instanceof Parser) {
            elementSize = this.options.type.sizeOf();
        }
        size = this.options.length * elementSize;

    // if this a skip
    } else if (this.type === 'Skip') {
        size = this.options.length;

    } else if (!this.type) {
        size = 0;
    }

    if (this.next) {
        size += this.next.sizeOf();
    }

    return size;
};

//========================================================================================
// Exports
//========================================================================================

exports.Parser = Parser;
<|endoftext|>


<commit_before>var _ = require('./ext-underscore');
var compose = require('functools').compose;

// The following two functions are jacked from Backbone.js (version 0.9.1)
var extend = function (protoProps, classProps) {
  var inherits = function(parent, protoProps, staticProps) {
    var child, ctor = function(){};
    if (protoProps && protoProps.hasOwnProperty('constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){parent.apply(this, arguments);};
    }
    _.extend(child, parent);
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();
    if (protoProps) _.extend(child.prototype, protoProps);
    if (staticProps) _.extend(child, staticProps);
    child.prototype.constructor = child;
    child.__super__ = parent.prototype;
    return child;
  };

  var child = inherits(this, protoProps, classProps);
  child.extend = this.extend;
  return child;
};

// Base Model
// ==========
var Base = function (attributes) {
  this.attributes = attributes || {};
};

Base.SCHEMA_TABLE = '_schema_version';

Base.extend = extend;

// include validators
Base.Validators = require('./validators.js');

// include schema defs
Base.Schema = require('./schema.js');

Base.prototype = {
  // set some reasonable defaults
  driver: 'mysql',
  engine: 'InnoDB',
  schema: null,
  validators: null,

  validate: function (attr) {
    attr = attr || this.attributes;

    var schema = this.constructor.getSchema();
    var errors = {};

    function arrayify (o) {
      if (_.isArray(o)) return o; else return [o];
    }

    function runTest (value, field, test) {
      if (errors[field]) return;
      var error = test(value, attr);
      if (error) {
        error.value = value;
        errors[field] = error;
      }
    }

    function runValidators (o, field) {
      var value = attr[field];
      _.each(arrayify(o.validators || o), runTest.bind(null, value, field));
    }

    // run all the validators defined by the schema
    _.each(schema, runValidators);

    // run any validators defined by model outside of the schema
    _.each(this.validators || {}, runValidators);

    if (_.isEmpty(errors)) return;
    return errors;
  },

  mutate: function (attr) {
    return this._radioactivity('storage', attr || this.attributes);
  },

  demutate: function (mattr) {
    return this._radioactivity('retrieval', mattr || this.attributes);
  },

  _radioactivity: function (direction, attr) {
    var schema = this.constructor.getSchema();

    // make a zipped array of field, mutator
    function extraction (o) {
      return _.map(o, function (spec, field) {
        var fn = _.extract(spec, ['mutators', direction]) || _.identity;
        return [field, fn];
      });
    }

    // drop all fields that aren't found in the attributes array
    function cleanup (a) {
      return _.filter(a, function (a) {
        var field = a[0];
        return attr[field];
      });
    }

    // turn the zipped array back into an object
    var stitch = _.zippo;

    // call of the functions in the mutators fn object with
    // the values of the attributes object
    function getMutations(spec, attr) {
      var fns = compose(extraction, cleanup, stitch)(spec);
      return _.callmap(fns, attr);
    }

    return getMutations(schema, attr);
  },

  save: function (callback) {
    callback = callback || function(){};

    var fields = [];
    var attr = this.mutate(this.attributes);
    var values = [];
    var data = {};
    var errors = null;
    var attrGetter = _.get(attr);
    var attrUndefined = _.compose(_.isUndefined, attrGetter);

    // Fail early: check the result of validation before saving
    if ( (errors = this.validate(this.attributes)) ) {
      return callback({ validation: errors });
    }

    // remove any fields that don't have an attribute set
    fields = _.reject(_.keys(this.schema), attrUndefined);

    // get the values for the remaining fields
    values = _.map(fields, attrGetter);

    // zip them back together into an object
    data = _.zipmap(fields, values);

    this.client.upsert(this.table, data, function (err, result) {
      if (err) return callback(err);

      if (!this.get('id')) {
        this.constructor.findById(result.insertId, function (err, saved) {
          if (err) return callback(err);
          this.attributes = saved.attributes;
          callback(null, this);
        }.bind(this));
      }

      else {
        callback(null, this);
      }
    }.bind(this));
  },

  destroy: function (callback) {
    callback = callback||function(){};

    var self = this;
    var attributes = this.attributes;
    var table = this.table;
    var querySQL = 'DELETE FROM `'+table+'` WHERE `id` = ? LIMIT 1';

    if (!this.get('id')) return callback(null, self);

    this.client.query(querySQL, [attributes.id], function (err, resp) {
      if (err) { return callback(err); }
      delete attributes.id;
      return callback(null, self);
    });
  },

  get: function (k) {
    var getters = _.extend({}, this.getters);
    var g, fn = (g = getters[k]) ? g.bind(this) : _.identity;
    return fn(this.attributes[k]);
  },

  set: function (k, v) {
    var setters = _.extend({}, this.setters);
    if (setters[k]) setters[k].bind(this)(v);
    else this.attributes[k] = v;
    return this;
  }
};

_.extend(Base, {
  // A number of methods create or populate the `_fieldspec` object.
  // The object is keyed by the fields in the schema. The values of are
  // objects that have, at the very minimum, an `sql` property. They can
  // also contain:
  //   `validators` -- an array of validator functions,
  //   `keysql` -- sql for generating key contraints
  //   `dependsOn` -- a model the field depends on.
  //   `mutators` -- object with `storage` and `retrieval` props
  fieldspec: null,

  find: function (criteria, callback) {
    var qb = this.client
      .select('*')
      .from(_.backtick(this.table()))
      .where('1=1');

    _.each(criteria, function (value, key) {
      qb.and(_.backtick(key) + ' = ?', value);
    });

    // demutate before turning back into an object
    var make = (function (attr) {
      return new this(this.prototype.demutate(attr));
    }).bind(this);

    qb.go(function (err, result) {
      if (err) return callback(err);
      callback(null, _.map(result, make));
    });
  },

  findOne: function (criteria, callback) {
    this.find(criteria, function (err, res) {
      if (err) return callback(err);
      callback(null, res.pop());
    });
  },

  findById: function (id, callback) {
    this.findOne({ id: id }, callback);
  },

  findAll: function (callback) {
    this.find({}, callback);
  },

  getSchema: function () {
    var schema = this.prototype.schema;
    var newSchema = {};

    if (!schema)
      throw new Error('missing schema');

    if (String(schema) !== '[object Object]')
      throw new TypeError('schema must be an object');

    _.each(schema, function (value, key) {
      // A string means that the user wants this inserted as raw sql
      if (_.isString(value))
        newSchema[key] = _.extend({}, {sql: value});

      // Pass objects straight through
      if (_.isObject(value))
        newSchema[key] = _.extend({}, value);

      // Functions should generate an object with, at the very least, an `sql`
      // property. They can optionally generate more (like validators).
      // The function can also be a higher order function that returns a
      // function that does the above.
      if (_.isFunction(value)) {
        var spec = _.isFunction(value()) ? value()(key) : value(key);
        newSchema[key] = _.extend({}, newSchema[key], spec);
      }
      _.defaults(newSchema[key], {mutators: {}, validators: []});
    });
    this.getSchema = function () { return newSchema; };
    return newSchema;
  },

  createTableSql: function (schema) {
    var fieldsql = [];
    var keysql = [];
    var table = this.table();
    var engine = this.prototype.engine;
    if (!table) {
      throw new Error('table must be specified before trying to' +
                      ' make the `create table` statement');
    }
    _.each(schema, function (value, key) {
      fieldsql.push([
        _.backtick(key),
        value.sql
      ].join(' '));

      if (value.keysql)
        keysql.push(value.keysql);
    });

    var sql = _.strjoin([
      _.upcase('create table if not exists'),
      _.backtick(table),
      _.paren(_.union(fieldsql, keysql).join(', ')),
      _.upcase('engine ='),
      engine
    ]);

    return sql;
  },

  table: function () {
    return this.prototype.table;
  },

  version: function () {
    return this.prototype.version;
  },

  makeTable: function (callback) {
    var schema = this.getSchema();
    _.each(schema, function (spec) {
      if (spec.dependsOn) { spec.dependsOn.makeTable(); }
    });
    this.client.query(this.createTableSql(schema), callback);
  },

  updateSchemaVersionTable: function (callback) {
    callback = callback || function () {  };
    // create the table if it doesn't exist
    var client = this.client;
    var modelTable = this.table();
    var version = this.version() || '0000';
    var schemaTable = Base.SCHEMA_TABLE;
    var sql =  _.strjoin([
      'create table if not exists',
      _.backtick(schemaTable),
      _.paren([
        '`table` varchar(255) not null primary key',
        '`version` varchar(255) not null'
      ].join(','))
    ]);
    client.query(sql, function (err, result) {
      if (err) throw err;
      var data = { table: modelTable, version: version };
      return client.insert(schemaTable, data, function (err, result) {
        if (!err) return callback(err, result);
        if (!err.message.match(/duplicate/i)) return callback(err, null);
        // insert failed due to duplicate key, use update instead
        client.update(schemaTable, data, { table: modelTable }, callback);
      });
    });
  }
});

module.exports = Base;
<commit_msg>Handle errors better in updateSchemaVersionTable.<commit_after>var _ = require('./ext-underscore');
var compose = require('functools').compose;

// The following two functions are jacked from Backbone.js (version 0.9.1)
var extend = function (protoProps, classProps) {
  var inherits = function(parent, protoProps, staticProps) {
    var child, ctor = function(){};
    if (protoProps && protoProps.hasOwnProperty('constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){parent.apply(this, arguments);};
    }
    _.extend(child, parent);
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();
    if (protoProps) _.extend(child.prototype, protoProps);
    if (staticProps) _.extend(child, staticProps);
    child.prototype.constructor = child;
    child.__super__ = parent.prototype;
    return child;
  };

  var child = inherits(this, protoProps, classProps);
  child.extend = this.extend;
  return child;
};

// Base Model
// ==========
var Base = function (attributes) {
  this.attributes = attributes || {};
};

Base.SCHEMA_TABLE = '_schema_version';

Base.extend = extend;

// include validators
Base.Validators = require('./validators.js');

// include schema defs
Base.Schema = require('./schema.js');

Base.prototype = {
  // set some reasonable defaults
  driver: 'mysql',
  engine: 'InnoDB',
  schema: null,
  validators: null,

  validate: function (attr) {
    attr = attr || this.attributes;

    var schema = this.constructor.getSchema();
    var errors = {};

    function arrayify (o) {
      if (_.isArray(o)) return o; else return [o];
    }

    function runTest (value, field, test) {
      if (errors[field]) return;
      var error = test(value, attr);
      if (error) {
        error.value = value;
        errors[field] = error;
      }
    }

    function runValidators (o, field) {
      var value = attr[field];
      _.each(arrayify(o.validators || o), runTest.bind(null, value, field));
    }

    // run all the validators defined by the schema
    _.each(schema, runValidators);

    // run any validators defined by model outside of the schema
    _.each(this.validators || {}, runValidators);

    if (_.isEmpty(errors)) return;
    return errors;
  },

  mutate: function (attr) {
    return this._radioactivity('storage', attr || this.attributes);
  },

  demutate: function (mattr) {
    return this._radioactivity('retrieval', mattr || this.attributes);
  },

  _radioactivity: function (direction, attr) {
    var schema = this.constructor.getSchema();

    // make a zipped array of field, mutator
    function extraction (o) {
      return _.map(o, function (spec, field) {
        var fn = _.extract(spec, ['mutators', direction]) || _.identity;
        return [field, fn];
      });
    }

    // drop all fields that aren't found in the attributes array
    function cleanup (a) {
      return _.filter(a, function (a) {
        var field = a[0];
        return attr[field];
      });
    }

    // turn the zipped array back into an object
    var stitch = _.zippo;

    // call of the functions in the mutators fn object with
    // the values of the attributes object
    function getMutations(spec, attr) {
      var fns = compose(extraction, cleanup, stitch)(spec);
      return _.callmap(fns, attr);
    }

    return getMutations(schema, attr);
  },

  save: function (callback) {
    callback = callback || function(){};

    var fields = [];
    var attr = this.mutate(this.attributes);
    var values = [];
    var data = {};
    var errors = null;
    var attrGetter = _.get(attr);
    var attrUndefined = _.compose(_.isUndefined, attrGetter);

    // Fail early: check the result of validation before saving
    if ( (errors = this.validate(this.attributes)) ) {
      return callback({ validation: errors });
    }

    // remove any fields that don't have an attribute set
    fields = _.reject(_.keys(this.schema), attrUndefined);

    // get the values for the remaining fields
    values = _.map(fields, attrGetter);

    // zip them back together into an object
    data = _.zipmap(fields, values);

    this.client.upsert(this.table, data, function (err, result) {
      if (err) return callback(err);

      if (!this.get('id')) {
        this.constructor.findById(result.insertId, function (err, saved) {
          if (err) return callback(err);
          this.attributes = saved.attributes;
          callback(null, this);
        }.bind(this));
      }

      else {
        callback(null, this);
      }
    }.bind(this));
  },

  destroy: function (callback) {
    callback = callback||function(){};

    var self = this;
    var attributes = this.attributes;
    var table = this.table;
    var querySQL = 'DELETE FROM `'+table+'` WHERE `id` = ? LIMIT 1';

    if (!this.get('id')) return callback(null, self);

    this.client.query(querySQL, [attributes.id], function (err, resp) {
      if (err) { return callback(err); }
      delete attributes.id;
      return callback(null, self);
    });
  },

  get: function (k) {
    var getters = _.extend({}, this.getters);
    var g, fn = (g = getters[k]) ? g.bind(this) : _.identity;
    return fn(this.attributes[k]);
  },

  set: function (k, v) {
    var setters = _.extend({}, this.setters);
    if (setters[k]) setters[k].bind(this)(v);
    else this.attributes[k] = v;
    return this;
  }
};

_.extend(Base, {
  // A number of methods create or populate the `_fieldspec` object.
  // The object is keyed by the fields in the schema. The values of are
  // objects that have, at the very minimum, an `sql` property. They can
  // also contain:
  //   `validators` -- an array of validator functions,
  //   `keysql` -- sql for generating key contraints
  //   `dependsOn` -- a model the field depends on.
  //   `mutators` -- object with `storage` and `retrieval` props
  fieldspec: null,

  find: function (criteria, callback) {
    var qb = this.client
      .select('*')
      .from(_.backtick(this.table()))
      .where('1=1');

    _.each(criteria, function (value, key) {
      qb.and(_.backtick(key) + ' = ?', value);
    });

    // demutate before turning back into an object
    var make = (function (attr) {
      return new this(this.prototype.demutate(attr));
    }).bind(this);

    qb.go(function (err, result) {
      if (err) return callback(err);
      callback(null, _.map(result, make));
    });
  },

  findOne: function (criteria, callback) {
    this.find(criteria, function (err, res) {
      if (err) return callback(err);
      callback(null, res.pop());
    });
  },

  findById: function (id, callback) {
    this.findOne({ id: id }, callback);
  },

  findAll: function (callback) {
    this.find({}, callback);
  },

  getSchema: function () {
    var schema = this.prototype.schema;
    var newSchema = {};

    if (!schema)
      throw new Error('missing schema');

    if (String(schema) !== '[object Object]')
      throw new TypeError('schema must be an object');

    _.each(schema, function (value, key) {
      // A string means that the user wants this inserted as raw sql
      if (_.isString(value))
        newSchema[key] = _.extend({}, {sql: value});

      // Pass objects straight through
      if (_.isObject(value))
        newSchema[key] = _.extend({}, value);

      // Functions should generate an object with, at the very least, an `sql`
      // property. They can optionally generate more (like validators).
      // The function can also be a higher order function that returns a
      // function that does the above.
      if (_.isFunction(value)) {
        var spec = _.isFunction(value()) ? value()(key) : value(key);
        newSchema[key] = _.extend({}, newSchema[key], spec);
      }
      _.defaults(newSchema[key], {mutators: {}, validators: []});
    });
    this.getSchema = function () { return newSchema; };
    return newSchema;
  },

  createTableSql: function (schema) {
    var fieldsql = [];
    var keysql = [];
    var table = this.table();
    var engine = this.prototype.engine;
    if (!table) {
      throw new Error('table must be specified before trying to' +
                      ' make the `create table` statement');
    }
    _.each(schema, function (value, key) {
      fieldsql.push([
        _.backtick(key),
        value.sql
      ].join(' '));

      if (value.keysql)
        keysql.push(value.keysql);
    });

    var sql = _.strjoin([
      _.upcase('create table if not exists'),
      _.backtick(table),
      _.paren(_.union(fieldsql, keysql).join(', ')),
      _.upcase('engine ='),
      engine
    ]);

    return sql;
  },

  table: function () {
    return this.prototype.table;
  },

  version: function () {
    return this.prototype.version;
  },

  makeTable: function (callback) {
    var schema = this.getSchema();
    _.each(schema, function (spec) {
      if (spec.dependsOn) { spec.dependsOn.makeTable(); }
    });
    this.client.query(this.createTableSql(schema), callback);
  },

  updateSchemaVersionTable: function (callback) {
    function handleError (err) {
      if (callback) callback(err, null);
      else throw err;
    }
    callback = callback || function () {  };
    var client = this.client;
    var modelTable = this.table();
    var version = this.version() || '0000';
    var schemaTable = Base.SCHEMA_TABLE;
    var sql =  _.strjoin([
      'create table if not exists',
      _.backtick(schemaTable),
      _.paren([
        '`table` varchar(255) not null primary key',
        '`version` varchar(255) not null'
      ].join(','))
    ]);
    // first create the table if it doesn't exist.
    client.query(sql, function (err, result) {
      if (err) return handleError(err);

      // try to do an insert. if it fails with a 'duplicate key' message,
      // switch to using an update. if there's any other error, use
      // `handleError` to either send it to the callback or throw it.
      var data = { table: modelTable, version: version };
      return client.insert(schemaTable, data, function (err, result) {
        if (!err) return callback(null, result);
        if (!err.message.match(/duplicate/i)) return handleError(err);

        var where = { table: modelTable };
        client.update(schemaTable, data, where, function (err, result) {
          if (err) return handleError(err);
          return callback(null, result);
        });
      });
    });
  }
});

module.exports = Base;
<|endoftext|>


<commit_before>'use strict';

var toArray       = require('es5-ext/lib/Array/from')
  , d             = require('es5-ext/lib/Object/descriptor')
  , extend        = require('es5-ext/lib/Object/extend')
  , validDocument = require('dom-ext/lib/Document/valid-document')
  , normalize     = require('dom-ext/lib/Document/prototype/normalize')
  , isDF          = require('dom-ext/lib/DocumentFragment/is-document-fragment')
  , validNode     = require('dom-ext/lib/Node/valid-node')
  , comment       = require('./base/comment')
  , element       = require('./base/element')
  , text          = require('./base/text')

  , isArray, toArray, slice = Array.prototype.slice
  , defineProperty = Object.defineProperty
  , Base, setup;

setup = function (name, protoConstructor, methodName) {
	var props = {}, protoName = '_' + name + '_';
	props[protoName] = d.gs(function () {
		defineProperty(this, protoName, d('', protoConstructor(this)));
		return this[protoName];
	});
	props[name] = d.gs((function (f) {
		var fn = function (arg) {
			var el = this._current.appendChild(this.document[methodName](arg));
			el.__proto__ = this[protoName];
			return el._construct.apply(el, slice.call(arguments, 1));
		};
		return function () {
			defineProperty(this, name, d('e', fn.bind(this)));
			return this[name];
		};
	}()));
	return props;
};

Base = function (document) {
	this.document = document;
	defineProperty(this, '_current', d('cw', document.createDocumentFragment()));
};
Object.defineProperties(Base.prototype, extend({
	collect: d(function (fn) {
		var previous = this._current
		  , current = (this._current = this.document.createDocumentFragment());
		fn();
		this._current = previous;
		return current;
	}),
	safeCollect: d(function (fn) {
		var direct, result;
		result = this.collect(function () { direct = fn(); });
		if (direct) result = direct;
		result = isDF(result) ? toArray(result.childNodes) : result;
		if (isArray(result) && (result.length === 1)) result = result[0];
		if (result.toDOM) result = result.toDOM(this.document);
		return result;
	}),
	_element_: d(function (name) {
		var protoName = '_' + name + '_';
		if (this[protoName]) return this[protoName];
		defineProperty(this, protoName, d('', element(this, name)));
		return this[protoName];
	})
}, d.binder({
	normalize: d(function (node) {
		var name = validNode(node).nodeName, proto;
		if (name[0] === '#') {
			name = name.slice(1);
			proto = this['_' + name + '_'];
			if (!proto) throw new Error("Cannot normalize: " + node);
		} else if (!(proto = this['_' + name + '_'])) {
			proto = this._element_(name);
		}
		node.__proto__ = proto;
		return node;
	}),
	element: d('e', function (name) {
		var el = this._current.appendChild(this.document.createElement(name));
		el.__proto__ = this._element_(name);
		return el._construct.apply(el, slice.call(arguments, 1));
	}),
	insert: d('e', function (node/*, …nodes*/) {
		return normalize.apply(this.document, arguments)
			.map(this._current.appendChild, this._current);
	})
}),
	setup('comment', comment, 'createComment'),
	setup('text', text, 'createTextNode')));

module.exports = exports = function (document) {
	return new Base(validDocument(document));
};
exports.prototype = Base.prototype;
<commit_msg>Improve safeCollect<commit_after>'use strict';

var toArray       = require('es5-ext/lib/Array/from')
  , d             = require('es5-ext/lib/Object/descriptor')
  , extend        = require('es5-ext/lib/Object/extend')
  , validDocument = require('dom-ext/lib/Document/valid-document')
  , normalize     = require('dom-ext/lib/Document/prototype/normalize')
  , isDF          = require('dom-ext/lib/DocumentFragment/is-document-fragment')
  , validNode     = require('dom-ext/lib/Node/valid-node')
  , comment       = require('./base/comment')
  , element       = require('./base/element')
  , text          = require('./base/text')

  , slice = Array.prototype.slice
  , defineProperty = Object.defineProperty
  , Base, setup;

setup = function (name, protoConstructor, methodName) {
	var props = {}, protoName = '_' + name + '_';
	props[protoName] = d.gs(function () {
		defineProperty(this, protoName, d('', protoConstructor(this)));
		return this[protoName];
	});
	props[name] = d.gs((function (f) {
		var fn = function (arg) {
			var el = this._current.appendChild(this.document[methodName](arg));
			el.__proto__ = this[protoName];
			return el._construct.apply(el, slice.call(arguments, 1));
		};
		return function () {
			defineProperty(this, name, d('e', fn.bind(this)));
			return this[name];
		};
	}()));
	return props;
};

Base = function (document) {
	this.document = document;
	defineProperty(this, '_current', d('cw', document.createDocumentFragment()));
};
Object.defineProperties(Base.prototype, extend({
	collect: d(function (fn) {
		var previous = this._current
		  , current = (this._current = this.document.createDocumentFragment());
		fn();
		this._current = previous;
		return current;
	}),
	safeCollect: d(function (fn) {
		var direct, result;
		result = this.collect(function () { direct = fn(); });
		if (direct) result = direct;
		if (isDF(result)) result = toArray(result.childNodes);
		else result = normalize.call(this.document, result);
		if (result.length === 1) result = result[0];
		return result;
	}),
	_element_: d(function (name) {
		var protoName = '_' + name + '_';
		if (this[protoName]) return this[protoName];
		defineProperty(this, protoName, d('', element(this, name)));
		return this[protoName];
	})
}, d.binder({
	normalize: d(function (node) {
		var name = validNode(node).nodeName, proto;
		if (name[0] === '#') {
			name = name.slice(1);
			proto = this['_' + name + '_'];
			if (!proto) throw new Error("Cannot normalize: " + node);
		} else if (!(proto = this['_' + name + '_'])) {
			proto = this._element_(name);
		}
		node.__proto__ = proto;
		return node;
	}),
	element: d('e', function (name) {
		var el = this._current.appendChild(this.document.createElement(name));
		el.__proto__ = this._element_(name);
		return el._construct.apply(el, slice.call(arguments, 1));
	}),
	insert: d('e', function (node/*, …nodes*/) {
		return normalize.apply(this.document, arguments)
			.map(this._current.appendChild, this._current);
	})
}),
	setup('comment', comment, 'createComment'),
	setup('text', text, 'createTextNode')));

module.exports = exports = function (document) {
	return new Base(validDocument(document));
};
exports.prototype = Base.prototype;
<|endoftext|>


<commit_before>var parser           = require('./parser')
var ast              = require('./parser/ast')
var Attribute        = require('./bindings/attribute')
var BooleanAttribute = require('./bindings/boolean-attribute')
var Expression       = require('./bindings/expression')
var If               = require('./bindings/if')

var BOOLEAN_ATTRIBUTES = ['checked', 'selected', 'disabled']

var Observer = require('./utils/observer')

module.exports = function bind(target, locals, opts) {
  if (!opts) opts = {}

  if (!Array.isArray(locals)) {
    locals = [locals]
  }

  var Binding = function(path, binding) {
    path = path.slice()

    var obj = null
    for (var i = locals.length - 1; i >= 0; --i) {
      if (path[0] in locals[i]) {
        obj = locals[i]
        break
      }
    }

    this.name    = path.pop()
    this.binding = binding
    if (obj !== null && typeof obj === 'object') {
      this.observer = Observer.create(obj)
      this.observer.observe(this.name, this.binding)
    }
  }

  Binding.prototype.render = function() {
    this.binding.render()
  }

  Binding.prototype.dispose = function() {
    if (this.observer) {
      this.observer.unobserve(this.name, this.binding)
    }
    this.binding.dispose()
    this.observer = this.binding = undefined
  }

  var nodes = []

  function handle(node) {
    switch (node.nodeType) {
      case Node.ELEMENT_NODE:
        if (node.tagName === 'TEMPLATE' && (node.hasAttribute('if') || node.hasAttribute('repeat'))) {
          var tmpl = new If(node, locals)
          nodes.push(new Binding([], tmpl))
          break
        }

        var attrs = Array.prototype.slice.call(node.attributes)
        attrs.forEach(function(attr) {
          var template = parser.parse(attr.value)
          var body = template.body
          if (body.length === 1 && body[0] instanceof ast.Text) {
            // do nothing, since this text node does not contain any expression
            return
          }

          var isBoolean = BOOLEAN_ATTRIBUTES.indexOf(attr.name) > -1
          if (body.length === 1 && body[0] instanceof ast.Expression && isBoolean) {
            var attribute = new BooleanAttribute(node, locals, body[0], attr)
            nodes.push(new Binding(body[0].path.keys, attribute))
            return
          }

          var attributes = []
          body.forEach(function(child) {
            if (child instanceof ast.Expression) {
              var attribute = new Attribute(node, locals, template, attr)
              attributes.push(new Binding(child.path.keys, attribute))
            }
          })

          if (attributes.length) nodes.push(attributes[0])
        })
        break
      case Node.TEXT_NODE:
        var body = parser.parse(node.nodeValue).body
        if (body.length === 1 && body[0] instanceof ast.Text) {
          // do nothing, since this text node does not contain any expression
          break
        }

        body.forEach(function(child) {
          var textNode = document.createTextNode(child.text || '')

          if (child instanceof ast.Expression) {
            var expression = new Expression(textNode, locals, child)
            nodes.push(new Binding(child.path.keys, expression))
          }

          nodes.push(function(node) {
            node.parentNode.insertBefore(textNode, node)
          }.bind(null, node))
        })

        nodes.push(function(node) {
          node.parentNode.removeChild(node)
        }.bind(null, node))

        break
    }
  }

  if (opts.deep === false) {
    var children = Array.prototype.slice.call(target.childNodes)
    children.forEach(handle)
  } else {
    var accept = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
    var walker = document.createTreeWalker(target, accept, function(/*node*/) {
      return NodeFilter.FILTER_ACCEPT
    }, false)

    while (walker.nextNode()) {
      handle(walker.currentNode)
    }
  }

  nodes.forEach(function(node) {
    if (typeof node === 'function') node()
    else node.render()
  })
}
<commit_msg>fix binding for deep paths<commit_after>var parser           = require('./parser')
var ast              = require('./parser/ast')
var Attribute        = require('./bindings/attribute')
var BooleanAttribute = require('./bindings/boolean-attribute')
var Expression       = require('./bindings/expression')
var If               = require('./bindings/if')

var BOOLEAN_ATTRIBUTES = ['checked', 'selected', 'disabled']

var Observer = require('./utils/observer')

module.exports = function bind(target, locals, opts) {
  if (!opts) opts = {}

  if (!Array.isArray(locals)) {
    locals = [locals]
  }

  var Binding = function(path, binding) {
    this.binding = binding

    path = path.slice()

    var obj = null
    for (var i = locals.length - 1; i >= 0; --i) {
      if (path[0] in locals[i]) {
        obj = locals[i]
        break
      }
    }

    if (!obj) {
      return this
    }

    var key = path.pop()
    var prop
    while ((prop = path.shift())) {
      if (!(prop in obj)) return ''
      else obj = obj[prop]
    }

    this.name    = key
    if (obj !== null && typeof obj === 'object') {
      this.observer = Observer.create(obj)
      this.observer.observe(this.name, this.binding)
    }
  }

  Binding.prototype.render = function() {
    this.binding.render()
  }

  Binding.prototype.dispose = function() {
    if (this.observer) {
      this.observer.unobserve(this.name, this.binding)
    }
    this.binding.dispose()
    this.observer = this.binding = undefined
  }

  var nodes = []

  function handle(node) {
    switch (node.nodeType) {
      case Node.ELEMENT_NODE:
        if (node.tagName === 'TEMPLATE' && (node.hasAttribute('if') || node.hasAttribute('repeat'))) {
          var tmpl = new If(node, locals)
          nodes.push(new Binding([], tmpl))
          break
        }

        var attrs = Array.prototype.slice.call(node.attributes)
        attrs.forEach(function(attr) {
          var template = parser.parse(attr.value)
          var body = template.body
          if (body.length === 1 && body[0] instanceof ast.Text) {
            // do nothing, since this text node does not contain any expression
            return
          }

          var isBoolean = BOOLEAN_ATTRIBUTES.indexOf(attr.name) > -1
          if (body.length === 1 && body[0] instanceof ast.Expression && isBoolean) {
            var attribute = new BooleanAttribute(node, locals, body[0], attr)
            nodes.push(new Binding(body[0].path.keys, attribute))
            return
          }

          var attributes = []
          body.forEach(function(child) {
            if (child instanceof ast.Expression) {
              var attribute = new Attribute(node, locals, template, attr)
              attributes.push(new Binding(child.path.keys, attribute))
            }
          })

          if (attributes.length) nodes.push(attributes[0])
        })
        break
      case Node.TEXT_NODE:
        var body = parser.parse(node.nodeValue).body
        if (body.length === 1 && body[0] instanceof ast.Text) {
          // do nothing, since this text node does not contain any expression
          break
        }

        body.forEach(function(child) {
          var textNode = document.createTextNode(child.text || '')

          if (child instanceof ast.Expression) {
            var expression = new Expression(textNode, locals, child)
            nodes.push(new Binding(child.path.keys, expression))
          }

          nodes.push(function(node) {
            node.parentNode.insertBefore(textNode, node)
          }.bind(null, node))
        })

        nodes.push(function(node) {
          node.parentNode.removeChild(node)
        }.bind(null, node))

        break
    }
  }

  if (opts.deep === false) {
    var children = Array.prototype.slice.call(target.childNodes)
    children.forEach(handle)
  } else {
    var accept = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
    var walker = document.createTreeWalker(target, accept, function(/*node*/) {
      return NodeFilter.FILTER_ACCEPT
    }, false)

    while (walker.nextNode()) {
      handle(walker.currentNode)
    }
  }

  nodes.forEach(function(node) {
    if (typeof node === 'function') node()
    else node.render()
  })
}
<|endoftext|>


<commit_before>var q = require('q'),
    _ = require('lodash'),
    common = require('@screeps/common');

function removeHidden(obj) {
    for(var i in obj) {
        if(i[0] == '_') {
            delete obj[i];
            continue;
        }
        if(_.isArray(obj[i])) {
            obj[i].forEach(removeHidden);
            continue;
        }
        if(_.isObject(obj[i])) {
            removeHidden(obj[i]);
        }
    }
}

module.exports = function(collectionName) {

    var bulk = [],
        opsCnt = 0,
        updates = {};

    return {
        update(id, data) {
            if(!id) {
                return;
            }
            opsCnt++;
            data = _.cloneDeep(data);

            _.forEach(data, (value, key) => {
                if(_.isObject(value)) {
                    if (!_.isObject(id)) {
                        throw new Error(`can not update an object diff property '${key}' without object reference`);
                    }
                    var originalValue = id[key] || {};
                    _.merge(originalValue, value);
                    data[key] = originalValue;
                }
            });
            if(_.isObject(id)) {
                _.merge(id, data);
                id = id._id;
            }

            removeHidden(data);

            updates[id] = updates[id] || {};
            _.extend(updates[id], data);
        },
        insert(data, id) {
            data = _.cloneDeep(data);
            removeHidden(data);

            if(id) {
                data._id = id;
            }

            opsCnt++;
            bulk.push({op: 'insert', data});
        },
        remove(id) {
            if(!id) {
                return;
            }
            opsCnt++;
            bulk.push({op: 'remove', id});
        },
        inc(id, key, amount) {
            if(!id) {
                return;
            }
            opsCnt++;
            bulk.push({op: 'inc', id, key, amount});
        },
        execute() {
            if(!opsCnt) return q.when({});
            for(var id in updates) {
                bulk.push({op: 'update', id, $set: updates[id]});
            }
            return common.storage.db[collectionName].bulk(bulk);
        }
    }

};<commit_msg>fix(bulk): add an option to pass a target object to "inc" method<commit_after>var q = require('q'),
    _ = require('lodash'),
    common = require('@screeps/common');

function removeHidden(obj) {
    for(var i in obj) {
        if(i[0] == '_') {
            delete obj[i];
            continue;
        }
        if(_.isArray(obj[i])) {
            obj[i].forEach(removeHidden);
            continue;
        }
        if(_.isObject(obj[i])) {
            removeHidden(obj[i]);
        }
    }
}

module.exports = function(collectionName) {

    var bulk = [],
        opsCnt = 0,
        updates = {};

    return {
        update(id, data) {
            if(!id) {
                return;
            }
            opsCnt++;
            data = _.cloneDeep(data);

            _.forEach(data, (value, key) => {
                if(_.isObject(value)) {
                    if (!_.isObject(id)) {
                        throw new Error(`can not update an object diff property '${key}' without object reference`);
                    }
                    var originalValue = id[key] || {};
                    _.merge(originalValue, value);
                    data[key] = originalValue;
                }
            });
            if(_.isObject(id)) {
                _.merge(id, data);
                id = id._id;
            }

            removeHidden(data);

            updates[id] = updates[id] || {};
            _.extend(updates[id], data);
        },
        insert(data, id) {
            data = _.cloneDeep(data);
            removeHidden(data);

            if(id) {
                data._id = id;
            }

            opsCnt++;
            bulk.push({op: 'insert', data});
        },
        remove(id) {
            if(!id) {
                return;
            }
            opsCnt++;
            bulk.push({op: 'remove', id});
        },
        inc(id, key, amount) {
            if(!id) {
                return;
            }
            if (_.isObject(id)) {
                id = id._id;
            }
            opsCnt++;
            bulk.push({op: 'inc', id, key, amount});
        },
        execute() {
            if(!opsCnt) return q.when({});
            for(var id in updates) {
                bulk.push({op: 'update', id, $set: updates[id]});
            }
            return common.storage.db[collectionName].bulk(bulk);
        }
    }

};<|endoftext|>


<commit_before>// Generated on 2015-09-22 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true,
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'earlyVotingApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*',
            'data/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<commit_msg>Remove htmlmin, as that is now hanging up the build process.<commit_after>// Generated on 2015-09-22 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server', 'autoprefixer:server']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true,
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      },
      sass: {
        src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        ignorePath: /(\.\.\/){1,2}bower_components\//
      }
    },

    // Compiles Sass to CSS and generates necessary files if requested
    compass: {
      options: {
        sassDir: '<%= yeoman.app %>/styles',
        cssDir: '.tmp/styles',
        generatedImagesDir: '.tmp/images/generated',
        imagesDir: '<%= yeoman.app %>/images',
        javascriptsDir: '<%= yeoman.app %>/scripts',
        fontsDir: '<%= yeoman.app %>/styles/fonts',
        importPath: './bower_components',
        httpImagesPath: '/images',
        httpGeneratedImagesPath: '/images/generated',
        httpFontsPath: '/styles/fonts',
        relativeAssets: false,
        assetCacheBuster: false,
        raw: 'Sass::Script::Number.precision = 10\n'
      },
      dist: {
        options: {
          generatedImagesDir: '<%= yeoman.dist %>/images/generated'
        }
      },
      server: {
        options: {
          sourcemap: true
        }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'earlyVotingApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*',
            'data/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'compass:server'
      ],
      test: [
        'compass'
      ],
      dist: [
        'compass:dist',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<|endoftext|>


<commit_before>$(document).ready(function(){
	/*
		Constrollo se è la prima visita dell'utente o se ci sono
		già i dati salvati in localstorage
	*/
	if (!check_user_local_storage()) {
		//Se e la prima visita carico il menu di benvenuto,
		//di default nascosto
		if ($("#welcome").css("display") == "none") {
			$("#welcome").show();
		};
	}else{
		//Carico i dati dell'utente
	}
});

//===============================================================
//======================  LOCALSTORAGE ==========================
//===============================================================

/**
	Funzione che controlla che in localStorage, alla chiave
	MMCUserData sono presenti dati
	@return true ci sono dati, false altrimenti
*/
function check_user_local_storage () {
	if (localStorage["MMCUserData"] != null) {
		return true;
	}else{
		return false;
	}
}

/**
	Funzione che carica da localstorage i dati salvati dall'utente
	@return Object che contiene i dati.
*/
function load_user_data () {
	var data;
	if (check_user_local_storage()) {
		try{
			data = JSON.parse(localStorage["MMCUserData"]);
		}catch(ex){
			console.log("Errore nel parsing dei dati utente");
		}
	};

	return data;
}

/**
	Funzione che salva i dati dell'utente in localStorage
	@data: dati da salvare
*/
function save_user_data (data) {
	localStorage["MMCUserData"] = JSON.stringify(data);
}<commit_msg>Aggiunta costante per la chiave utente<commit_after>$(document).ready(function(){
	/*
		Constrollo se è la prima visita dell'utente o se ci sono
		già i dati salvati in localstorage
	*/
	if (!check_user_local_storage()) {
		//Se e la prima visita carico il menu di benvenuto,
		//di default nascosto
		if ($("#welcome").css("display") == "none") {
			$("#welcome").show();
		};
	}else{
		//Carico i dati dell'utente
	}
});

//===============================================================
//======================  LOCALSTORAGE ==========================
//===============================================================

var userKey = {
	"key" : "MMCUserData"
}

/**
	Funzione che controlla che in localStorage, alla chiave
	MMCUserData sono presenti dati
	@return true ci sono dati, false altrimenti
*/
function check_user_local_storage () {
	if (localStorage[userKey["key"]] != null) {
		return true;
	}else{
		return false;
	}
}

/**
	Funzione che carica da localstorage i dati salvati dall'utente
	@return Object che contiene i dati.
*/
function load_user_data () {
	var data;
	if (check_user_local_storage()) {
		try{
			data = JSON.parse(localStorage[userKey["key"]]);
		}catch(ex){
			console.log("Errore nel parsing dei dati utente");
		}
	};

	return data;
}

/**
	Funzione che salva i dati dell'utente in localStorage
	@data: dati da salvare
*/
function save_user_data (data) {
	localStorage[userKey["key"]] = JSON.stringify(data);
}<|endoftext|>


<commit_before>App.actions = {
  listItem: {
    update: function(itemData, newItemData) {
      var payload = {
        itemData: itemData,
        newItemData: newItemData
      };

      this.dispatch(App.constants.ITEM_UPDATE, payload);
      App.updateItem(
        payload,
        function(data) {
          this.dispatch(App.constants.ITEM_UPDATE_SUCCESS, data);
        }.bind(this),
        function(payload) {
          this.dispatch(App.constants.ITEM_UPDATE_FAILURE, payload);
        }.bind(this)
      );
    },

    updateMultiple: function() {
      console.log('updateMultiple');
    },

    add: function(itemData) {
      var listID = this.flux.store("ListStore").getID();
      var listItemID = App.makeId();
      itemData.list_id = listID;
      itemData.listItemID = listItemID;
      App.saveItem(
        itemData,
        function(newItemData) {
          this.dispatch(App.constants.ITEM_ADD, newItemData);
        }.bind(this)
      );
    },

    delete: function(itemId) {
      this.dispatch(App.constants.ITEM_DELETE, itemId);
      App.deleteItem(itemId);
    }
  },
  list: {
    update: function(listData) {
      // console.log('Action: list::update()', listData);
      this.dispatch(App.constants.LIST_UPDATE, listData.newData);
      listData.newData.listId = this.flux.store("ListStore").getID();
      App.updateList(
        listData.newData,
        function(data) {
          this.dispatch(App.constants.LIST_UPDATE_SUCCESS, data);
        }.bind(this),
        function(error) {
          listData.error = error;
          this.dispatch(App.constants.LIST_UPDATE_FAILURE, listData);
        }.bind(this)
      );
    },

    updateTags: function(newTagData) {
      this.dispatch(App.constants.LIST_TAGS_UPDATE);
      App.updateTags(
        newTagData,
        function(data) {
          this.dispatch(App.constants.LIST_UPDATE_SUCCESS, data);
        }.bind(this),
        function(error) {
          this.dispatch(App.constants.LIST_TAGS_UPDATE_FAILURE);
        }.bind(this)
      );
    },

    removeTag: function(tagToRemove) {
      console.log('delete', tagToRemove);
      this.dispatch(App.constants.LIST_TAG_REMOVE);
      App.removeTag(
        tagToRemove,
        function(data) {
          // console.log('remove success', data);
          this.dispatch(App.constants.LIST_TAG_REMOVE_SUCCESS, data);
        }.bind(this),
        function(error) {
          // console.log('error remove', error);
          this.dispatch(App.constants.LIST_TAG_REMOVE_FAILURE);
        }.bind(this)
      );
    }
  }
};<commit_msg>Add UI only action.<commit_after>App.actions = {
  listItem: {
    update: function(itemData, newItemData) {
      var payload = {
        itemData: itemData,
        newItemData: newItemData
      };

      this.dispatch(App.constants.ITEM_UPDATE, payload);
      App.updateItem(
        payload,
        function(data) {
          this.dispatch(App.constants.ITEM_UPDATE_SUCCESS, data);
        }.bind(this),
        function(payload) {
          this.dispatch(App.constants.ITEM_UPDATE_FAILURE, payload);
        }.bind(this)
      );
    },

    updateMultiple: function() {
      console.log('updateMultiple');
    },

    add: function(itemData) {
      var listID = this.flux.store("ListStore").getID();
      var listItemID = App.makeId();
      itemData.list_id = listID;
      itemData.listItemID = listItemID;
      App.saveItem(
        itemData,
        function(newItemData) {
          this.dispatch(App.constants.ITEM_ADD, newItemData);
        }.bind(this)
      );
    },

    delete: function(itemId) {
      this.dispatch(App.constants.ITEM_DELETE, itemId);
      App.deleteItem(itemId);
    },

    addToUiOnly: function(itemData) {
      this.dispatch(App.constants.ITEM_ADD, itemData);
    }
  },
  list: {
    update: function(listData) {
      // console.log('Action: list::update()', listData);
      this.dispatch(App.constants.LIST_UPDATE, listData.newData);
      listData.newData.listId = this.flux.store("ListStore").getID();
      App.updateList(
        listData.newData,
        function(data) {
          this.dispatch(App.constants.LIST_UPDATE_SUCCESS, data);
        }.bind(this),
        function(error) {
          listData.error = error;
          this.dispatch(App.constants.LIST_UPDATE_FAILURE, listData);
        }.bind(this)
      );
    },

    updateTags: function(newTagData) {
      this.dispatch(App.constants.LIST_TAGS_UPDATE);
      App.updateTags(
        newTagData,
        function(data) {
          this.dispatch(App.constants.LIST_UPDATE_SUCCESS, data);
        }.bind(this),
        function(error) {
          this.dispatch(App.constants.LIST_TAGS_UPDATE_FAILURE);
        }.bind(this)
      );
    },

    removeTag: function(tagToRemove) {
      console.log('delete', tagToRemove);
      this.dispatch(App.constants.LIST_TAG_REMOVE);
      App.removeTag(
        tagToRemove,
        function(data) {
          // console.log('remove success', data);
          this.dispatch(App.constants.LIST_TAG_REMOVE_SUCCESS, data);
        }.bind(this),
        function(error) {
          // console.log('error remove', error);
          this.dispatch(App.constants.LIST_TAG_REMOVE_FAILURE);
        }.bind(this)
      );
    }
  }
};<|endoftext|>