<commit_before>//
// Eyes.js - a customizable value inspector for Node.js
//
//   usage:
//
//       var inspect = require('eyes').inspector({styles: {all: 'magenta'}});
//       inspect(something); // inspect with the settings passed to `inspector`
//
//     or
//
//       var eyes = require('eyes');
//       eyes.inspect(something); // inspect with the default settings
//
var eyes = {
    defaults: {
        styles: {                // Styles applied to stdout
            all:    'cyan',      // Overall style applied to everything
            label:  'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]`
            other:  'inverted',  // Objects which don't have a literal representation, such as functions
            key:    'bold',      // The keys in object literals, like 'a' in `{a: 1}`
            special: null,       // null, undefined...
            string:  null,
            number:  null,
            bool:    null
        },
        writer: process.stdio.write,
        maxLength: 2048          // Truncate output if longer
    },

    // Return a curried inspect() function, with the `options` argument filled in.
    inspector: function (options) {
        var that = this;
        return function (obj, label, opts) {
            return that.inspect(obj, label,
                process.mixin(true, {}, options || {}, opts || {}));
        };
    },

    // Calls print() to output the inspected object
    inspect: function (obj, label, options) {
        options = process.mixin(true, {}, this.defaults, options || {})
        return this.print(this.stringify(obj, options.styles), label, options);
    },

    // Output using the 'writer', and an optional label
    // Loop through `str`, and truncate it after `options.maxLength` has been reached.
    // Because escape sequences are, at this point embeded within
    // the output string, we can't measure the length of the string
    // in a useful way, without separating what is an escape sequence,
    // versus a printable character (`c`). So we resort to counting the
    // length manually.
    print: function (str, label, options) {
        for (var c = 0, i = 0; i < str.length; i++) {
            if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5
            else if (c === options.maxLength) {
               str = str.slice(0, i - 1) + '…' + '\033[0;39m';
               break;
            } else { c++ }
        }
        return options.writer((label ? this.stylize(label, options.styles.label, options.styles) + ': ' : '') +
                                       this.stylize(str,   options.styles.all, options.styles) + "\n");
    },

    // Convert any object to a string, ready for output.
    // When an 'array' or an 'object' are encountered, they are
    // passed to specialized functions, which can then recursively call
    // stringify().
    stringify: function (obj, styles) {
        var that = this, stylize = function (str, style) {
            return that.stylize(str, style, styles)
        };

        switch (typeOf(obj)) {
            case "string":
                obj = (obj.length === 1 ? "'" + obj + "'" : '"' + obj + '"')
                      .replace(/\n/g, '\\n');
                return stylize(obj, styles.string);
            case "regexp"   : return stylize('/' + obj.source + '/', styles.regexp);
            case "number"   : return stylize(obj + '', styles.number);
            case "function" : return stylize("Function",  styles.other);
            case "null"     : return stylize("null",      styles.special);
            case "undefined": return stylize("undefined", styles.special);
            case "boolean"  : return stylize(obj + '',    styles.bool);
            case "array"    : return this.stringifyArray(obj,  styles);
            case "object"   : return this.stringifyObject(obj, styles);
        }
    },

    // Convert an array to a string, such as [1, 2, 3].
    // This function calls stringify() for each of the elements
    // in the array.
    stringifyArray: function (ary, styles) {
        var out = [];

        for (var i = 0; i < ary.length; i++) {
            out.push(this.stringify(ary[i], styles));
        }
        return '[' + out.join(', ') + ']';
    },

    // Convert an object to a string, such as {a: 1}.
    // This function calls stringify() for each of its values,
    // and does not output functions or prototype values.
    stringifyObject: function (obj, styles) {
        var out = [];

        for (var k in obj) {
            if (obj.hasOwnProperty(k) && !(obj[k] instanceof Function)) {
                out.push(this.stylize(k, styles.key, styles) + ': ' + this.stringify(obj[k], styles));
            }
        }
        return "{" + out.join(', ') + "}";
    },

    // Apply a style to a string, eventually,
    // I'd like this to support passing multiple
    // styles.
    stylize: function (str, style, styles) {
        var codes = {
            'bold'      : [1,  22],
            'underline' : [4,  24],
            'inverse'   : [7,  27],
            'cyan'      : [36, 39],
            'magenta'   : [35, 39],
            'yellow'    : [33, 39],
            'green'     : [32, 39],
            'red'       : [31, 39]
        }, endCode;

        if (style && codes[style]) {
            endCode = (codes[style][1] === 39 && styles.all) ? codes[styles.all][0]
                                                             : codes[style][1];
            return '\033[' + codes[style][0] + 'm' + str +
                   '\033[' + endCode + 'm';
        } else { return str }
    }
};

// CommonJS module support
process.mixin(exports, eyes);

// A better `typeof`
function typeOf(value) {
    var s = typeof value;

    if (s === 'object' || s === 'function') {
        if (value) {
            if (value instanceof Array) { s = 'array' }
            else if (value instanceof RegExp) { s = 'regexp' }
        } else { s = 'null' }
    }
    return s;
}

<commit_msg>moved the color reset, so it would happen in all scenarios<commit_after>//
// Eyes.js - a customizable value inspector for Node.js
//
//   usage:
//
//       var inspect = require('eyes').inspector({styles: {all: 'magenta'}});
//       inspect(something); // inspect with the settings passed to `inspector`
//
//     or
//
//       var eyes = require('eyes');
//       eyes.inspect(something); // inspect with the default settings
//
var eyes = {
    defaults: {
        styles: {                // Styles applied to stdout
            all:    'cyan',      // Overall style applied to everything
            label:  'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]`
            other:  'inverted',  // Objects which don't have a literal representation, such as functions
            key:    'bold',      // The keys in object literals, like 'a' in `{a: 1}`
            special: null,       // null, undefined...
            string:  null,
            number:  null,
            bool:    null
        },
        writer: process.stdio.write,
        maxLength: 2048          // Truncate output if longer
    },

    // Return a curried inspect() function, with the `options` argument filled in.
    inspector: function (options) {
        var that = this;
        return function (obj, label, opts) {
            return that.inspect(obj, label,
                process.mixin(true, {}, options || {}, opts || {}));
        };
    },

    // Calls print() to output the inspected object
    inspect: function (obj, label, options) {
        options = process.mixin(true, {}, this.defaults, options || {})
        return this.print(this.stringify(obj, options.styles), label, options);
    },

    // Output using the 'writer', and an optional label
    // Loop through `str`, and truncate it after `options.maxLength` has been reached.
    // Because escape sequences are, at this point embeded within
    // the output string, we can't measure the length of the string
    // in a useful way, without separating what is an escape sequence,
    // versus a printable character (`c`). So we resort to counting the
    // length manually.
    print: function (str, label, options) {
        for (var c = 0, i = 0; i < str.length; i++) {
            if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5
            else if (c === options.maxLength) {
               str = str.slice(0, i - 1) + '…';
               break;
            } else { c++ }
        }
        return options.writer((label ? this.stylize(label, options.styles.label, options.styles) + ': ' : '') +
                                       this.stylize(str,   options.styles.all, options.styles) + "\n" + '\033[0m');
    },

    // Convert any object to a string, ready for output.
    // When an 'array' or an 'object' are encountered, they are
    // passed to specialized functions, which can then recursively call
    // stringify().
    stringify: function (obj, styles) {
        var that = this, stylize = function (str, style) {
            return that.stylize(str, style, styles)
        };

        switch (typeOf(obj)) {
            case "string":
                obj = (obj.length === 1 ? "'" + obj + "'" : '"' + obj + '"')
                      .replace(/\n/g, '\\n');
                return stylize(obj, styles.string);
            case "regexp"   : return stylize('/' + obj.source + '/', styles.regexp);
            case "number"   : return stylize(obj + '', styles.number);
            case "function" : return stylize("Function",  styles.other);
            case "null"     : return stylize("null",      styles.special);
            case "undefined": return stylize("undefined", styles.special);
            case "boolean"  : return stylize(obj + '',    styles.bool);
            case "array"    : return this.stringifyArray(obj,  styles);
            case "object"   : return this.stringifyObject(obj, styles);
        }
    },

    // Convert an array to a string, such as [1, 2, 3].
    // This function calls stringify() for each of the elements
    // in the array.
    stringifyArray: function (ary, styles) {
        var out = [];

        for (var i = 0; i < ary.length; i++) {
            out.push(this.stringify(ary[i], styles));
        }
        return '[' + out.join(', ') + ']';
    },

    // Convert an object to a string, such as {a: 1}.
    // This function calls stringify() for each of its values,
    // and does not output functions or prototype values.
    stringifyObject: function (obj, styles) {
        var out = [];

        for (var k in obj) {
            if (obj.hasOwnProperty(k) && !(obj[k] instanceof Function)) {
                out.push(this.stylize(k, styles.key, styles) + ': ' + this.stringify(obj[k], styles));
            }
        }
        return "{" + out.join(', ') + "}";
    },

    // Apply a style to a string, eventually,
    // I'd like this to support passing multiple
    // styles.
    stylize: function (str, style, styles) {
        var codes = {
            'bold'      : [1,  22],
            'underline' : [4,  24],
            'inverse'   : [7,  27],
            'cyan'      : [36, 39],
            'magenta'   : [35, 39],
            'yellow'    : [33, 39],
            'green'     : [32, 39],
            'red'       : [31, 39]
        }, endCode;

        if (style && codes[style]) {
            endCode = (codes[style][1] === 39 && styles.all) ? codes[styles.all][0]
                                                             : codes[style][1];
            return '\033[' + codes[style][0] + 'm' + str +
                   '\033[' + endCode + 'm';
        } else { return str }
    }
};

// CommonJS module support
process.mixin(exports, eyes);

// A better `typeof`
function typeOf(value) {
    var s = typeof value;

    if (s === 'object' || s === 'function') {
        if (value) {
            if (value instanceof Array) { s = 'array' }
            else if (value instanceof RegExp) { s = 'regexp' }
        } else { s = 'null' }
    }
    return s;
}

<|endoftext|>


<commit_before>var xml     = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.title          = options.title;
    this.description    = options.description;
    this.link           = options.link;
    this.image          = options.image;
    this.copyright      = options.copyright;

    this.author         = options.author;

    this.items          = [];
    this.categories     = [];

    this.item = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author
        };

        this.items.push(item);
        return this;
    }

    this.category = function (category) {
        this.categories.push(category);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };
};

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    feed.push({ title:      options.title });
    feed.push({ id:         options.link });
    feed.push({ updated:    RFC3339(d) });      // Auto-magic :)

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/
    if(author)
    {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (relative link to feed) - will be added in a future version

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/
    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    // contributor

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i=0; i<entries.length; i++) {
        //
        // entry: required elements
        //
        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        // entry author(s)
        if(entries[i].author) {
            for(var y=0; y<entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor

        // published

        // source

        // rights

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var author = options.author;
    var d = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    // Required RSS 2.0 channel elements
    channel.push({ title:           options.title });
    channel.push({ description:     options.description });
    channel.push({ link:            options.link });

    if(author.email && author.name)
        channel.push({ author: author.email + ' (' + author.name + ')' });

    else if(author.email && !author.name)
        channel.push({ author: author.email });

    else if(!author.email && author.name)
         channel.push({ author: author.name });

    channel.push({ lastBuildDate:   d.toUTCString() })

    if(options.image)
        channel.push({ image:   options.image });

    if(options.copyright)
        channel.push({ copyright:   options.copyright });

    channel.push({ generator:   generatorName });

    // Categories
    for(var i=0; i<categories.length; i++)
        channel.push({ category: categories[i] });

    // Items
    for(var i=0; i<items.length; i++) {
        var item = [
            { title:        { _cdata: items[i].title }},
            { link:         items[i].link },
            { pubDate:      items[i].date.toUTCString() },
            { description:  { _cdata: items[i].description }}
        ];

        if(items[i].author) {
            for(var y=0; y<items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

module.exports = Feed;
<commit_msg>deprecating item() and category(), replaced by addItem() and addCategory()<commit_after>var xml     = require('xml');
var generatorName = 'Feed for Node.js';

// ============================================================================
// Feed Main Module
// ============================================================================
function Feed(options) {
    this.title          = options.title;
    this.description    = options.description;
    this.link           = options.link;
    this.image          = options.image;
    this.copyright      = options.copyright;

    this.author         = options.author;

    this.items          = [];
    this.categories     = [];

    this.addItem = function (options) {
        options = options || {};
        var item = {
            title:          options.title,
            link:           options.link,
            description:    options.description,
            date:           options.date,
            image:          options.image,
            author:         options.author
        };

        this.items.push(item);
        return this;
    }

    this.addCategory = function (category) {
        this.categories.push(category);
        return this;
    }

    this.render = function (format) {
        switch(format) {
            case 'rss-2.0':
                return rss_2_0(this);
            break;

            case 'atom-1.0':
                return atom_1_0(this);
            break;

            default:
                return rss_2_0(this);
        }
    };

    // DEPRECATED - replaced by addItem(options) and addCategory(category)
    this.item     = function (options)  { return this.addItem(options); }
    this.category = function (category) { return this.addCategory(category); }
};

// ============================================================================
// Atom 1.0 [application/atom+xml]
//
// - Reference: http://www.atomenabled.org/developers/syndication/
// ============================================================================

function atom_1_0(options) {
    var items       = [],
        container   = [],
        feed        = [],

        entries     = options.items,
        categories  = options.categories,
        author      = options.author,
        d           = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ feed: feed });
    feed.push({ _attr: { xmlns: 'http://www.w3.org/2005/Atom' }});

    /**************************************************************************
     * "feed" node: REQUIRED elements
     *************************************************************************/
    feed.push({ title:      options.title });
    feed.push({ id:         options.link });
    feed.push({ updated:    RFC3339(d) });      // Auto-magic :)

    /**************************************************************************
     * "feed" node: recommended elements
     *************************************************************************/
    if(author)
    {
        var authorDetails = [];

        if(author.name)
            authorDetails.push({ name:     author.name });

        if(author.email)
            authorDetails.push({ email:     author.email });

        if(author.link)
            authorDetails.push({ uri:     author.link });

        feed.push({ author: authorDetails });
    }

    // link (relative link to feed) - will be added in a future version

    /**************************************************************************
     * "feed" node: optional elements
     *************************************************************************/
    if(options.description)
        feed.push({ subtitle:   options.description });

    if(options.image)
        feed.push({ logo:       options.image });

    if(options.copyright)
        feed.push({ rights:     options.copyright });

    feed.push({ generator:      generatorName });

    // Looping through categories (if any set!)
    for(var i=0; i<categories.length; i++)
        feed.push({ category: [{ _attr: { term: categories[i] } }] });

    // contributor

    // icon

    /**************************************************************************
     * "entry" nodes
     *************************************************************************/
    for(var i=0; i<entries.length; i++) {
        //
        // entry: required elements
        //
        var entry = [
            { title:        { _attr: { type: 'html' }, _cdata: entries[i].title }},
            { id:           entries[i].link },
            { link:         [{ _attr: { href: entries[i].link } }]},
            { updated:      RFC3339(entries[i].date) }
        ];

        //
        // entry: recommended elements
        //
        if(entries[i].description)
            entry.push({ summary: { _attr: { type: 'html' }, _cdata: entries[i].description }});

        // entry author(s)
        if(entries[i].author) {
            for(var y=0; y<entries[i].author.length; y++) {
                var entryAuthor = [];

                if(entries[i].author[y].name)
                    entryAuthor.push({ name: entries[i].author[y].name });

                if(entries[i].author[y].email)
                    entryAuthor.push({ email: entries[i].author[y].email });

                if(entries[i].author[y].link)
                    entryAuthor.push({ uri: entries[i].author[y].link });

                entry.push({ author: entryAuthor });
            }
        }

        // content

        // link - relative link to article

        //
        // entry: optional elements
        //

        // category

        // contributor

        // published

        // source

        // rights

        feed.push({ entry: entry });
    }

    return doctype + xml(container, true);
}

// ============================================================================
// Real Simple Syndication 2.0 (RSS 2.0)
//
// - Reference: http://feed2.w3.org/docs/rss2.html
// ============================================================================
function rss_2_0(options) {
    var items       = [],
        channel     = [],
        container   = [];

    var items = options.items;
    var categories = options.categories;
    var author = options.author;
    var d = new Date();

    var doctype = '<?xml version="1.0" encoding="utf-8"?>\n';

    container.push({ rss: [] });
    container[0].rss.push({ _attr: { version: '2.0' } });
    container[0].rss.push({ channel: channel });

    // Required RSS 2.0 channel elements
    channel.push({ title:           options.title });
    channel.push({ description:     options.description });
    channel.push({ link:            options.link });

    if(author.email && author.name)
        channel.push({ author: author.email + ' (' + author.name + ')' });

    else if(author.email && !author.name)
        channel.push({ author: author.email });

    else if(!author.email && author.name)
         channel.push({ author: author.name });

    channel.push({ lastBuildDate:   d.toUTCString() })

    if(options.image)
        channel.push({ image:   options.image });

    if(options.copyright)
        channel.push({ copyright:   options.copyright });

    channel.push({ generator:   generatorName });

    // Categories
    for(var i=0; i<categories.length; i++)
        channel.push({ category: categories[i] });

    // Items
    for(var i=0; i<items.length; i++) {
        var item = [
            { title:        { _cdata: items[i].title }},
            { link:         items[i].link },
            { pubDate:      items[i].date.toUTCString() },
            { description:  { _cdata: items[i].description }}
        ];

        if(items[i].author) {
            for(var y=0; y<items[i].author.length; y++) {
                var itemAuthor = [];

                if(items[i].author[y].name)
                    itemAuthor.push({ name: items[i].author[y].name });

                if(items[i].author[y].email)
                    itemAuthor.push({ email: items[i].author[y].email });

                if(items[i].author[y].link)
                    itemAuthor.push({ link: items[i].author[y].link });

                item.push({ author: itemAuthor });
            }
        }

        if(items[i].image)
            item.push({ enclosure: [{ _attr: { url: items[i].image } }] });

        channel.push({ item: item });
    }

    return doctype + xml(container, true);
}

function RFC3339(d) {
    function pad(n) { return n < 10 ? '0' + n : n }

    return d.getUTCFullYear()+'-'
        + pad(d.getUTCMonth()+1)+'-'
        + pad(d.getUTCDate())+'T'
        + pad(d.getUTCHours())+':'
        + pad(d.getUTCMinutes())+':'
        + pad(d.getUTCSeconds())+'Z'
}

module.exports = Feed;<|endoftext|>


<commit_before>const path = require('path');

const fs = require('fs-extra');
const jsonfile = require('jsonfile');
const gitRevSync = require('git-rev-sync');
const packageJson = require('package-json');
const deepReplace = require('./deep-replace');

jsonfile.spaces = 2;

const deepExtend = (obj1, obj2) => {
	let outputObj;

	const obj1Copy = JSON.parse(JSON.stringify(obj1));
	outputObj = obj1Copy;

	if (obj2) {
		const obj2Copy = JSON.parse(JSON.stringify(obj2));
		outputObj = Object.assign({}, obj1Copy, obj2Copy);
	}

	return outputObj;
};

const replace = (search, replace) => str => {
	return str.replace(new RegExp(search, 'g'), replace);
};

module.exports = (settings, ...deps) => {
	const [log, file] = deps;

	const cwd = process.cwd();

	const existsSync = url => {
		let exists;

		try {
			const stat = fs.statSync(url);
			if (stat.isFile()) {
				exists = true;
			}
		} catch (err) {
			exists = false;
		}

		return exists;
	};

	const dirExistsSync = path => {
		let exists;

		try {
			const stat = fs.statSync(path);
			if (stat.isDirectory()) {
				exists = true;
			}
		} catch (err) {
			exists = false;
		}

		return exists;
	};

	const loadJson = url => {
		// log(`'loadJson: ${url}`); // trace

		try {
			// eslint-disable-next-line
			const json = require(url);

			if (json) {
				return json;
			}
		} catch (err) {
			log.error(err, 'red');
		}

		return {};
	};

	const saveJson = (url, data) => {
		// log(`'saveJson: ${url}`); //trace

		try {
			jsonfile.writeFileSync(url, data);

			// log(`${url} updated`);
			return true;
		} catch (err) {
			log.info(`${log.ul(url)} NOT updated!`);

			log.info(err, 'red');

			return false;
		}
	};

	const getPkgJson = () => {
		log('getPkg');

		const pkgPath = path.join(cwd, 'package.json');
		const pkgJson = fs.readJson(pkgPath);

		return {data: pkgJson, path: pkgPath};
	};

	const getProject = roboRoot => {
		const jsonPath = path.join(roboRoot, settings.projectFile);
		const json = fs.readJsonSync(jsonPath);
		return json;
	};

	const saveProject = (roboRoot, projectData) => {
		const jsonPath = path.join(roboRoot, settings.projectFile);
		const json = fs.writeJsonSync(jsonPath, projectData);
		return json;
	};

	const breakoutRepoUrl = repo => {
		const result = {
			// commitish: null,
			// repo: null,
			// type: null
		};

		if (Reflect.has(repo, 'type')) {
			result.type = repo.type;
		}

		const commitish = repo.url.split('#')[1];
		if (commitish) {
			result.commitish = commitish;
		}

		const gitUrl = repo.url.replace('git+', '').split('#')[0];
		result.repo = gitUrl;

		return result;
	};

	const isHash = str => {
		const rxp = str.replace(new RegExp('[a-fA-F0-9]{40}', 'g'), '');
		console.log('.......................(ishash).........');
		console.log(rxp);
		return rxp === '';
	};

	const buildDepsTree = dependencyObject => new Promise((resolve, reject) => {
		const promises = [];

		const getVersion = dependency => new Promise((resolve, reject) => {
			packageJson(dependency, 'latest').then(json => {
				const result = {
					name: dependency,
					// Leave the nulls to so references exist in the json to
					// update branch, etc.
					branch: null,
					commit: null,
					version: null
				};

				if (Reflect.has(json, 'version')) {
					result.version = json.version;
				}

				if (Reflect.has(json, 'repository')) {
					const parts = breakoutRepoUrl(json.repository);

					if (typeof parts.commitish === 'string') {
						if (isHash(parts.commitish)) {
							result.commit = parts.commit;
						} else {
							result.branch = parts.commit;
						}
					}

					result.repository = parts.repo;
				}

				log.info(`'${log.ul(result.name)}' resolved via registry (NPM).`);
				resolve(result);
			}).catch(reject);
		});

		Reflect.ownKeys(dependencyObject).forEach(dependency => {
			// Only resolve dependencies that do not have attached info
			if (Reflect.ownKeys(dependencyObject[dependency]).length < 1) {
				promises.push(getVersion(dependency));
			}
		});

		Promise.all(promises).then(results => {
			const packages = {};

			results.forEach(result => {
				packages[result.name] = result;
			});

			resolve(packages);
		}).catch(reject);
	});

	const pluckCommand = (commands, keys) => {
		if (typeof keys === 'string') {
			return commands[keys];
		}

		if (!Array.isArray(keys)) {
			return false;
		}

		if (keys.length > 0) {
			const nextKey = keys.shift();
			const plucked = commands[nextKey];
			return pluckCommand(plucked, keys);
		}

		return commands;
	};

	// filter is an array of dependancies that remain
	const generateCommands = (roboRoot, commandType, filter, ...args) => {
		const project = getProject(roboRoot);

		const projectDeps = Reflect.ownKeys(project.dependencies);
		const commands = pluckCommand(project.commands, commandType);

		let dependencies = [];

		let filterMode = Array.isArray(filter);

		if (filterMode) {
			filter.forEach(depObj => {
				let dependency;

				if (typeof depObj === 'object') {
					if (!Reflect.has(depObj, 'dependency')) {
						throw new Error('FATAL: "dependency" MUST be defined in structured filter map.');
					}
					dependency = depObj.dependency;
				} else if (typeof depObj === 'string') {
					dependency = depObj;
				} else {
					throw new Error('Encountered an unknown type of dependency!');
				}

				dependencies.push(dependency);
			});
		} else if (!filterMode && filter !== 'roboroot') {
			dependencies = projectDeps;
		} else if (!filterMode && filter === 'roboroot') {
			dependencies = ['roboroot'];
		}

		// console.log(dependencies);

		const commandStack = [];

		dependencies.forEach((dependency, idx) => {
			commands.forEach(cmd => {
				const command = deepExtend(cmd); // copy out of json

				const robonautModsDir = path.join(roboRoot, settings.modulesDir);
				deepReplace(command, replace('{{robonaut}}', robonautModsDir + path.sep));

				let filterMap;

				if (filterMode) {
					filterMap = filter[idx];
				}

				if (filterMode && typeof filterMap === 'object') {
					// Special case for structured command maps (such as fuse)
					Reflect.ownKeys(filterMap).forEach(key => {
						deepReplace(command, replace(`{{${key}}}`, filterMap[key]));
					});
				} else {
					// The normal case for flat command maps
					deepReplace(command, replace('{{dependency}}', dependency));
				}

				const dependencyDir = path.join(robonautModsDir, dependency);

				if (filter === 'roboroot') {
					const branch = gitRevSync.branch(roboRoot);
					deepReplace(command, replace('{{branch}}', branch));
				} else if (dirExistsSync(dependencyDir)) {
					const branch = gitRevSync.branch(dependencyDir);
					deepReplace(command, replace('{{branch}}', branch));
				}

				deepReplace(command, replace('{{roboroot}}', roboRoot));

				if (filter !== 'roboroot') {
					const repository = project.dependencies[dependency].repository;
					deepReplace(command, replace('{{repository}}', repository));
				}

				const prog = command.command;
				const args = command.args;
				const opts = deepExtend(settings.defaults.processOptions, command.options);

				const spawnCommand = [prog, args, opts];

				commandStack.push(spawnCommand);
			});
		});

		// console.log(commandStack);
		// process.exit(0);

		return commandStack;
	};

	const updateProjectDepsJson = roboRoot => {
		const project = getProject(roboRoot);

		Reflect.ownKeys(project.dependencies).forEach(dependency => {
			// Update Git Things
			const dependencyDir = path.join(roboRoot, settings.modulesDir, dependency);
			const gitBranch = gitRevSync.branch(dependencyDir);
			const gitCommit = gitRevSync.long(dependencyDir);
			project.dependencies[dependency].branch = gitBranch;
			project.dependencies[dependency].commit = gitCommit;

			// Update Npm Things
			const depPkgJsonFile = path.join(dependencyDir, 'package.json');
			const depPkgJson = loadJson(depPkgJsonFile);
			const version = depPkgJson.version;
			project.dependencies[dependency].version = version;
		});

		saveProject(roboRoot, project);
		log.info(`Updated ${log.ul(settings.projectFile)}.`);
	};

	const exports = {
		removeSync: fs.removeSync,
		copySync: fs.copySync,

		cwd: cwd,

		buildDepsTree,
		deepExtend,
		generateCommands,
		existsSync,
		dirExistsSync,
		loadJson,
		saveJson,
		getProject,
		saveProject,
		getPkgJson,
		updateProjectDepsJson
	};

	return exports;
};
<commit_msg>assemble now in json branch.<commit_after>const path = require('path');

const fs = require('fs-extra');
const jsonfile = require('jsonfile');
const gitRevSync = require('git-rev-sync');
const packageJson = require('package-json');
const deepReplace = require('./deep-replace');

jsonfile.spaces = 2;

const deepExtend = (obj1, obj2) => {
	let outputObj;

	const obj1Copy = JSON.parse(JSON.stringify(obj1));
	outputObj = obj1Copy;

	if (obj2) {
		const obj2Copy = JSON.parse(JSON.stringify(obj2));
		outputObj = Object.assign({}, obj1Copy, obj2Copy);
	}

	return outputObj;
};

const replace = (search, replace) => str => {
	return str.replace(new RegExp(search, 'g'), replace);
};

module.exports = (settings, ...deps) => {
	const [log, file] = deps;

	const cwd = process.cwd();

	const existsSync = url => {
		let exists;

		try {
			const stat = fs.statSync(url);
			if (stat.isFile()) {
				exists = true;
			}
		} catch (err) {
			exists = false;
		}

		return exists;
	};

	const dirExistsSync = path => {
		let exists;

		try {
			const stat = fs.statSync(path);
			if (stat.isDirectory()) {
				exists = true;
			}
		} catch (err) {
			exists = false;
		}

		return exists;
	};

	const loadJson = url => {
		// log(`'loadJson: ${url}`); // trace

		try {
			// eslint-disable-next-line
			const json = require(url);

			if (json) {
				return json;
			}
		} catch (err) {
			log.error(err, 'red');
		}

		return {};
	};

	const saveJson = (url, data) => {
		// log(`'saveJson: ${url}`); //trace

		try {
			jsonfile.writeFileSync(url, data);

			// log(`${url} updated`);
			return true;
		} catch (err) {
			log.info(`${log.ul(url)} NOT updated!`);

			log.info(err, 'red');

			return false;
		}
	};

	const getPkgJson = () => {
		log('getPkg');

		const pkgPath = path.join(cwd, 'package.json');
		const pkgJson = fs.readJson(pkgPath);

		return {data: pkgJson, path: pkgPath};
	};

	const getProject = roboRoot => {
		const jsonPath = path.join(roboRoot, settings.projectFile);
		const json = fs.readJsonSync(jsonPath);
		return json;
	};

	const saveProject = (roboRoot, projectData) => {
		const jsonPath = path.join(roboRoot, settings.projectFile);
		const json = fs.writeJsonSync(jsonPath, projectData);
		return json;
	};

	const breakoutRepoUrl = repo => {
		const result = {
			// commitish: null,
			// repo: null,
			// type: null
		};

		if (Reflect.has(repo, 'type')) {
			result.type = repo.type;
		}

		const commitish = repo.url.split('#')[1];
		if (commitish) {
			result.commitish = commitish;
		}

		const gitUrl = repo.url.replace('git+', '').split('#')[0];
		result.repo = gitUrl;

		return result;
	};

	const isHash = str => {
		const rxp = str.replace(new RegExp('[a-fA-F0-9]{40}', 'g'), '');
		console.log('.......................(ishash).........');
		console.log(rxp);
		return rxp === '';
	};

	const buildDepsTree = dependencyObject => new Promise((resolve, reject) => {
		const promises = [];

		const getVersion = dependency => new Promise((resolve, reject) => {
			packageJson(dependency, 'latest').then(json => {
				const result = {
					name: dependency,
					// Leave the nulls to so references exist in the json to
					// update branch, etc.
					branch: null,
					commit: null,
					version: null
				};

				if (Reflect.has(json, 'version')) {
					result.version = json.version;
				}

				if (Reflect.has(json, 'repository')) {
					const parts = breakoutRepoUrl(json.repository);

					if (typeof parts.commitish === 'string') {
						if (isHash(parts.commitish)) {
							result.commit = parts.commit;
						} else {
							result.branch = parts.commit;
						}
					}

					result.repository = parts.repo;
				}

				log.info(`'${log.ul(result.name)}' resolved via registry (NPM).`);
				resolve(result);
			}).catch(reject);
		});

		Reflect.ownKeys(dependencyObject).forEach(dependency => {
			// Only resolve dependencies that do not have attached info
			if (Reflect.ownKeys(dependencyObject[dependency]).length < 1) {
				promises.push(getVersion(dependency));
			}
		});

		Promise.all(promises).then(results => {
			const packages = {};

			results.forEach(result => {
				packages[result.name] = result;
			});

			resolve(packages);
		}).catch(reject);
	});

	const pluckCommand = (commands, keys) => {
		if (typeof keys === 'string') {
			return commands[keys];
		}

		if (!Array.isArray(keys)) {
			return false;
		}

		if (keys.length > 0) {
			const nextKey = keys.shift();
			const plucked = commands[nextKey];
			return pluckCommand(plucked, keys);
		}

		return commands;
	};

	// filter is an array of dependancies that remain
	const generateCommands = (roboRoot, commandType, filter, ...args) => {
		const project = getProject(roboRoot);

		const projectDeps = Reflect.ownKeys(project.dependencies);
		const commands = pluckCommand(project.commands, commandType);

		let dependencies = [];

		let filterMode = Array.isArray(filter);

		if (filterMode) {
			filter.forEach(depObj => {
				let dependency;

				if (typeof depObj === 'object') {
					if (!Reflect.has(depObj, 'dependency')) {
						throw new Error('FATAL: "dependency" MUST be defined in structured filter map.');
					}
					dependency = depObj.dependency;
				} else if (typeof depObj === 'string') {
					dependency = depObj;
				} else {
					throw new Error('Encountered an unknown type of dependency!');
				}

				dependencies.push(dependency);
			});
		} else if (!filterMode && filter !== 'roboroot') {
			dependencies = projectDeps;
		} else if (!filterMode && filter === 'roboroot') {
			dependencies = ['roboroot'];
		}

		// console.log(dependencies);

		const commandStack = [];

		dependencies.forEach((dependency, idx) => {
			commands.forEach(cmd => {
				const command = deepExtend(cmd); // copy out of json

				const robonautModsDir = path.join(roboRoot, settings.modulesDir);
				deepReplace(command, replace('{{robonaut}}', robonautModsDir + path.sep));

				let filterMap;

				if (filterMode) {
					filterMap = filter[idx];
				}

				if (filterMode && typeof filterMap === 'object') {
					// Special case for structured command maps (such as fuse)
					Reflect.ownKeys(filterMap).forEach(key => {
						deepReplace(command, replace(`{{${key}}}`, filterMap[key]));
					});
				} else {
					// The normal case for flat command maps
					deepReplace(command, replace('{{dependency}}', dependency));
				}

				const dependencyDir = path.join(robonautModsDir, dependency);

				if (filter === 'roboroot') {
					const branch = gitRevSync.branch(roboRoot);
					deepReplace(command, replace('{{branch}}', branch));
				} else if (dirExistsSync(dependencyDir)) {
					const branch = gitRevSync.branch(dependencyDir);
					deepReplace(command, replace('{{branch}}', branch));
				}

				deepReplace(command, replace('{{roboroot}}', roboRoot));

				if (filter !== 'roboroot') {
					const repository = project.dependencies[dependency].repository;
					deepReplace(command, replace('{{repository}}', repository));

					const projectBranch = project.dependencies[dependency].branch;
					deepReplace(command, replace('{{projectBranch}}', projectBranch));
				}

				const prog = command.command;
				const args = command.args;
				const opts = deepExtend(settings.defaults.processOptions, command.options);

				const spawnCommand = [prog, args, opts];

				commandStack.push(spawnCommand);
			});
		});

		// console.log(commandStack);
		// process.exit(0);

		return commandStack;
	};

	const updateProjectDepsJson = roboRoot => {
		const project = getProject(roboRoot);

		Reflect.ownKeys(project.dependencies).forEach(dependency => {
			// Update Git Things
			const dependencyDir = path.join(roboRoot, settings.modulesDir, dependency);
			const gitBranch = gitRevSync.branch(dependencyDir);
			const gitCommit = gitRevSync.long(dependencyDir);
			project.dependencies[dependency].branch = gitBranch;
			project.dependencies[dependency].commit = gitCommit;

			// Update Npm Things
			const depPkgJsonFile = path.join(dependencyDir, 'package.json');
			const depPkgJson = loadJson(depPkgJsonFile);
			const version = depPkgJson.version;
			project.dependencies[dependency].version = version;
		});

		saveProject(roboRoot, project);
		log.info(`Updated ${log.ul(settings.projectFile)}.`);
	};

	const exports = {
		removeSync: fs.removeSync,
		copySync: fs.copySync,

		cwd: cwd,

		buildDepsTree,
		deepExtend,
		generateCommands,
		existsSync,
		dirExistsSync,
		loadJson,
		saveJson,
		getProject,
		saveProject,
		getPkgJson,
		updateProjectDepsJson
	};

	return exports;
};
<|endoftext|>


<commit_before>/**
 * The file cache class.
 *
 * @class Cache
 */
var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var watch = require("fwatcher");
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
//var removeFingerprint = /\.[^\.]+\.js$/;
var suffix = /\.\w+$/;
var replaceDoubleSlash = /\/{2,}/g;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;

// global file cache
var fileCache = engine.pojo('files');

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {string} The file path.
 * @param {function} The callback function.
 * @param {array} Give an array with allowed paths. (optional)
 * @param {string} A string, which is prepended to a path.
 */
var getFile = exports.get = function getFile (file, callback, index) {

    if (typeof file === 'string') {
        file = {path: file};
    }

    // path must be absolute
    if (!file || !file.path || file.path.indexOf('../') !== -1) {
        var err = new Error('Invalid path.');
        console.error('lib/file.js#51', err.toString());
        return callback(err);
    }

    // set default base path to public app folder
    file.base = file.base || engine.paths.app_public;

    // create a clean path without the fingerprint
    file.clean = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.clean).replace(replaceDoubleSlash, '/');

    // check if file is already in cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {
        return callback(null, cachedFile, index);
    }

    // check if path had fingerprint
    file.fpInPath = file.fpInPath || (file.clean === file.path ? false : true);

    // get file type
    file.type = mime.lookup(file.clean);

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            console.error('lib/file.js#83', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (cachedFile !== null) {
            watch(file.read, function (err) {
                if (err) {
                    return console.error(err);
                }

                // remove cached data on file change, to force reload
                console.log('FILE CHANGE:', file.read);

                // set file item to null
                fileCache.set(file.read, null);
            });
        }

        // minify js, html or css
        switch (file.type) {
            case 'text/html':

                // TODO don't compress as long html imports or binary ws data is available
                file.noCompression = true;
                data = minifyHtml(data.toString(), html_minifier_options);
                break;

            case 'text/css':
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                break;
            case 'application/javascript':

                // wrap javascript files, except the engine client
                if (file.clean !== 'engine.js') {
                    data = "E('" + ((file.wrap || '')  + file.clean).replace(replaceDoubleSlash, '/') + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
                }

                if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                    data = data.code;
                }
                break;
        }

        // create fingerprint
        file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

        // add the data to the file object
        file.data = data;

        // don't compress data
        if (file.noCompression) {
            return createCacheObject(file, callback, index);
        }

        zlib.gzip(file.data, function (err, data) {

            if (err) {
                console.error('lib/file.js#218', err.toString());
                return callback(err);
            }

            // update file data with compressed data
            file.data = data;

            createCacheObject(file, callback, index);
        });
    });
};

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (file, callback, index) {

    // get the modification time
    fs.stat(file.read, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // create http headers
        // TODO send not modified for non scrips and css
        file.http = {
            'Vary': 'Accept-Encoding',
            'Cache-Control': 'public, max-age=' + (file.fpInPath ? engine.http.maxAge_fingerprint : engine.http.maxAge),
            'Content-Encoding': 'gzip',
            'Content-Length': file.data.length,
            'Content-Type': file.type,
            'Last-Modified': stats.mtime,
            'Etag': file.fp
        };

        // TODO don't append fingerprints to the markup until it's loaded via html import
        if (file.type !== 'text/html') {

            // handle files with suffix
            if (suffix.test(file.clean)) {

                file.ext = file.path.split('.');

                // add public file identifier to path
                if (file.ext[0][0] === '/' && file.ext[0][1] !== '/') {
                    file.ext[0] = '/!' + file.ext[0];
                }

                file.ext.splice(file.ext.length - 1, 0, '@' + file.fp);
                file.ext = file.ext.join('.');

            // handle files without suffix
            } else {
                file.ext = file.path + '.' + '@' + file.fp;
            }
        }

        // add module id to module paths
        file.ext = (file.path[0] !== '/' ? file.wrap : '') + (file.ext || file.path);

        // save zipped data in cache
        fileCache.set(file.read, file);

        // return data
        callback(null, file, index);
    });
}

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepareComponents = function (loadContext, callback) {

    if (!loadContext || !loadContext.components) {
        var err = new Error('Invalid load context.');
        console.error('lib/file.js#227', err.toString());
        return callback(err);
    }

    // check if there are components to load
    if (
        (!loadContext.components.scripts || !loadContext.components.scripts.length) &&
        (!loadContext.components.markup || !loadContext.components.markup.length) &&
        (!loadContext.components.styles || !loadContext.components.styles.length)
    ) {
        return callback(null, loadContext);
    }

    var count = 0;

    // the prepend path for external module resource urls
    var modulePath = loadContext._id  + '/';

    var handler = function (err, file, index) {

        if (err) {
            console.error('lib/file.js#247', err.toString());
            if (--count === 0) {
                callback(null, loadContext);
            }
            return;
        }

        // update module components paths
        if (loadContext.components[file.compType] && loadContext.components[file.compType][index]) {
            loadContext.components[file.compType][index] = file.ext;
        }

        if (--count === 0) {
            callback(null, loadContext);
        }
    };

    for (var type in loadContext.components) {

        // check if type is allowed
        switch (type) {
            case 'scripts':
            case 'markup':
            case 'styles':
                break;
            default:
              continue;
        }

        // get file paths array
        var files = loadContext.components[type];

        // update count
        count += files.length;

        for (var i = 0, file; i < files.length; ++i) {

            // ignore external paths
            if (files[i].indexOf('//') > -1) {
                if (--count === 0) {
                    callback(null, loadContext);
                }
                continue;
            }

            // create file object
            file = {path: files[i]};

            // save component type to update the components later
            file.compType = type;

            // set a longer cache time for fingerprinted files
            file.fpInPath = true;

            // handle absolute files
            if (file.path[0] === '/') {
                file.base = type === 'markup' ? engine.paths.app_markup : engine.paths.app_public;

            // handle module files
            } else {
                file.base = loadContext._base;
                file.wrap = modulePath;
            }

            // read the file
            getFile(file, handler, i);
        }
    }
};

/**f
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // get fingerprint and remove fingerprint from read path
        path.splice(path.length - 2, 1)[0];

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<commit_msg>dont change serverside paths<commit_after>/**
 * The file cache class.
 *
 * @class Cache
 */
var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var watch = require("fwatcher");
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
//var removeFingerprint = /\.[^\.]+\.js$/;
var suffix = /\.\w+$/;
var replaceDoubleSlash = /\/{2,}/g;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;

// global file cache
var fileCache = engine.pojo('files');

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {string} The file path.
 * @param {function} The callback function.
 * @param {array} Give an array with allowed paths. (optional)
 * @param {string} A string, which is prepended to a path.
 */
var getFile = exports.get = function getFile (file, callback, index) {

    if (typeof file === 'string') {
        file = {path: file};
    }

    // path must be absolute
    if (!file || !file.path || file.path.indexOf('../') !== -1) {
        var err = new Error('Invalid path.');
        console.error('lib/file.js#51', err.toString());
        return callback(err);
    }

    // set default base path to public app folder
    file.base = file.base || engine.paths.app_public;

    // create a clean path without the fingerprint
    file.clean = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.clean).replace(replaceDoubleSlash, '/');

    // check if file is already in cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {
        return callback(null, cachedFile, index);
    }

    // check if path had fingerprint
    file.fpInPath = file.fpInPath || (file.clean === file.path ? false : true);

    // get file type
    file.type = mime.lookup(file.clean);

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            console.error('lib/file.js#83', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (cachedFile !== null) {
            watch(file.read, function (err) {
                if (err) {
                    return console.error(err);
                }

                // remove cached data on file change, to force reload
                console.log('FILE CHANGE:', file.read);

                // set file item to null
                fileCache.set(file.read, null);
            });
        }

        // minify js, html or css
        switch (file.type) {
            case 'text/html':

                // TODO don't compress as long html imports or binary ws data is available
                file.noCompression = true;
                data = minifyHtml(data.toString(), html_minifier_options);
                break;

            case 'text/css':
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                break;
            case 'application/javascript':

                // wrap javascript files, except the engine client
                if (file.clean !== 'engine.js') {
                    data = "E('" + ((file.wrap || '')  + file.clean).replace(replaceDoubleSlash, '/') + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
                }

                if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                    data = data.code;
                }
                break;
        }

        // create fingerprint
        file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

        // add the data to the file object
        file.data = data;

        // don't compress data
        if (file.noCompression) {
            return createCacheObject(file, callback, index);
        }

        zlib.gzip(file.data, function (err, data) {

            if (err) {
                console.error('lib/file.js#218', err.toString());
                return callback(err);
            }

            // update file data with compressed data
            file.data = data;

            createCacheObject(file, callback, index);
        });
    });
};

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (file, callback, index) {

    // get the modification time
    fs.stat(file.read, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // create http headers
        // TODO send not modified for non scrips and css
        file.http = {
            'Vary': 'Accept-Encoding',
            'Cache-Control': 'public, max-age=' + (file.fpInPath ? engine.http.maxAge_fingerprint : engine.http.maxAge),
            'Content-Encoding': 'gzip',
            'Content-Length': file.data.length,
            'Content-Type': file.type,
            'Last-Modified': stats.mtime,
            'Etag': file.fp
        };

        // TODO don't append fingerprints to the markup until it's loaded via html import
        if (file.type !== 'text/html') {

            // handle files with suffix
            if (suffix.test(file.clean)) {

                file.ext = file.path.split('.');

                file.ext.splice(file.ext.length - 1, 0, '@' + file.fp);
                file.ext = file.ext.join('.');

            // handle files without suffix
            } else {
                file.ext = file.path + '.' + '@' + file.fp;
            }
        }

        // add module id to module paths
        file.ext = (file.path[0] !== '/' ? file.wrap : '') + (file.ext || file.path);

        // save zipped data in cache
        fileCache.set(file.read, file);

        // return data
        callback(null, file, index);
    });
}

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepareComponents = function (loadContext, callback) {

    if (!loadContext || !loadContext.components) {
        var err = new Error('Invalid load context.');
        console.error('lib/file.js#227', err.toString());
        return callback(err);
    }

    // check if there are components to load
    if (
        (!loadContext.components.scripts || !loadContext.components.scripts.length) &&
        (!loadContext.components.markup || !loadContext.components.markup.length) &&
        (!loadContext.components.styles || !loadContext.components.styles.length)
    ) {
        return callback(null, loadContext);
    }

    var count = 0;

    // the prepend path for external module resource urls
    var modulePath = loadContext._id  + '/';

    var handler = function (err, file, index) {

        if (err) {
            console.error('lib/file.js#247', err.toString());
            if (--count === 0) {
                callback(null, loadContext);
            }
            return;
        }

        // update module components paths
        if (loadContext.components[file.compType] && loadContext.components[file.compType][index]) {
            loadContext.components[file.compType][index] = file.ext;
        }

        if (--count === 0) {
            callback(null, loadContext);
        }
    };

    for (var type in loadContext.components) {

        // check if type is allowed
        switch (type) {
            case 'scripts':
            case 'markup':
            case 'styles':
                break;
            default:
              continue;
        }

        // get file paths array
        var files = loadContext.components[type];

        // update count
        count += files.length;

        for (var i = 0, file; i < files.length; ++i) {

            // ignore external paths
            if (files[i].indexOf('//') > -1) {
                if (--count === 0) {
                    callback(null, loadContext);
                }
                continue;
            }

            // create file object
            file = {path: files[i]};

            // save component type to update the components later
            file.compType = type;

            // set a longer cache time for fingerprinted files
            file.fpInPath = true;

            // handle absolute files
            if (file.path[0] === '/') {
                file.base = type === 'markup' ? engine.paths.app_markup : engine.paths.app_public;

            // handle module files
            } else {
                file.base = loadContext._base;
                file.wrap = modulePath;
            }

            // read the file
            getFile(file, handler, i);
        }
    }
};

/**f
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // get fingerprint and remove fingerprint from read path
        path.splice(path.length - 2, 1)[0];

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<|endoftext|>


<commit_before>var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};

// The Flow class
// ==============

// Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
// subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex

var Duplex  = require('stream').Duplex;

exports.Flow = Flow;

// Public API
// ----------

// * **Event: 'error' (type)**: signals an error
//
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
//   ([as described in the standard][1]) using this method
//
// * **disableRemoteFlowControl()**: sends a WINDOW_UPDATE signaling that we don't want flow control
//
// * **disableLocalFlowControl()**: disables flow control for outgoing frames
//
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2

// API for child classes
// ---------------------

// * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
//   with the given `flowControlId` (or every update frame if not given)
//
// * **Event: 'receiving' (frame)**: there's an incoming frame
//
// * **Event: 'sending' (frame)**: a frame was added to the output queue
//
// * **_send()**: called when more frames should be pushed. The child class is expected to override
//   this (instead of the `_read` method of the Duplex class).
//
// * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is
//   expected to override this (instead of the `_write` method of the Duplex class).
//
// * **push(frame): bool**: schedules `frame` for sending.
//
//   Returns `true` if it needs more frames in the output queue, `false` if the output queue is
//   full, and `null` if did not push the frame into the output queue (instead, it pushed it into
//   the flow control queue).
//
// * **_push(frame, remainderCallback): bool**: like push, but never puts the frame into the
//   flow control queue.
//
//   Instead, it pushes directly into the output queue if possible (according to the flow control
//   window) and calls `remainderCallback` with the remaining non-pushable part of the frame. It is
//   capable of dividing DATA frames into multiple chunks.
//
//   Use this instead of `push` if you always want to have empty flow control queue (but never mix
//   the two).
//
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
//
// * **_log**: the Flow class uses the `_log` object of the parent

// Constructor
// -----------

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched.
function Flow(flowControlId) {
  Duplex.call(this, { objectMode: true });

  this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
  this._flowControlId = flowControlId;
  this._queue = [];

  this._ended = false;
  this._received = 0;
  this._remoteFlowControlDisabled = true;
}
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });

// Incoming frames
// ---------------

// `_receive` is called when there's an incoming frame.
Flow.prototype._receive = function _receive(frame, callback) {
  throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
};

// `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
// to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
// incoming frame is a WINDOW_UPDATE.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._write = function _write(frame, encoding, callback) {
  this.emit('receiving', frame);

  if (frame.flags.END_STREAM) {
    this._ended = true;
  }

  if ((frame.type === 'DATA') && (frame.data.length > 0) && !this._remoteFlowControlDisabled) {
    this._receive(frame, function() {
      this._received += frame.data.length;
      if (!this._restoreWindowTimer) {
        this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
      }
      callback();
    }.bind(this));
  }

  else {
    this._receive(frame, callback);
  }

  if ((frame.type === 'WINDOW_UPDATE') &&
      ((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
    this._updateWindow(frame);
  }
};

// `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
// a WINDOW_UPDATE that restores the flow control window of the remote end.
Flow.prototype._restoreWindow = function _restoreWindow() {
  delete this._restoreWindowTimer;
  if (!this._ended && !this._remoteFlowControlDisabled && (this._received > 0)) {
    this.push({
      type: 'WINDOW_UPDATE',
      stream: this._flowControlId,
      window_size: this._received
    });
    this._received = 0;
  }
};

// Remote flow control is currently disabled by default, but in the future, it may be turned off
// using the `disableRemoteFlowControl` method.
Flow.prototype.disableRemoteFlowControl = function disableRemoteFlowControl() {
  this._remoteFlowControlDisabled = true;
  this.push({
    type: 'WINDOW_UPDATE',
    stream: this._flowControlId,
    flags: {
      END_FLOW_CONTROL: true
    }
  });
};

// Outgoing frames - sending procedure
// -----------------------------------

//                                         flow
//                +----------------------------------------------------+
//                |                                                    |
//                +--------+  _onWindow   +---------+                  |
//        read()  | output |  Increase()  | flow    |  _send()         |
//     <----------|        |<-------------| control |<-------------    |
//                | buffer |              | buffer  |                  |
//                +--------+              +---------+                  |
//                | input  |                                           |
//     ---------->|        |-------------------------------------->    |
//       write()  | buffer |  _write()                 _receive()      |
//                +--------+                                           |
//                |                                                    |
//                +----------------------------------------------------+

// `_send` is called when more frames should be pushed to the output buffer.
Flow.prototype._send = function _send() {
  throw new Error('The _send() method has to be overridden by the child class!');
};

// `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
// items in the output queue. It first check the flow control `_queue` and only calls `_send` if
// there are no items in it (which means that we are not waiting for window update).
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._read = function _read() {
  if (this._queue.length === 0) {
    this._send();
  } else {
    assert(this._window === 0);
    assert(this._queue[0].type === 'DATA');
  }
  this._readableState.reading = false;
};

// `_onWindowIncrease` is called when window size increases which means that the peer is ready to
// receive more data. Flushes frames stored in the flow control queue and then triggers a further
// `_read` call if the output buffers are still low on frames.
Flow.prototype._onWindowIncrease = function _onWindowIncrease() {
  var moreNeeded = true, frame;

  var unshiftRemainder = this._queue.unshift.bind(this._queue);
  while (moreNeeded && (frame = this._queue.shift())) {
    moreNeeded = this._push(frame, unshiftRemainder);
  }

  this.read(0); // See http://nodejs.org/api/stream.html#stream_stream_read_0
};

// `_push(frame)` is the low-level version of `push`. Use this instead of `push` if you always want
// to have empty flow control queue (but never mix the two). It pushes `frame` into the output queue
// and decreases the flow control window size. It is capable of splitting DATA frames into smaller
// parts, if the window size is not enough to push the whole frame. It calls `remainderCallback`
// synchronously before returning with the frame it was not able to push to the output queue. The
// remainder may be the whole frame or the remaining part of a DATA frame. The return value is
// similar to `push` except that it returns `null` if it did not push anything to the output queue.

var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383

Flow.prototype._push = function _push(frame, remainderCallback) {
  do {
    var forwardable = undefined, remainder = undefined;
    if ((frame === null) || (frame.type !== 'DATA') ||
        ((frame.data.length <= this._window) && (frame.data.length <= MAX_PAYLOAD_SIZE))) {
      forwardable = frame;
    }

    else if (this._window <= 0) {
      remainder = frame;
    }

    else {
      var chunkSize = Math.min(this._window, MAX_PAYLOAD_SIZE);
      forwardable = {
        stream: frame.stream,
        type: 'DATA',
        data: frame.data.slice(0, chunkSize)
      };

      this._log.trace({ frame: frame, size: frame.data.length, forwardable: chunkSize },
                      'Splitting out forwardable part of a DATA frame.');
      frame.data = frame.data.slice(chunkSize);
      remainder = frame;
    }

    var moreNeeded = null;
    if (forwardable !== undefined) {
      this._log.trace({ frame: forwardable }, 'Pushing frame into the output queue');
      if (forwardable && (forwardable.type === 'DATA') && (this._window !== Infinity)) {
        this._log.trace({ window: this._window, by: forwardable.data.length },
                         'Decreasing flow control window size.');
        this._window -= forwardable.data.length;
        assert(this._window >= 0);
      }
      moreNeeded = Duplex.prototype.push.call(this, forwardable);
    }

    frame = remainder;
  } while (remainder && moreNeeded);

  if (remainder !== undefined) {
    remainderCallback(remainder);
  }

  return moreNeeded;
};

// Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
Flow.prototype.push = function push(frame) {
  if (frame === null) {
    this._log.debug('Enqueueing outgoing End Of Stream');
  } else {
    frame.flags = frame.flags || {};
    this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
    this.emit('sending', frame);
  }

  if (this._queue.length === 0) {
    return this._push(frame, this._queue.push.bind(this._queue));
  } else {
    this._queue.push(frame);
    return null;
  }
};

// `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the
// [Stream](stream.html) class to mark the last frame with END_STREAM flag.
Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() {
  var readableQueue = this._readableState.buffer;
  return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1];
};

// Outgoing frames - managing the window size
// ------------------------------------------

// Flow control window size is manipulated using the `_increaseWindow` method.
//
// * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled
//   again once disabled. Any attempt to re-enable flow control MUST be rejected with a
//   FLOW_CONTROL_ERROR error code.
// * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
//   depends on it being a stream or the connection itself.

var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;

Flow.prototype._increaseWindow = function _increaseWindow(size) {
  if ((this._window === Infinity) && (size !== Infinity)) {
    this._log.error('Trying to increase flow control window after flow control was turned off.');
    this.emit('error', 'FLOW_CONTROL_ERROR');
  } else {
    this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
    this._window += size;
    if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
      this._log.error('Flow control window grew too large.');
      this.emit('error', 'FLOW_CONTROL_ERROR');
    } else {
      this._onWindowIncrease();
    }
  }
};

// The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
// modifies the flow control window:
//
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
//   END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
//   flag set is ignored.
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
//   specified in the frame.
Flow.prototype._updateWindow = function _updateWindow(frame) {
  this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size);
};

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
// calling the `setInitialWindow` method. The window size has to be modified by the difference
// between the new value and the old value.
Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) {
  this._increaseWindow(initialWindow - this._initialWindow);
  this._initialWindow = initialWindow;
};

// Flow control for outgoing frames can be disabled by the peer with various methods.
Flow.prototype.disableLocalFlowControl = function disableLocalFlowControl() {
  this._increaseWindow(Infinity);
};
<commit_msg>Flow: simpler data flow on window update.<commit_after>var assert = process.env.HTTP2_ASSERT ? require('assert') : function noop() {};

// The Flow class
// ==============

// Flow is a [Duplex stream][1] subclass which implements HTTP/2 flow control. It is designed to be
// subclassed by [Connection](connection.html) and the `upstream` component of [Stream](stream.html).
// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex

var Duplex  = require('stream').Duplex;

exports.Flow = Flow;

// Public API
// ----------

// * **Event: 'error' (type)**: signals an error
//
// * **setInitialWindow(size)**: the initial flow control window size can be changed *any time*
//   ([as described in the standard][1]) using this method
//
// * **disableRemoteFlowControl()**: sends a WINDOW_UPDATE signaling that we don't want flow control
//
// * **disableLocalFlowControl()**: disables flow control for outgoing frames
//
// [1]: http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-6.9.2

// API for child classes
// ---------------------

// * **new Flow([flowControlId])**: creating a new flow that will listen for WINDOW_UPDATES frames
//   with the given `flowControlId` (or every update frame if not given)
//
// * **Event: 'receiving' (frame)**: there's an incoming frame
//
// * **Event: 'sending' (frame)**: a frame was added to the output queue
//
// * **_send()**: called when more frames should be pushed. The child class is expected to override
//   this (instead of the `_read` method of the Duplex class).
//
// * **_receive(frame, readyCallback)**: called when there's an incoming frame. The child class is
//   expected to override this (instead of the `_write` method of the Duplex class).
//
// * **push(frame): bool**: schedules `frame` for sending.
//
//   Returns `true` if it needs more frames in the output queue, `false` if the output queue is
//   full, and `null` if did not push the frame into the output queue (instead, it pushed it into
//   the flow control queue).
//
// * **_push(frame, remainderCallback): bool**: like push, but never puts the frame into the
//   flow control queue.
//
//   Instead, it pushes directly into the output queue if possible (according to the flow control
//   window) and calls `remainderCallback` with the remaining non-pushable part of the frame. It is
//   capable of dividing DATA frames into multiple chunks.
//
//   Use this instead of `push` if you always want to have empty flow control queue (but never mix
//   the two).
//
// * **getLastQueuedFrame(): frame**: returns the last frame in output buffers
//
// * **_log**: the Flow class uses the `_log` object of the parent

// Constructor
// -----------

// When a HTTP/2.0 connection is first established, new streams are created with an initial flow
// control window size of 65535 bytes.
var INITIAL_WINDOW_SIZE = 65535;

// `flowControlId` is needed if only specific WINDOW_UPDATEs should be watched.
function Flow(flowControlId) {
  Duplex.call(this, { objectMode: true });

  this._window = this._initialWindow = INITIAL_WINDOW_SIZE;
  this._flowControlId = flowControlId;
  this._queue = [];

  this._ended = false;
  this._received = 0;
  this._remoteFlowControlDisabled = true;
}
Flow.prototype = Object.create(Duplex.prototype, { constructor: { value: Flow } });

// Incoming frames
// ---------------

// `_receive` is called when there's an incoming frame.
Flow.prototype._receive = function _receive(frame, callback) {
  throw new Error('The _receive(frame, callback) method has to be overridden by the child class!');
};

// `_receive` is called by `_write` which in turn is [called by Duplex][1] when someone `write()`s
// to the flow. It emits the 'receiving' event and notifies the window size tracking code if the
// incoming frame is a WINDOW_UPDATE.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._write = function _write(frame, encoding, callback) {
  this.emit('receiving', frame);

  if (frame.flags.END_STREAM) {
    this._ended = true;
  }

  if ((frame.type === 'DATA') && (frame.data.length > 0) && !this._remoteFlowControlDisabled) {
    this._receive(frame, function() {
      this._received += frame.data.length;
      if (!this._restoreWindowTimer) {
        this._restoreWindowTimer = setImmediate(this._restoreWindow.bind(this));
      }
      callback();
    }.bind(this));
  }

  else {
    this._receive(frame, callback);
  }

  if ((frame.type === 'WINDOW_UPDATE') &&
      ((this._flowControlId === undefined) || (frame.stream === this._flowControlId))) {
    this._updateWindow(frame);
  }
};

// `_restoreWindow` basically acknowledges the DATA frames received since it's last call. It sends
// a WINDOW_UPDATE that restores the flow control window of the remote end.
Flow.prototype._restoreWindow = function _restoreWindow() {
  delete this._restoreWindowTimer;
  if (!this._ended && !this._remoteFlowControlDisabled && (this._received > 0)) {
    this.push({
      type: 'WINDOW_UPDATE',
      stream: this._flowControlId,
      window_size: this._received
    });
    this._received = 0;
  }
};

// Remote flow control is currently disabled by default, but in the future, it may be turned off
// using the `disableRemoteFlowControl` method.
Flow.prototype.disableRemoteFlowControl = function disableRemoteFlowControl() {
  this._remoteFlowControlDisabled = true;
  this.push({
    type: 'WINDOW_UPDATE',
    stream: this._flowControlId,
    flags: {
      END_FLOW_CONTROL: true
    }
  });
};

// Outgoing frames - sending procedure
// -----------------------------------

//                                         flow
//                +-------------------------------------------------+
//                |                                                 |
//                +--------+           +---------+                  |
//        read()  | output |  _read()  | flow    |  _send()         |
//     <----------|        |<----------| control |<-------------    |
//                | buffer |           | buffer  |                  |
//                +--------+           +---------+                  |
//                | input  |                                        |
//     ---------->|        |----------------------------------->    |
//       write()  | buffer |  _write()              _receive()      |
//                +--------+                                        |
//                |                                                 |
//                +-------------------------------------------------+

// `_send` is called when more frames should be pushed to the output buffer.
Flow.prototype._send = function _send() {
  throw new Error('The _send() method has to be overridden by the child class!');
};

// `_send` is called by `_read` which is in turn [called by Duplex][1] when it wants to have more
// items in the output queue.
// [1]: http://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback_1
Flow.prototype._read = function _read() {
  // * if the flow control queue is empty, then let the user push more frames
  if (this._queue.length === 0) {
    this._send();
  }

  // * if there are items in the flow control queue, then let's put them into the output queue (to
  //   the extent it is possible with respect to the window size and output queue feedback)
  else if (this._window > 0) {
    var frame;
    var moreNeeded = true;
    var unshiftRemainder = this._queue.unshift.bind(this._queue);
    while (moreNeeded && (frame = this._queue.shift())) {
      moreNeeded = this._push(frame, unshiftRemainder);
    }

    assert((moreNeeded == false) ||                                     // output queue is full
           (this._queue.length === 0) ||                                // flow control queue is empty
           ((this._window === 0) && (this._queue[0].type === 'DATA'))); // waiting for window update
  }

  this._readableState.reading = false;
};

// `_push(frame)` is the low-level version of `push`. Use this instead of `push` if you always want
// to have empty flow control queue (but never mix the two). It pushes `frame` into the output queue
// and decreases the flow control window size. It is capable of splitting DATA frames into smaller
// parts, if the window size is not enough to push the whole frame. It calls `remainderCallback`
// synchronously before returning with the frame it was not able to push to the output queue. The
// remainder may be the whole frame or the remaining part of a DATA frame. The return value is
// similar to `push` except that it returns `null` if it did not push anything to the output queue.

var MAX_PAYLOAD_SIZE = 4096; // Must not be greater than MAX_HTTP_PAYLOAD_SIZE which is 16383

Flow.prototype._push = function _push(frame, remainderCallback) {
  do {
    var forwardable = undefined, remainder = undefined;
    if ((frame === null) || (frame.type !== 'DATA') ||
        ((frame.data.length <= this._window) && (frame.data.length <= MAX_PAYLOAD_SIZE))) {
      forwardable = frame;
    }

    else if (this._window <= 0) {
      remainder = frame;
    }

    else {
      var chunkSize = Math.min(this._window, MAX_PAYLOAD_SIZE);
      forwardable = {
        stream: frame.stream,
        type: 'DATA',
        data: frame.data.slice(0, chunkSize)
      };

      this._log.trace({ frame: frame, size: frame.data.length, forwardable: chunkSize },
                      'Splitting out forwardable part of a DATA frame.');
      frame.data = frame.data.slice(chunkSize);
      remainder = frame;
    }

    var moreNeeded = null;
    if (forwardable !== undefined) {
      this._log.trace({ frame: forwardable }, 'Pushing frame into the output queue');
      if (forwardable && (forwardable.type === 'DATA') && (this._window !== Infinity)) {
        this._log.trace({ window: this._window, by: forwardable.data.length },
                         'Decreasing flow control window size.');
        this._window -= forwardable.data.length;
        assert(this._window >= 0);
      }
      moreNeeded = Duplex.prototype.push.call(this, forwardable);
    }

    frame = remainder;
  } while (remainder && moreNeeded);

  if (remainder !== undefined) {
    remainderCallback(remainder);
  }

  return moreNeeded;
};

// Push `frame` into the flow control queue, or if it's empty, then directly into the output queue
Flow.prototype.push = function push(frame) {
  if (frame === null) {
    this._log.debug('Enqueueing outgoing End Of Stream');
  } else {
    frame.flags = frame.flags || {};
    this._log.debug({ frame: frame }, 'Enqueueing outgoing frame');
    this.emit('sending', frame);
  }

  if (this._queue.length === 0) {
    return this._push(frame, this._queue.push.bind(this._queue));
  } else {
    this._queue.push(frame);
    return null;
  }
};

// `getLastQueuedFrame` returns the last frame in output buffers. This is primarily used by the
// [Stream](stream.html) class to mark the last frame with END_STREAM flag.
Flow.prototype.getLastQueuedFrame = function getLastQueuedFrame() {
  var readableQueue = this._readableState.buffer;
  return this._queue[this._queue.length - 1] || readableQueue[readableQueue.length - 1];
};

// Outgoing frames - managing the window size
// ------------------------------------------

// Flow control window size is manipulated using the `_increaseWindow` method.
//
// * Invoking it with `Infinite` means turning off flow control. Flow control cannot be enabled
//   again once disabled. Any attempt to re-enable flow control MUST be rejected with a
//   FLOW_CONTROL_ERROR error code.
// * A sender MUST NOT allow a flow control window to exceed 2^31 - 1 bytes. The action taken
//   depends on it being a stream or the connection itself.

var WINDOW_SIZE_LIMIT = Math.pow(2, 31) - 1;

Flow.prototype._increaseWindow = function _increaseWindow(size) {
  if ((this._window === Infinity) && (size !== Infinity)) {
    this._log.error('Trying to increase flow control window after flow control was turned off.');
    this.emit('error', 'FLOW_CONTROL_ERROR');
  } else {
    this._log.trace({ window: this._window, by: size }, 'Increasing flow control window size.');
    this._window += size;
    if ((this._window !== Infinity) && (this._window > WINDOW_SIZE_LIMIT)) {
      this._log.error('Flow control window grew too large.');
      this.emit('error', 'FLOW_CONTROL_ERROR');
    } else {
      this.read(0);
    }
  }
};

// The `_updateWindow` method gets called every time there's an incoming WINDOW_UPDATE frame. It
// modifies the flow control window:
//
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
//   END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
//   flag set is ignored.
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
//   specified in the frame.
Flow.prototype._updateWindow = function _updateWindow(frame) {
  this._increaseWindow(frame.flags.END_FLOW_CONTROL ? Infinity : frame.window_size);
};

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
// calling the `setInitialWindow` method. The window size has to be modified by the difference
// between the new value and the old value.
Flow.prototype.setInitialWindow = function setInitialWindow(initialWindow) {
  this._increaseWindow(initialWindow - this._initialWindow);
  this._initialWindow = initialWindow;
};

// Flow control for outgoing frames can be disabled by the peer with various methods.
Flow.prototype.disableLocalFlowControl = function disableLocalFlowControl() {
  this._increaseWindow(Infinity);
};
<|endoftext|>