<commit_before>/*!
 * Copyright 2012-2014 Mattias Fliesberg
 *
 * This file is part of opmuse.
 *
 * opmuse is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * opmuse is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with opmuse.  If not, see <http://www.gnu.org/licenses/>.
 */

define(['jquery', 'inheritance', 'bootstrap/popover', 'layout', 'domReady!'],
    function ($, inheritance, popover, layout) {

    'use strict';

    var Throb = Class.extend({
        init: function (brand) {
            var that = this;

            this.brand = brand;

            // animation duration as set in throbber.less
            // we want to always run one full cycle of the animation and never
            // stop in the middle of it...
            this.animationDuration = 700;

            this.active = 0;

            $(document).ajaxSend(function () {
                if (that.active == 0) {
                    that.start();
                }

                that.active++;
            });

            $(document).ajaxComplete(function () {
                that.active--;

                var sinceStart = new Date().getTime() - that.startTime;

                var timeout = null;

                if (sinceStart <= that.animationDuration) {
                    timeout = that.animationDuration - sinceStart;
                } else {
                    timeout = that.animationDuration * Math.ceil(sinceStart / that.animationDuration) - sinceStart;
                }

                setTimeout(function () {
                    if (that.active == 0) {
                        that.stop();
                    }
                }, timeout);
            });
        },
        stop: function () {
            $(this.brand).removeClass('throb');
        },
        start: function () {
            this.startTime = new Date().getTime();

            $(this.brand).addClass('throb');
        },
        setError: function (msg) {
            $(this.brand).addClass('error').popover({
                content: $('<p>').text(msg).addClass('text-danger'),
                html: true,
                trigger: 'hover',
                container: 'body',
                placement: 'right bottom'
            });
        },
        unsetError: function () {
            $(this.brand).removeClass('error').popover('destroy');
        }
    });

    var instance = null;

    /**
     * This one listens to click events and loads the href in the content div
     * instead of reloading the full page.
     */
    var Ajaxify = Class.extend({
        init: function () {
            if (instance !== null) {
                throw Error('Only one instance of Ajaxify allowed!');
            }

            var that = this;

            this.throb = new Throb('.navbar-brand');

            this.contents = ['#main', '#navbar-sub', '#navbar-main', '#spacer'];

            if ($(this.contents[0]).length == 0) {
                return;
            }

            this.selector = '#top, #queue, #main, #spacer';

            this.initialURL = document.location.href;

            this.activeRequest = null;

            // setTimeout hack to ignore initial popstate that at least chrome fires,
            // but not firefox
            setTimeout(function () {
                $(window).on('popstate', function (event) {
                    var href = document.location.pathname + document.location.search;

                    if (that.activeRequest !== null) {
                        that.activeRequest.abort();
                    }

                    if (href !== null) {
                        that.loadPage(href);
                    }
                });
            });

            this.load(this.selector, false);

            $(document).on('click.ajaxify', 'a',
                function (event) {

                    var ajaxify = $(this).data('ajaxify');

                    if (typeof ajaxify != 'undefined' && ajaxify !== null && ajaxify === false) {
                        return;
                    }

                    var href = $(this).attr('href');

                    if (!event.ctrlKey && that.isRelative(href)) {
                        that.setPage(href);
                    } else {
                        // open external and ctrl-click in new window/tab
                        window.open(href, '_blank');
                    }

                    $(this).trigger('ajaxifyClick');

                    // continue propagation if the link has said attribute
                    return $(this).is('[data-ajaxify=continue]');
                }
            );
        },
        load: function (element, trigger) {
            var that = this;

            if (typeof trigger == 'undefined' || trigger === null) {
                trigger = true;
            }

            if (trigger) {
                $(this.selector).trigger('ajaxifyInit');
            }
        },
        loadPage: function (href) {
            var that = this;
            var contents = that.contents.join(',');

            layout.showOverlay();

            this.activeRequest = $.ajax(href, {
                success: function (data, textStatus, xhr) {
                    if (typeof data != 'undefined' && data !== null && data !== '') {
                        that.setPageInDom(data);
                    }

                    layout.hideOverlay();
                    that.activeRequest = null;
                },
                error: function (xhr) {
                    if (xhr.statusText != 'abort') {
                        that.setPageInDom(xhr.responseText);
                    }

                    layout.hideOverlay();
                    that.activeRequest = null;
                }
            });
        },
        setPageInDom: function (data) {
            var html = $($.parseHTML(data));

            document.title = $.trim(html.find('#title').text());

            var notFound = 0;

            for (var index in this.contents) {
                var content = this.contents[index];
                var newContent = html.find(content);

                if (newContent.length === 0) {
                    notFound += 1;
                    continue;
                }

                this.setInDom(content, newContent);
            }

            $(window).scrollTop(0);

            // if none of the contents are found we just replace the whole
            // document, this happens when there's a standard cherrypy error
            if (notFound === this.contents.length) {
                var newDoc = document.open('text/html', 'replace');
                newDoc.write(data);
                newDoc.close();
            }

            this.load(this.selector);
        },
        setInDom: function (content, newContent) {
            $(content).empty();

            if ($(newContent).length == 0) {
                return;
            }

            $(content)
                .append($(newContent).contents());

            this.fixAttributes(newContent, content);
        },
        fixAttributes: function (newContent, content) {
            // TODO adds attributes but doesn't remove no longer existing attributes
            $.each($(newContent).get(0).attributes, function (index, attribute) {
                $(content).attr(attribute.name, attribute.value);
            });
        },
        getPage: function (href) {
            if (!this.isRelative(href)) {
                href = this.getPathComponent(href);
            }

            return href;
        },
        setPage: function (href) {
            href = this.getPage(href);

            history.pushState({}, '', href);

            this.loadPage(href);
        },
        getPathComponent: function (href) {
            return href.match(/^http(s)?:\/\/[^\/]+(.*)/)[2];
        },
        isRelative: function (href) {
            return !/^http(s)?:\/\//.test(href);
        }
    });

    return (function () {
        if (instance === null) {
            instance = new Ajaxify();
        }

        return instance;
    })();
});
<commit_msg>fix so text/plain errors get rendered properly<commit_after>/*!
 * Copyright 2012-2014 Mattias Fliesberg
 *
 * This file is part of opmuse.
 *
 * opmuse is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * opmuse is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with opmuse.  If not, see <http://www.gnu.org/licenses/>.
 */

define(['jquery', 'inheritance', 'bootstrap/popover', 'layout', 'domReady!'],
    function ($, inheritance, popover, layout) {

    'use strict';

    var Throb = Class.extend({
        init: function (brand) {
            var that = this;

            this.brand = brand;

            // animation duration as set in throbber.less
            // we want to always run one full cycle of the animation and never
            // stop in the middle of it...
            this.animationDuration = 700;

            this.active = 0;

            $(document).ajaxSend(function () {
                if (that.active == 0) {
                    that.start();
                }

                that.active++;
            });

            $(document).ajaxComplete(function () {
                that.active--;

                var sinceStart = new Date().getTime() - that.startTime;

                var timeout = null;

                if (sinceStart <= that.animationDuration) {
                    timeout = that.animationDuration - sinceStart;
                } else {
                    timeout = that.animationDuration * Math.ceil(sinceStart / that.animationDuration) - sinceStart;
                }

                setTimeout(function () {
                    if (that.active == 0) {
                        that.stop();
                    }
                }, timeout);
            });
        },
        stop: function () {
            $(this.brand).removeClass('throb');
        },
        start: function () {
            this.startTime = new Date().getTime();

            $(this.brand).addClass('throb');
        },
        setError: function (msg) {
            $(this.brand).addClass('error').popover({
                content: $('<p>').text(msg).addClass('text-danger'),
                html: true,
                trigger: 'hover',
                container: 'body',
                placement: 'right bottom'
            });
        },
        unsetError: function () {
            $(this.brand).removeClass('error').popover('destroy');
        }
    });

    var instance = null;

    /**
     * This one listens to click events and loads the href in the content div
     * instead of reloading the full page.
     */
    var Ajaxify = Class.extend({
        init: function () {
            if (instance !== null) {
                throw Error('Only one instance of Ajaxify allowed!');
            }

            var that = this;

            this.throb = new Throb('.navbar-brand');

            this.contents = ['#main', '#navbar-sub', '#navbar-main', '#spacer'];

            if ($(this.contents[0]).length == 0) {
                return;
            }

            this.selector = '#top, #queue, #main, #spacer';

            this.initialURL = document.location.href;

            this.activeRequest = null;

            // setTimeout hack to ignore initial popstate that at least chrome fires,
            // but not firefox
            setTimeout(function () {
                $(window).on('popstate', function (event) {
                    var href = document.location.pathname + document.location.search;

                    if (that.activeRequest !== null) {
                        that.activeRequest.abort();
                    }

                    if (href !== null) {
                        that.loadPage(href);
                    }
                });
            });

            this.load(this.selector, false);

            $(document).on('click.ajaxify', 'a',
                function (event) {

                    var ajaxify = $(this).data('ajaxify');

                    if (typeof ajaxify != 'undefined' && ajaxify !== null && ajaxify === false) {
                        return;
                    }

                    var href = $(this).attr('href');

                    if (!event.ctrlKey && that.isRelative(href)) {
                        that.setPage(href);
                    } else {
                        // open external and ctrl-click in new window/tab
                        window.open(href, '_blank');
                    }

                    $(this).trigger('ajaxifyClick');

                    // continue propagation if the link has said attribute
                    return $(this).is('[data-ajaxify=continue]');
                }
            );
        },
        load: function (element, trigger) {
            var that = this;

            if (typeof trigger == 'undefined' || trigger === null) {
                trigger = true;
            }

            if (trigger) {
                $(this.selector).trigger('ajaxifyInit');
            }
        },
        loadPage: function (href) {
            var that = this;
            var contents = that.contents.join(',');

            layout.showOverlay();

            this.activeRequest = $.ajax(href, {
                success: function (data, textStatus, xhr) {
                    if (typeof data != 'undefined' && data !== null && data !== '') {
                        that.setPageInDom(data);
                    }

                    layout.hideOverlay();
                    that.activeRequest = null;
                },
                error: function (xhr) {
                    var contentType = xhr.getResponseHeader("Content-Type");

                    var data = null;

                    if (contentType == "text/plain") {
                        // do you know of a way to change so the browser just
                        // renders text/plain instead? i dont, would be nice.
                        data = "<pre>" + xhr.responseText + "</pre>";
                    } else if (xhr.statusText != 'abort') {
                        data = xhr.responseText;
                    }

                    if (data !== null) {
                        that.setPageInDom(data);
                    }

                    layout.hideOverlay();
                    that.activeRequest = null;
                }
            });
        },
        setPageInDom: function (data) {
            var html = $($.parseHTML(data));

            document.title = $.trim(html.find('#title').text());

            var notFound = 0;

            for (var index in this.contents) {
                var content = this.contents[index];
                var newContent = html.find(content);

                if (newContent.length === 0) {
                    notFound += 1;
                    continue;
                }

                this.setInDom(content, newContent);
            }

            $(window).scrollTop(0);

            // if none of the contents are found we just replace the whole
            // document, this happens when there's a standard cherrypy error
            if (notFound === this.contents.length) {
                var newDoc = document.open('text/html', 'replace');
                newDoc.write(data);
                newDoc.close();
            }

            this.load(this.selector);
        },
        setInDom: function (content, newContent) {
            $(content).empty();

            if ($(newContent).length == 0) {
                return;
            }

            $(content)
                .append($(newContent).contents());

            this.fixAttributes(newContent, content);
        },
        fixAttributes: function (newContent, content) {
            // TODO adds attributes but doesn't remove no longer existing attributes
            $.each($(newContent).get(0).attributes, function (index, attribute) {
                $(content).attr(attribute.name, attribute.value);
            });
        },
        getPage: function (href) {
            if (!this.isRelative(href)) {
                href = this.getPathComponent(href);
            }

            return href;
        },
        setPage: function (href) {
            href = this.getPage(href);

            history.pushState({}, '', href);

            this.loadPage(href);
        },
        getPathComponent: function (href) {
            return href.match(/^http(s)?:\/\/[^\/]+(.*)/)[2];
        },
        isRelative: function (href) {
            return !/^http(s)?:\/\//.test(href);
        }
    });

    return (function () {
        if (instance === null) {
            instance = new Ajaxify();
        }

        return instance;
    })();
});
<|endoftext|>


<commit_before>var fs = require('fs');
var commands = require('./commands');
var units = require('./units');
var upgrades = require('./upgrades');
var research = require('./research');
var abbrevs = require('./abbrevs.json');

var aiscript;
var currentRace;

var binaryFilename = 'build/aiscript.bin';

fs.writeFileSync(binaryFilename, '');

function reset() {
    aiscript = {
        buf: new Buffer(32000),
        length: 0,
        cursor: 0,
        getAddress: function () {
            return this.cursor + 4;
        }
    };
    currentRace = '';
}
reset();

// Add ai commands
Object.keys(commands).forEach(function (command, i) {
    exports[command] = generateCommandFunction(command, commands[command].params);
});

// Add abbreviations
Object.keys(abbrevs).forEach(function (abbrev, i) {
    function clean(str) {
        str = str.toLowerCase();
        str = str.replace(/ /g, '_');
        return str;
    }

    exports[clean(abbrev)] = abbrev;
    abbrevs[abbrev].forEach(function(shortcut) {
        exports[clean(shortcut)] = abbrev;
    });
});

function checkParams(validParams, params) {
    var ret = [];

    validParams.forEach(function (validParam, i) {
        if(validParam === 'priority' && params[i] === undefined) {
            params[i] = 80;
        }

        ret.push(params[i]);
    });

    return ret;
}

function binWrite(b) {
    aiscript.buf[aiscript.cursor] = b;
    aiscript.cursor += 1;
    aiscript.length += 1;
}

function binWriteWord(w) {
    aiscript.buf.writeUInt16LE(w, aiscript.cursor);
    aiscript.cursor += 2;
    aiscript.length += 2;
}

function appendCommand(command) {
    binWrite(commands[command].op);
}

function appendArg(val, type) {
    switch (type) {
        case 'byte':
        case 'priority':
            binWrite(val);
            break;
        case 'address':
        case 'word':
            binWriteWord(val);
            break;
        case 'building':
        case 'unit':
        case 'military':
            binWriteWord(units[val].id);
            break;
        case 'technology':
            binWriteWord(research[val].id);
            break;
        case 'upgrade':
            binWriteWord(upgrades[val].id);
            break;
        default:
            console.error('Type not recognized: ' + type);
    }
}

addresses = {};

/* Creates a block and returns its address */
// Blocks are created inline and skipped over using a goto
// If true, isUnique causes the block to be added without a check to see if it already exists
function createBlock(block, isUnique) {
    if (isUnique || !addresses[block]) {
        var skipAddressLocation = aiscript.cursor + 1;
        exports.goto(0);
        addresses[block] = aiscript.cursor + 4;
        block();
        aiscript.buf.writeUInt16LE(aiscript.cursor + 4, skipAddressLocation);
    }

    return addresses[block];
}

function generateCommandFunction(command, validParams) {
    return function () {
        var params = checkParams(validParams, arguments);

        if (params) {
            // Translate functions to addresses and insert contents
            validParams.forEach(function (type, i) {
                if (type == 'address' && typeof(params[i]) == 'function') {
                    params[i] = createBlock(params[i]);
                }
            });

            appendCommand(command);
            validParams.forEach(function addParam(type, i) {
                // Evaluate functions to get a simple value
                if (type != 'address' && typeof(params[i]) == 'function') {
                    params[i] = params[i]();
                }

                appendArg(params[i], type);
            });
        }
    };
}

/* Macros */

exports.build_start = function(quantity, building, priority) {
    exports.build(quantity, building, priority);
    exports.wait_buildstart(quantity, building);
};

exports.build_finish = function(quantity, building, priority) {
    exports.build_start(quantity, building, priority);
    exports.wait_build(quantity, building);
};

exports.loop = function(block) {
    var startAddr;
    var gotoAddr;

    var inner = function () {
        block();
        exports.wait(10);
        gotoAddr = aiscript.cursor + 1;
        exports.goto(0xffff);
    };

    startAddr = createBlock(inner, true);
    aiscript.buf.writeUInt16LE(startAddr, gotoAddr);
    exports.goto(startAddr);
};

exports.pick = function(blocks) {
    if (typeof blocks.length == 'undefined') {
        var blocks_obj = blocks;
        blocks = [];
        Object.keys(blocks_obj).forEach(function (key) {
            blocks.push(blocks_obj[key]);
        });
    }

    var startAddress = aiscript.cursor;
    var startJumpLocations = [];
    var finishJumpLocations = [];

    exports.loop(function() {
        blocks.forEach(function(block) {
            var newBlock = function() {
                if (block.options && block.options.versus) {
                    var raceAddr = [
                        block.options.versus.terran ? aiscript.cursor + 4 + 7 : 0xffff,
                        block.options.versus.zerg ? aiscript.cursor + 4 + 7 : 0xffff,
                        block.options.versus.protoss ? aiscript.cursor + 4 + 7 : 0xffff
                    ];

                    raceAddr.forEach(function (addr, index) {
                        if (addr == 0xffff) {
                            startJumpLocations.push(aiscript.cursor + 1 + ( 2 * index ));
                        }
                    });

                    exports.race_jump(raceAddr[0], raceAddr[1], raceAddr[2]);
                }

                block();
                finishJumpLocations.push(aiscript.cursor + 1);
                exports.goto(0xffff);
            };

            exports.random_jump(5, createBlock(newBlock, true));
        });
    });

    startJumpLocations.forEach(function(address) {
        aiscript.buf.writeUInt16LE(startAddress, address);
    });

    finishJumpLocations.forEach(function(address) {
        aiscript.buf.writeUInt16LE(aiscript.cursor + 4, address);
    });
};

/*
 * Due to a bug in SC, only one return address can be stored at once,
 * meaning that call + multiple threads is completely broken. Call is
 * overloaded here to just inline the function content.
 */
exports.call = function(block) {
    block();
};

/* Helpers */

exports.init_shortcuts = function(race) {
    exports.race = race;

    switch (race) {
        case 'terran':
            exports.peon = 'Terran SCV';
            exports.town_hall = 'Terran Command Center';
            exports.gas = 'Terran Refinery';
            break;
    }
};

exports.header = function(race) {
    exports.init_shortcuts(race);

    exports.start_town();
    exports.transports_off();
    exports.farms_notiming();
    exports.defaultbuild_off();
    exports.default_min(7);

    exports.build_start(1, exports.town_hall);
    exports.build_start(4, exports.peon);
};

exports.flush = function () {
    exports.stop();

    var len = new Buffer(4);
    len.writeUInt32LE(aiscript.length + 4, 0);
    fs.appendFileSync(binaryFilename, len);

    var buf = aiscript.buf.slice(0, aiscript.length);
    fs.appendFileSync(binaryFilename, buf);

    switch (exports.race) {
        case 'terran':
            fs.appendFileSync(binaryFilename, 'TMCx\x04\x00\x00\x00\x3f\x05\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00');
    }

    reset();
};<commit_msg>fixed issue with block addresses<commit_after>var fs = require('fs');
var commands = require('./commands');
var units = require('./units');
var upgrades = require('./upgrades');
var research = require('./research');
var abbrevs = require('./abbrevs.json');

var aiscript;
var currentRace;

var binaryFilename = 'build/aiscript.bin';

fs.writeFileSync(binaryFilename, '');

function reset() {
    aiscript = {
        buf: new Buffer(32000),
        length: 0,
        cursor: 0,
        getAddress: function () {
            return this.cursor + 4;
        }
    };
    currentRace = '';
}
reset();

// Add ai commands
Object.keys(commands).forEach(function (command, i) {
    exports[command] = generateCommandFunction(command, commands[command].params);
});

// Add abbreviations
Object.keys(abbrevs).forEach(function (abbrev, i) {
    function clean(str) {
        str = str.toLowerCase();
        str = str.replace(/ /g, '_');
        return str;
    }

    exports[clean(abbrev)] = abbrev;
    abbrevs[abbrev].forEach(function(shortcut) {
        exports[clean(shortcut)] = abbrev;
    });
});

function checkParams(validParams, params) {
    var ret = [];

    validParams.forEach(function (validParam, i) {
        if(validParam === 'priority' && params[i] === undefined) {
            params[i] = 80;
        }

        ret.push(params[i]);
    });

    return ret;
}

function binWrite(b) {
    aiscript.buf[aiscript.cursor] = b;
    aiscript.cursor += 1;
    aiscript.length += 1;
}

function binWriteWord(w) {
    aiscript.buf.writeUInt16LE(w, aiscript.cursor);
    aiscript.cursor += 2;
    aiscript.length += 2;
}

function appendCommand(command) {
    binWrite(commands[command].op);
}

function appendArg(val, type) {
    switch (type) {
        case 'byte':
        case 'priority':
            binWrite(val);
            break;
        case 'address':
        case 'word':
            binWriteWord(val);
            break;
        case 'building':
        case 'unit':
        case 'military':
            binWriteWord(units[val].id);
            break;
        case 'technology':
            binWriteWord(research[val].id);
            break;
        case 'upgrade':
            binWriteWord(upgrades[val].id);
            break;
        default:
            console.error('Type not recognized: ' + type);
    }
}

addresses = {};

/* Creates a block and returns its address */
// Blocks are created inline and skipped over using a goto
// If true, isUnique causes the block to be added without a check to see if it already exists
function createBlock(block, isUnique) {
    var address;

    if (isUnique || !addresses[block]) {
        var skipAddressLocation = aiscript.cursor + 1;
        exports.goto(0);
        address = aiscript.cursor + 4;
        block();
        aiscript.buf.writeUInt16LE(aiscript.cursor + 4, skipAddressLocation);
    }

    if (!isUnique) {
        addresses[block] = aiscript.cursor + 4
    };

    return address;
}

function generateCommandFunction(command, validParams) {
    return function () {
        var params = checkParams(validParams, arguments);

        if (params) {
            // Translate functions to addresses and insert contents
            validParams.forEach(function (type, i) {
                if (type == 'address' && typeof(params[i]) == 'function') {
                    params[i] = createBlock(params[i]);
                }
            });

            appendCommand(command);
            validParams.forEach(function addParam(type, i) {
                // Evaluate functions to get a simple value
                if (type != 'address' && typeof(params[i]) == 'function') {
                    params[i] = params[i]();
                }

                appendArg(params[i], type);
            });
        }
    };
}

/* Macros */

exports.build_start = function(quantity, building, priority) {
    exports.build(quantity, building, priority);
    exports.wait_buildstart(quantity, building);
};

exports.build_finish = function(quantity, building, priority) {
    exports.build_start(quantity, building, priority);
    exports.wait_build(quantity, building);
};

exports.loop = function(block) {
    var startAddr;
    var gotoAddr;

    var inner = function () {
        block();
        exports.wait(10);
        gotoAddr = aiscript.cursor + 1;
        exports.goto(0xffff);
    };

    startAddr = createBlock(inner, true);
    aiscript.buf.writeUInt16LE(startAddr, gotoAddr);
    exports.goto(startAddr);
};

exports.pick = function(blocks) {
    if (typeof blocks.length == 'undefined') {
        var blocks_obj = blocks;
        blocks = [];
        Object.keys(blocks_obj).forEach(function (key) {
            blocks.push(blocks_obj[key]);
        });
    }

    var startAddress = aiscript.cursor;
    var startJumpLocations = [];
    var finishJumpLocations = [];

    exports.loop(function() {
        blocks.forEach(function(block) {
            var newBlock = function() {
                if (block.options && block.options.versus) {
                    var raceAddr = [
                        block.options.versus.terran ? aiscript.cursor + 4 + 7 : 0xffff,
                        block.options.versus.zerg ? aiscript.cursor + 4 + 7 : 0xffff,
                        block.options.versus.protoss ? aiscript.cursor + 4 + 7 : 0xffff
                    ];

                    raceAddr.forEach(function (addr, index) {
                        if (addr == 0xffff) {
                            startJumpLocations.push(aiscript.cursor + 1 + ( 2 * index ));
                        }
                    });

                    exports.race_jump(raceAddr[0], raceAddr[1], raceAddr[2]);
                }

                block();
                finishJumpLocations.push(aiscript.cursor + 1);
                exports.goto(0xffff);
            };

            exports.random_jump(5, createBlock(newBlock, true));
        });
    });

    startJumpLocations.forEach(function(address) {
        aiscript.buf.writeUInt16LE(startAddress, address);
    });

    finishJumpLocations.forEach(function(address) {
        aiscript.buf.writeUInt16LE(aiscript.cursor + 4, address);
    });
};

/*
 * Due to a bug in SC, only one return address can be stored at once,
 * meaning that call + multiple threads is completely broken. Call is
 * overloaded here to just inline the function content.
 */
exports.call = function(block) {
    block();
};

/* Helpers */

exports.init_shortcuts = function(race) {
    exports.race = race;

    switch (race) {
        case 'terran':
            exports.peon = 'Terran SCV';
            exports.town_hall = 'Terran Command Center';
            exports.gas = 'Terran Refinery';
            break;
    }
};

exports.header = function(race) {
    exports.init_shortcuts(race);

    exports.start_town();
    exports.transports_off();
    exports.farms_notiming();
    exports.defaultbuild_off();
    exports.default_min(7);

    exports.build_start(1, exports.town_hall);
    exports.build_start(4, exports.peon);
};

exports.flush = function () {
    exports.stop();

    var len = new Buffer(4);
    len.writeUInt32LE(aiscript.length + 4, 0);
    fs.appendFileSync(binaryFilename, len);

    var buf = aiscript.buf.slice(0, aiscript.length);
    fs.appendFileSync(binaryFilename, buf);

    switch (exports.race) {
        case 'terran':
            fs.appendFileSync(binaryFilename, 'TMCx\x04\x00\x00\x00\x3f\x05\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00');
    }

    reset();
};<|endoftext|>


<commit_before>/**
 * ContentCacheManager - maintains a cache of stream hashes from a PDF file
 */

const { callbackify } = require('util')
const fs = require('fs')
const crypto = require('crypto')
const Path = require('path')
const Settings = require('settings-sharelatex')

const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize

const START_OF_STREAM_MARKER = 'stream'
const END_OF_STREAM_MARKER = 'endstream'
const START_OF_STREAM_MARKER_LENGTH = START_OF_STREAM_MARKER.length
const END_OF_STREAM_MARKER_LENGTH = END_OF_STREAM_MARKER.length

/**
 *
 * @param {String} contentDir path to directory where content hash files are cached
 * @param {String} filePath the pdf file to scan for streams
 */
async function update(contentDir, filePath) {
  const stream = fs.createReadStream(filePath)
  const extractor = new PdfStreamsExtractor()
  const ranges = []
  const newRanges = []
  const seenHashes = new Set()
  for await (const chunk of stream) {
    const pdfStreams = extractor.consume(chunk)
    for (const pdfStream of pdfStreams) {
      if (pdfStream.end - pdfStream.start < MIN_CHUNK_SIZE) continue
      const hash = pdfStreamHash(pdfStream.buffers)

      const range = { start: pdfStream.start, end: pdfStream.end, hash }
      ranges.push(range)

      // Optimization: Skip writing of duplicate streams.
      if (seenHashes.has(hash)) continue
      seenHashes.add(hash)

      if (await writePdfStream(contentDir, hash, pdfStream.buffers)) {
        newRanges.push(range)
      }
    }
  }
  return [ranges, newRanges]
}

class PdfStreamsExtractor {
  constructor() {
    this.fileIndex = 0
    this.inStream = false
    this.streamStartIndex = 0
    this.buffers = []
    this.lastChunk = Buffer.alloc(0)
  }

  consume(chunk) {
    let chunkIndex = 0
    const pdfStreams = []
    chunk = Buffer.concat([this.lastChunk, chunk])
    while (true) {
      if (!this.inStream) {
        // Not in a stream, look for stream start
        const index = chunk.indexOf(START_OF_STREAM_MARKER, chunkIndex)
        if (index === -1) {
          // Couldn't find stream start
          break
        }
        // Found stream start, start a stream
        this.inStream = true
        this.streamStartIndex = this.fileIndex + index
        chunkIndex = index
      } else {
        // In a stream, look for stream end
        const index = chunk.indexOf(END_OF_STREAM_MARKER, chunkIndex)
        if (index === -1) {
          break
        }
        // add "endstream" part
        const endIndex = index + END_OF_STREAM_MARKER_LENGTH
        this.buffers.push(chunk.slice(chunkIndex, endIndex))
        pdfStreams.push({
          start: this.streamStartIndex,
          end: this.fileIndex + endIndex,
          buffers: this.buffers
        })
        this.inStream = false
        this.buffers = []
        chunkIndex = endIndex
      }
    }

    const remaining = chunk.length - chunkIndex
    const nextMarkerLength = this.inStream
      ? END_OF_STREAM_MARKER_LENGTH
      : START_OF_STREAM_MARKER_LENGTH
    if (remaining > nextMarkerLength) {
      const retainMarkerSection = chunk.length - nextMarkerLength
      if (this.inStream) {
        this.buffers.push(chunk.slice(chunkIndex, retainMarkerSection))
      }
      this.lastChunk = chunk.slice(retainMarkerSection)
      this.fileIndex += retainMarkerSection
    } else {
      this.lastChunk = chunk.slice(chunkIndex)
      this.fileIndex += chunkIndex
    }
    return pdfStreams
  }
}

function pdfStreamHash(buffers) {
  const hash = crypto.createHash('sha256')
  for (const buffer of buffers) {
    hash.update(buffer)
  }
  return hash.digest('hex')
}

async function writePdfStream(dir, hash, buffers) {
  const filename = Path.join(dir, hash)
  try {
    await fs.promises.stat(filename)
    // The file exists. Do not rewrite the content.
    // It would change the modified-time of the file and hence invalidate the
    //  ETags used for client side caching via browser internals.
    return false
  } catch (e) {}
  const atomicWriteFilename = filename + '~'
  const file = await fs.promises.open(atomicWriteFilename, 'w')
  if (Settings.enablePdfCachingDark) {
    // Write an empty file in dark mode.
    buffers = []
  }
  try {
    try {
      for (const buffer of buffers) {
        await file.write(buffer)
      }
    } finally {
      await file.close()
    }
    await fs.promises.rename(atomicWriteFilename, filename)
  } catch (err) {
    try {
      await fs.promises.unlink(atomicWriteFilename)
    } catch (_) {
      throw err
    }
  }
  return true
}

module.exports = {
  HASH_REGEX: /^[0-9a-f]{64}$/,
  update: callbackify(update)
}
<commit_msg>wip expire old hash files<commit_after>/**
 * ContentCacheManager - maintains a cache of stream hashes from a PDF file
 */

const { callbackify } = require('util')
const fs = require('fs')
const crypto = require('crypto')
const Path = require('path')
const Settings = require('settings-sharelatex')

const MIN_CHUNK_SIZE = Settings.pdfCachingMinChunkSize

const START_OF_STREAM_MARKER = 'stream'
const END_OF_STREAM_MARKER = 'endstream'
const START_OF_STREAM_MARKER_LENGTH = START_OF_STREAM_MARKER.length
const END_OF_STREAM_MARKER_LENGTH = END_OF_STREAM_MARKER.length

/**
 *
 * @param {String} contentDir path to directory where content hash files are cached
 * @param {String} filePath the pdf file to scan for streams
 */
async function update(contentDir, filePath) {
  const stream = fs.createReadStream(filePath)
  const extractor = new PdfStreamsExtractor()
  const ranges = []
  const newRanges = []
  const seenHashes = new Set()
  // keep track of hashes expire old ones when they reach a generation > N.
  const tracker = new HashFileTracker()
  await loadState(contentDir, tracker)

  for await (const chunk of stream) {
    const pdfStreams = extractor.consume(chunk)
    for (const pdfStream of pdfStreams) {
      if (pdfStream.end - pdfStream.start < MIN_CHUNK_SIZE) continue
      const hash = pdfStreamHash(pdfStream.buffers)

      const range = { start: pdfStream.start, end: pdfStream.end, hash }
      ranges.push(range)

      // Optimization: Skip writing of duplicate streams.
      if (seenHashes.has(hash)) continue
      seenHashes.add(hash)

      if (await writePdfStream(contentDir, hash, pdfStream.buffers)) {
        newRanges.push(range)
      }
    }
  }
  const expiredHashes = tracker.update(ranges).findStale(5)
  await deleteHashFiles(expiredHashes)
  return [ranges, newRanges]
}

class HashFileTracker {
  constructor(contentDir) {
    this.hashAge = new Map()
  }

  update(ranges) {
    for (const [hash, age] of this.hashAge) {
      this.hashAge.set(hash, age + 1)
    }
    for (const range in ranges) {
      this.hashAge.set(range.hash, 0)
    }
  }

  findStale(maxAge) {
    var stale = []
    for (const [hash, age] of this.hashAge) {
      if (age > maxAge) {
        stale.push(hash)
        this.hashAge.delete(hash)
      }
    }
    return stale
  }
}

async function loadState(contentDir, tracker) {

}

async function deleteHashFiles(n) {
  // delete any hash file older than N generations
}

class PdfStreamsExtractor {
  constructor() {
    this.fileIndex = 0
    this.inStream = false
    this.streamStartIndex = 0
    this.buffers = []
    this.lastChunk = Buffer.alloc(0)
  }

  consume(chunk) {
    let chunkIndex = 0
    const pdfStreams = []
    chunk = Buffer.concat([this.lastChunk, chunk])
    while (true) {
      if (!this.inStream) {
        // Not in a stream, look for stream start
        const index = chunk.indexOf(START_OF_STREAM_MARKER, chunkIndex)
        if (index === -1) {
          // Couldn't find stream start
          break
        }
        // Found stream start, start a stream
        this.inStream = true
        this.streamStartIndex = this.fileIndex + index
        chunkIndex = index
      } else {
        // In a stream, look for stream end
        const index = chunk.indexOf(END_OF_STREAM_MARKER, chunkIndex)
        if (index === -1) {
          break
        }
        // add "endstream" part
        const endIndex = index + END_OF_STREAM_MARKER_LENGTH
        this.buffers.push(chunk.slice(chunkIndex, endIndex))
        pdfStreams.push({
          start: this.streamStartIndex,
          end: this.fileIndex + endIndex,
          buffers: this.buffers
        })
        this.inStream = false
        this.buffers = []
        chunkIndex = endIndex
      }
    }

    const remaining = chunk.length - chunkIndex
    const nextMarkerLength = this.inStream
      ? END_OF_STREAM_MARKER_LENGTH
      : START_OF_STREAM_MARKER_LENGTH
    if (remaining > nextMarkerLength) {
      const retainMarkerSection = chunk.length - nextMarkerLength
      if (this.inStream) {
        this.buffers.push(chunk.slice(chunkIndex, retainMarkerSection))
      }
      this.lastChunk = chunk.slice(retainMarkerSection)
      this.fileIndex += retainMarkerSection
    } else {
      this.lastChunk = chunk.slice(chunkIndex)
      this.fileIndex += chunkIndex
    }
    return pdfStreams
  }
}

function pdfStreamHash(buffers) {
  const hash = crypto.createHash('sha256')
  for (const buffer of buffers) {
    hash.update(buffer)
  }
  return hash.digest('hex')
}

async function writePdfStream(dir, hash, buffers) {
  const filename = Path.join(dir, hash)
  try {
    await fs.promises.stat(filename)
    // The file exists. Do not rewrite the content.
    // It would change the modified-time of the file and hence invalidate the
    //  ETags used for client side caching via browser internals.
    return false
  } catch (e) {}
  const atomicWriteFilename = filename + '~'
  const file = await fs.promises.open(atomicWriteFilename, 'w')
  if (Settings.enablePdfCachingDark) {
    // Write an empty file in dark mode.
    buffers = []
  }
  try {
    try {
      for (const buffer of buffers) {
        await file.write(buffer)
      }
    } finally {
      await file.close()
    }
    await fs.promises.rename(atomicWriteFilename, filename)
  } catch (err) {
    try {
      await fs.promises.unlink(atomicWriteFilename)
    } catch (_) {
      throw err
    }
  }
  return true
}

module.exports = {
  HASH_REGEX: /^[0-9a-f]{64}$/,
  update: callbackify(update)
}
<|endoftext|>


<commit_before>'use strict';

module.exports = numberWords;

// TODO: Write tests - not 100% yet
// TODO: Minimize calls (MAX takes 32 calls)
// TODO: Add support for “st”, “nd”, “rd” and “th”

var TEN             = 10;
var ONE_HUNDRED     = 100;
var ONE_THOUSAND    = 1000;             // 1.000 (3)
var ONE_MILLION     = 1000000;          // 1.000.000 (6)
var ONE_BILLION     = 1000000000;       // 1.000.000.000 (9)
var ONE_TRILLION    = 1000000000000;    // 1.000.000.000.000 (12)
var ONE_QUADRILLION = 1000000000000000; // 1.000.000.000.000.000 (15)
var MAX             = 9007199254740992; // 9.007.199.254.740.992

var lessThanTwenty = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',
    'ten', 'eleven', 'twelv', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'];
var lessThanOneHundred = ['twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninty'];
var isFinite_ = Number.isFinite || function (value) {
    return typeof value === "number" && isFinite(value);
};

function numberWords(number) {
    return toWords(number);
}

var iterations = 0
function toWords(number, words) {
    if (!isFinite_(number)) throw TypeError;
    if (!words) words = [];
    iterations++;
    console.log(iterations, number, words);

    var remainder, currentWord;
    if (number === 0) {
        return words.join(' ');

    } else if (number < 20) {
        remainder = 0;
        currentWord = lessThanTwenty[number];

    } else if (number < ONE_HUNDRED) {
        remainder = number % TEN;
        currentWord = lessThanOneHundred[Math.floor(number / TEN)];

    } else if (number < ONE_THOUSAND) {
        remainder = number % ONE_HUNDRED;
        currentWord = toWords(Math.floor(number / ONE_HUNDRED)) + ' hundred';

    } else if (number < ONE_MILLION) {
        remainder = number % ONE_THOUSAND;
        currentWord = toWords(Math.floor(number / ONE_THOUSAND)) + ' thousand';

    } else if (number < ONE_BILLION) {
        remainder = number % ONE_MILLION;
        currentWord = toWords(Math.floor(number / ONE_MILLION)) + ' million';

    } else if (number < ONE_TRILLION) {
        remainder = number % ONE_BILLION;
        currentWord = toWords(Math.floor(number / ONE_BILLION)) + ' billion';

    } else if (number < ONE_QUADRILLION) {
        remainder = number % ONE_TRILLION;
        currentWord = toWords(Math.floor(number / ONE_TRILLION)) + ' trillion';

    } else if (number <= MAX) {
        remainder = number % ONE_QUADRILLION;
        currentWord = toWords(Math.floor(number / ONE_QUADRILLION)) + ' quadrillion';

    }

    words.push(currentWord);
    return toWords(remainder, words);
}
<commit_msg>Delete index.js<commit_after><|endoftext|>


<commit_before>(function() {
  "use strict";

  var WORD = /[\w$]+/, RANGE = 500;

  CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
    var word = options && options.word || WORD;
    var range = options && options.range || RANGE;
    var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
    var start = cur.ch, end = start;
    while (end < curLine.length && word.test(curLine.charAt(end))) ++end;
    while (start && word.test(curLine.charAt(start - 1))) --start;
    var curWord = start != end && curLine.slice(start, end);

    var list = [], seen = {};
    var re = new RegExp(word.source, "g");
    for (var dir = -1; dir <= 1; dir += 2) {
      var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
      for (; line != end; line += dir) {
        var text = editor.getLine(line), m;
        while (m = re.exec(text)) {
          if (line == cur.line && m[0] === curWord) continue;
          if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
            seen[m[0]] = true;
            list.push(m[0]);
          }
        }
      }
    }
    return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
  });
})();
<commit_msg>[anyword-hint addon] Fix variable clash<commit_after>(function() {
  "use strict";

  var WORD = /[\w$]+/, RANGE = 500;

  CodeMirror.registerHelper("hint", "anyword", function(editor, options) {
    var word = options && options.word || WORD;
    var range = options && options.range || RANGE;
    var cur = editor.getCursor(), curLine = editor.getLine(cur.line);
    var start = cur.ch, end = start;
    while (end < curLine.length && word.test(curLine.charAt(end))) ++end;
    while (start && word.test(curLine.charAt(start - 1))) --start;
    var curWord = start != end && curLine.slice(start, end);

    var list = [], seen = {};
    var re = new RegExp(word.source, "g");
    for (var dir = -1; dir <= 1; dir += 2) {
      var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
      for (; line != endLine; line += dir) {
        var text = editor.getLine(line), m;
        while (m = re.exec(text)) {
          if (line == cur.line && m[0] === curWord) continue;
          if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
            seen[m[0]] = true;
            list.push(m[0]);
          }
        }
      }
    }
    return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
  });
})();
<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var fs = require('fs-extra');
var path = require('path');
var _ = require('underscore');
var moment = require('moment');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');
var super_ = require('../Type');
var async = require('async');
var grappling = require('grappling-hook');

/**
 * localfiles FieldType Constructor
 * @extends Field
 * @api public
 */

function localfiles(list, path, options) {
	grappling.mixin(this).allowHooks('move');
	this._underscoreMethods = ['format', 'uploadFiles'];
	this._fixedSize = 'full';

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	// TODO: implement initial form, usage disabled for now
	if (options.initial) {
		throw new Error('Invalid Configuration\n\n' +
			'localfiles fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n');
	}

	if (options.overwrite !== false) {
		options.overwrite = true;
	}

	localfiles.super_.call(this, list, path, options);

	// validate destination dir
	if (!options.dest) {
		throw new Error('Invalid Configuration\n\n' +
			'localfiles fields (' + list.key + '.' + path + ') require the "dest" option to be set.');
	}

	// Allow hook into before and after
	if (options.pre && options.pre.move) {
		this.pre('move', options.pre.move);
	}

	if (options.post && options.post.move) {
		this.post('move', options.post.move);
	}

}

/*!
 * Inherit from Field
 */

util.inherits(localfiles, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

localfiles.prototype.addToSchema = function() {

	var field = this;
	var schema = this.list.schema;
	var mongoose = keystone.mongoose;

	var paths = this.paths = {
		// fields
		filename:		this._path.append('.filename'),
		path:			this._path.append('.path'),
		originalname:	this._path.append('.originalname'),
		size:			this._path.append('.size'),
		filetype:		this._path.append('.filetype'),
		// virtuals
		exists:			this._path.append('.exists'),
		upload:			this._path.append('_upload'),
		action:			this._path.append('_action'),
		order: 			this._path.append('_order')
	};

	var schemaPaths = new mongoose.Schema({
		filename:		String,
		originalname:	String,
		path:			String,
		size:			Number,
		filetype:		String
	});

	// The .href virtual returns the public path of the file
	schemaPaths.virtual('href').get(function() {
		return field.href.call(field, this);
	});

	schema.add(this._path.addTo({}, [schemaPaths]));

	var exists = function(item, element_id) {
		var values = item.get(field.path);
		var value;

		if (typeof values === 'undefined' || values.length === 0) {
			return false;
		}

		// if current Field contains any file, it means it exists
		if (typeof element_id === 'undefined') {
			value = values[0];
		} else {
			value = values.find(function (val) { return val._id == element_id; });
		}

		if (typeof value === 'undefined') {
			return false;
		}

		var filepaths = value.path,
			filename = value.filename;

		if (!filepaths || !filename) {
			return false;
		}

		return fs.existsSync(path.join(filepaths, filename));
	};

	// The .exists virtual indicates whether a file is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var reset = function(item, element_id) {
		if (typeof element_id === 'undefined') {
			item.set(field.path, []);
		} else {
			var values = item.get(field.path);
			var value = values.find(function (val) { return val._id == element_id; });
			if (typeof value !== 'undefined') {
				values.splice(values.indexOf(value), 1);
				item.set(field.path, values);
			}
		}
	};

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the file from localfiles and resets the field
		 *
		 * @api public
		 */
		delete: function(element_id) {
			if (exists(this, element_id)) {
				var values = this.get(field.path);
				var value = values.find(function (val) { return val._id == element_id; });
				if (typeof value !== 'undefined') {
					fs.unlinkSync(path.join(value.path, value.filename));
				}
			}
			reset(this, element_id);
		}
	};

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

localfiles.prototype.format = function(item, i) {
	var files = item.get(this.path);
	if (typeof i === 'undefined') {
		return utils.plural(files.length, '* File');
	}
	var file = files[i];
	if (!file) return '';
	if (this.hasFormatter()) {
		file.href = this.href(file);
		return this.options.format.call(this, item, file);
	}
	return file.filename;
};

/**
 * Detects whether the field has a formatter function
 *
 * @api public
 */

localfiles.prototype.hasFormatter = function() {
	return 'function' === typeof this.options.format;
};

/**
 * Return the public href for a single stored file
 *
 * @api public
 */

localfiles.prototype.href = function(file) {
	if (!file.filename) return '';
	var prefix = this.options.prefix ? this.options.prefix : file.path;
	return prefix + '/' + file.filename;
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

localfiles.prototype.isModified = function(item) {
	return item.isModified(this.paths.path);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

localfiles.prototype.inputIsValid = function(data) {//eslint-disable-line no-unused-vars
	// TODO - how should file field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

localfiles.prototype.updateItem = function(item, data) {//eslint-disable-line no-unused-vars
	// TODO - direct updating of data (not via upload)
};

/**
 * Uploads the file for this field
 *
 * @api public
 */

localfiles.prototype.uploadFiles = function(item, files, update, callback) {

	var field = this;

	if ('function' === typeof update) {
		callback = update;
		update = false;
	}

	async.map(files, function(file, processedFile) {

		var prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '';
		var filename = prefix + file.name;
		var filetype = file.mimetype || file.type;

		if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, filetype)) {
			return processedFile(new Error('Unsupported File Type: ' + filetype));
		}

		var doMove = function(doneMove) {

			if ('function' === typeof field.options.filename) {
				filename = field.options.filename(item, file);
			}

			fs.move(file.path, path.join(field.options.dest, filename), { clobber: field.options.overwrite }, function(err) {
				if (err) return doneMove(err);
				var fileData = {
					filename: filename,
					originalname: file.originalname,
					path: field.options.dest,
					size: file.size,
					filetype: filetype
				};
				if (update) {
					item.get(field.path).push(fileData);
				}
				doneMove(null, fileData);
			});

		};

		field.callHook('pre:move', item, file, function(err) {
			if (err) return processedFile(err);
			doMove(function(err, fileData) {
				if (err) return processedFile(err);
				field.callHook('post:move', item, file, fileData, function(err) {
					return processedFile(err, fileData);
				});
			});
		});

	}, callback);

};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the file to localfiles)
 *
 * @api public
 */

localfiles.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		// Order
		if (req.body[paths.order]) {
			var files = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');
			files.sort(function(a, b) {
				return (newOrder.indexOf(a._id.toString()) > newOrder.indexOf(b._id.toString())) ? 1 : -1;
			});
		}

		// Removals
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');
			actions.forEach(function(action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];
				if (!(/^(delete|reset)$/.test(method)) || !ids) return;
				ids.split(',').forEach(function(id) {
					field.apply(item, method, id);
				});
			});
		}

		// Upload new files
		if (req.files) {
			var upFiles = req.files[paths.upload];
			if (upFiles) {
				if (!Array.isArray(upFiles)) {
					upFiles = [upFiles];
				}
				if (upFiles.length > 0) {
					upFiles = _.filter(upFiles, function(f) { return typeof f.name !== 'undefined' && f.name.length > 0; });
					if (upFiles.length > 0) {
						console.log('uploading files:');
						console.log(upFiles);
						return field.uploadFiles(item, upFiles, true, callback);
					}
				}
			}
		}

		return callback();
	};

};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

localfiles.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */

module.exports = localfiles;
<commit_msg>Missing var statement<commit_after>/*!
 * Module dependencies.
 */

var fs = require('fs-extra');
var path = require('path');
var _ = require('underscore');
var moment = require('moment');
var keystone = require('../../../');
var util = require('util');
var utils = require('keystone-utils');
var super_ = require('../Type');
var async = require('async');
var grappling = require('grappling-hook');

/**
 * localfiles FieldType Constructor
 * @extends Field
 * @api public
 */

function localfiles(list, path, options) {
	grappling.mixin(this).allowHooks('move');
	this._underscoreMethods = ['format', 'uploadFiles'];
	this._fixedSize = 'full';

	// TODO: implement filtering, usage disabled for now
	options.nofilter = true;

	// TODO: implement initial form, usage disabled for now
	if (options.initial) {
		throw new Error('Invalid Configuration\n\n' +
			'localfiles fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n');
	}

	if (options.overwrite !== false) {
		options.overwrite = true;
	}

	localfiles.super_.call(this, list, path, options);

	// validate destination dir
	if (!options.dest) {
		throw new Error('Invalid Configuration\n\n' +
			'localfiles fields (' + list.key + '.' + path + ') require the "dest" option to be set.');
	}

	// Allow hook into before and after
	if (options.pre && options.pre.move) {
		this.pre('move', options.pre.move);
	}

	if (options.post && options.post.move) {
		this.post('move', options.post.move);
	}

}

/*!
 * Inherit from Field
 */

util.inherits(localfiles, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

localfiles.prototype.addToSchema = function() {

	var field = this;
	var schema = this.list.schema;
	var mongoose = keystone.mongoose;

	var paths = this.paths = {
		// fields
		filename:		this._path.append('.filename'),
		path:			this._path.append('.path'),
		originalname:	this._path.append('.originalname'),
		size:			this._path.append('.size'),
		filetype:		this._path.append('.filetype'),
		// virtuals
		exists:			this._path.append('.exists'),
		upload:			this._path.append('_upload'),
		action:			this._path.append('_action'),
		order: 			this._path.append('_order')
	};

	var schemaPaths = new mongoose.Schema({
		filename:		String,
		originalname:	String,
		path:			String,
		size:			Number,
		filetype:		String
	});

	// The .href virtual returns the public path of the file
	schemaPaths.virtual('href').get(function() {
		return field.href.call(field, this);
	});

	schema.add(this._path.addTo({}, [schemaPaths]));

	var exists = function(item, element_id) {
		var values = item.get(field.path);
		var value;

		if (typeof values === 'undefined' || values.length === 0) {
			return false;
		}

		// if current Field contains any file, it means it exists
		if (typeof element_id === 'undefined') {
			value = values[0];
		} else {
			value = values.find(function (val) { return val._id == element_id; });
		}

		if (typeof value === 'undefined') {
			return false;
		}

		var filepaths = value.path;
		var filename = value.filename;

		if (!filepaths || !filename) {
			return false;
		}

		return fs.existsSync(path.join(filepaths, filename));
	};

	// The .exists virtual indicates whether a file is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var reset = function(item, element_id) {
		if (typeof element_id === 'undefined') {
			item.set(field.path, []);
		} else {
			var values = item.get(field.path);
			var value = values.find(function (val) { return val._id == element_id; });
			if (typeof value !== 'undefined') {
				values.splice(values.indexOf(value), 1);
				item.set(field.path, values);
			}
		}
	};

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the file from localfiles and resets the field
		 *
		 * @api public
		 */
		delete: function(element_id) {
			if (exists(this, element_id)) {
				var values = this.get(field.path);
				var value = values.find(function (val) { return val._id == element_id; });
				if (typeof value !== 'undefined') {
					fs.unlinkSync(path.join(value.path, value.filename));
				}
			}
			reset(this, element_id);
		}
	};

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
};

/**
 * Formats the field value
 *
 * @api public
 */

localfiles.prototype.format = function(item, i) {
	var files = item.get(this.path);
	if (typeof i === 'undefined') {
		return utils.plural(files.length, '* File');
	}
	var file = files[i];
	if (!file) return '';
	if (this.hasFormatter()) {
		file.href = this.href(file);
		return this.options.format.call(this, item, file);
	}
	return file.filename;
};

/**
 * Detects whether the field has a formatter function
 *
 * @api public
 */

localfiles.prototype.hasFormatter = function() {
	return 'function' === typeof this.options.format;
};

/**
 * Return the public href for a single stored file
 *
 * @api public
 */

localfiles.prototype.href = function(file) {
	if (!file.filename) return '';
	var prefix = this.options.prefix ? this.options.prefix : file.path;
	return prefix + '/' + file.filename;
};

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

localfiles.prototype.isModified = function(item) {
	return item.isModified(this.paths.path);
};

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

localfiles.prototype.inputIsValid = function(data) {//eslint-disable-line no-unused-vars
	// TODO - how should file field input be validated?
	return true;
};

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

localfiles.prototype.updateItem = function(item, data) {//eslint-disable-line no-unused-vars
	// TODO - direct updating of data (not via upload)
};

/**
 * Uploads the file for this field
 *
 * @api public
 */

localfiles.prototype.uploadFiles = function(item, files, update, callback) {

	var field = this;

	if ('function' === typeof update) {
		callback = update;
		update = false;
	}

	async.map(files, function(file, processedFile) {

		var prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '';
		var filename = prefix + file.name;
		var filetype = file.mimetype || file.type;

		if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, filetype)) {
			return processedFile(new Error('Unsupported File Type: ' + filetype));
		}

		var doMove = function(doneMove) {

			if ('function' === typeof field.options.filename) {
				filename = field.options.filename(item, file);
			}

			fs.move(file.path, path.join(field.options.dest, filename), { clobber: field.options.overwrite }, function(err) {
				if (err) return doneMove(err);
				var fileData = {
					filename: filename,
					originalname: file.originalname,
					path: field.options.dest,
					size: file.size,
					filetype: filetype
				};
				if (update) {
					item.get(field.path).push(fileData);
				}
				doneMove(null, fileData);
			});

		};

		field.callHook('pre:move', item, file, function(err) {
			if (err) return processedFile(err);
			doMove(function(err, fileData) {
				if (err) return processedFile(err);
				field.callHook('post:move', item, file, fileData, function(err) {
					return processedFile(err, fileData);
				});
			});
		});

	}, callback);

};

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the file to localfiles)
 *
 * @api public
 */

localfiles.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		// Order
		if (req.body[paths.order]) {
			var files = item.get(field.path);
			var newOrder = req.body[paths.order].split(',');
			files.sort(function(a, b) {
				return (newOrder.indexOf(a._id.toString()) > newOrder.indexOf(b._id.toString())) ? 1 : -1;
			});
		}

		// Removals
		if (req.body && req.body[paths.action]) {
			var actions = req.body[paths.action].split('|');
			actions.forEach(function(action) {
				action = action.split(':');
				var method = action[0];
				var ids = action[1];
				if (!(/^(delete|reset)$/.test(method)) || !ids) return;
				ids.split(',').forEach(function(id) {
					field.apply(item, method, id);
				});
			});
		}

		// Upload new files
		if (req.files) {
			var upFiles = req.files[paths.upload];
			if (upFiles) {
				if (!Array.isArray(upFiles)) {
					upFiles = [upFiles];
				}
				if (upFiles.length > 0) {
					upFiles = _.filter(upFiles, function(f) { return typeof f.name !== 'undefined' && f.name.length > 0; });
					if (upFiles.length > 0) {
						console.log('uploading files:');
						console.log(upFiles);
						return field.uploadFiles(item, upFiles, true, callback);
					}
				}
			}
		}

		return callback();
	};

};

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

localfiles.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
};

/*!
 * Export class
 */

module.exports = localfiles;
<|endoftext|>


<commit_before>"use strict";

var testHelper = require("../test-helper");
var captureArgs = require("../test-helper/capture-args");

testHelper.assertionTests("assert", "isRegExp", function(
    pass,
    fail,
    msg,
    error
) {
    pass("for RegExp", new RegExp("apple pie"));
    pass("for RegExp literal", /apple pie/);
    fail("for string", "apple pie");
    fail("for array", []);
    fail("for object", {});
    fail("for arguments", captureArgs());
    msg(
        "fail with descriptive message",
        "[assert.isRegExp] Expected {  } to be a RegExp",
        {}
    );
    msg(
        "fail with custom message",
        "[assert.isRegExp] Nope: Expected {  } to be a RegExp",
        {},
        "Nope"
    );
    error(
        "for object",
        {
            code: "ERR_ASSERTION",
            operator: "assert.isRegExp"
        },
        {}
    );
});
<commit_msg>Remove test helper function from isRegExp()<commit_after>"use strict";

var captureArgs = require("../test-helper/capture-args");
var referee = require("../referee");
var assert = require("assert");

describe("assert.isRegExp", function() {
    it("should pass for RegExp", function() {
        referee.assert.isRegExp(new RegExp("apple pie"));
    });

    it("should pass for RegExp literal", function() {
        referee.assert.isRegExp(/apple pie/);
    });

    it("should fail for string", function() {
        assert.throws(
            function() {
                referee.assert.isRegExp("apple pie");
            },
            function(error) {
                assert.equal(error.code, "ERR_ASSERTION");
                assert.equal(error.name, "AssertionError");
                assert.equal(
                    error.message,
                    "[assert.isRegExp] Expected apple pie to be a RegExp"
                );
                assert.equal(error.operator, "assert.isRegExp");
                return true;
            }
        );
    });

    it("should fail for arguments", function() {
        assert.throws(
            function() {
                referee.assert.isRegExp(captureArgs());
            },
            function(error) {
                assert.equal(error.code, "ERR_ASSERTION");
                assert.equal(error.name, "AssertionError");
                assert.equal(
                    error.message,
                    "[assert.isRegExp] Expected {  } to be a RegExp"
                );
                assert.equal(error.operator, "assert.isRegExp");
                return true;
            }
        );
    });

    it("should fail for object with custom message", function() {
        assert.throws(
            function() {
                referee.assert.isRegExp({}, "Nope");
            },
            function(error) {
                assert.equal(error.code, "ERR_ASSERTION");
                assert.equal(error.name, "AssertionError");
                assert.equal(
                    error.message,
                    "[assert.isRegExp] Nope: Expected {  } to be a RegExp"
                );
                assert.equal(error.operator, "assert.isRegExp");
                return true;
            }
        );
    });

    it("should fail for array", function() {
        assert.throws(
            function() {
                referee.assert.isRegExp([]);
            },
            function(error) {
                assert.equal(error.code, "ERR_ASSERTION");
                assert.equal(error.name, "AssertionError");
                assert.equal(
                    error.message,
                    "[assert.isRegExp] Expected [] to be a RegExp"
                );
                assert.equal(error.operator, "assert.isRegExp");
                return true;
            }
        );
    });
});
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * Displays the directrix for a quadratic.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const GQSymbols = require( 'GRAPHING_QUADRATICS/common/GQSymbols' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Shape = require( 'KITE/Shape' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Util = require( 'DOT/Util' );

  class DirectrixNode extends Node {

    /**
     * @param {Property.<Quadratic>} quadraticProperty
     * @param {Graph} graph
     * @param {ModelViewTransform2} modelViewTransform
     * @param {BooleanProperty} directrixVisibleProperty
     * @param {BooleanProperty} equationsVisibleProperty
     * @param {Object} [options]
     */
    constructor( quadraticProperty, graph, modelViewTransform, directrixVisibleProperty, equationsVisibleProperty, options ) {

      options = _.extend( {
        color: GQColors.DIRECTRIX,
        decimals: GQConstants.EQUATION_DECIMALS
      }, options );

      const path = new Path( null, {
        stroke: options.color,
        lineWidth: GQConstants.AXIS_OF_SYMMETRY_LINE_WIDTH,
        lineDash: GQConstants.AXIS_OF_SYMMETRY_LINE_DASH
      } );

      const equationNode = new RichText( '', {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: options.color
      } );

      assert && assert( !options.children, 'Directrix sets children' );
      options.children = [ path, equationNode ];

      super( options );

      const minX = modelViewTransform.modelToViewX( graph.xRange.min );
      const maxX = modelViewTransform.modelToViewX( graph.xRange.max );

      quadraticProperty.link( quadratic => {

        assert && assert( quadratic.directrix !== undefined, 'undefined directrix is not supported' );
        assert && assert( quadratic.vertex !== undefined, 'undefined vertex is not supported' );

        // update the line
        const y = modelViewTransform.modelToViewY( quadratic.directrix );
        path.shape = new Shape().moveTo( minX, y ).lineTo( maxX, y );

        // update the equation
        equationNode.text = StringUtils.fillIn( '{{y}} = {{value}}', {
          y: GQSymbols.y,
          value: Util.toFixedNumber( quadratic.directrix, options.decimals )
        } );

        // position the equation to avoid overlapping vertex and x axis
        const xOffset = 15;
        const yOffset = 3;
        if ( quadratic.vertex.x >= 0 ) {
          equationNode.left = path.left + xOffset;
        }
        else {
          equationNode.right = path.right - xOffset;
        }

        if ( quadratic.directrix > quadratic.vertex.y ) {
          equationNode.bottom = path.top - yOffset;
        }
        else {
          equationNode.top = path.bottom + yOffset;
        }
      } );

      directrixVisibleProperty.link( visible => { this.visible = visible; } );
      equationsVisibleProperty.link( visible => { equationNode.visible = visible; } );
    }
  }

  return graphingQuadratics.register( 'DirectrixNode', DirectrixNode );
} );<commit_msg>position directrix equation 1 unit in from edge of graph<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Displays the directrix for a quadratic.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const GQSymbols = require( 'GRAPHING_QUADRATICS/common/GQSymbols' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const Node = require( 'SCENERY/nodes/Node' );
  const Path = require( 'SCENERY/nodes/Path' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Shape = require( 'KITE/Shape' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Util = require( 'DOT/Util' );

  class DirectrixNode extends Node {

    /**
     * @param {Property.<Quadratic>} quadraticProperty
     * @param {Graph} xRange - range of graph's x axis
     * @param {ModelViewTransform2} modelViewTransform
     * @param {BooleanProperty} directrixVisibleProperty
     * @param {BooleanProperty} equationsVisibleProperty
     * @param {Object} [options]
     */
    constructor( quadraticProperty, xRange, modelViewTransform, directrixVisibleProperty, equationsVisibleProperty, options ) {

      options = _.extend( {
        color: GQColors.DIRECTRIX,
        decimals: GQConstants.EQUATION_DECIMALS
      }, options );

      const path = new Path( null, {
        stroke: options.color,
        lineWidth: GQConstants.AXIS_OF_SYMMETRY_LINE_WIDTH,
        lineDash: GQConstants.AXIS_OF_SYMMETRY_LINE_DASH
      } );

      const equationNode = new RichText( '', {
        font: new PhetFont( GQConstants.GRAPH_EQUATION_FONT_SIZE ),
        fill: options.color
      } );

      assert && assert( !options.children, 'Directrix sets children' );
      options.children = [ path, equationNode ];

      super( options );

      const minX = modelViewTransform.modelToViewX( xRange.min );
      const maxX = modelViewTransform.modelToViewX( xRange.max );

      quadraticProperty.link( quadratic => {

        assert && assert( quadratic.directrix !== undefined, 'undefined directrix is not supported' );
        assert && assert( quadratic.vertex !== undefined, 'undefined vertex is not supported' );

        // update the line
        const y = modelViewTransform.modelToViewY( quadratic.directrix );
        path.shape = new Shape().moveTo( minX, y ).lineTo( maxX, y );

        // update the equation
        equationNode.text = StringUtils.fillIn( '{{y}} = {{value}}', {
          y: GQSymbols.y,
          value: Util.toFixedNumber( quadratic.directrix, options.decimals )
        } );

        // position the equation to avoid overlapping vertex and x axis
        const xOffset = 15;
        const yOffset = 3;
        if ( quadratic.vertex.x >= 0 ) {
          equationNode.left = modelViewTransform.modelToViewX( xRange.min + 1 );
        }
        else {
          equationNode.right = modelViewTransform.modelToViewX( xRange.max - 1 );
        }

        if ( quadratic.directrix > quadratic.vertex.y ) {
          equationNode.bottom = path.top - yOffset;
        }
        else {
          equationNode.top = path.bottom + yOffset;
        }
      } );

      directrixVisibleProperty.link( visible => { this.visible = visible; } );
      equationsVisibleProperty.link( visible => { equationNode.visible = visible; } );
    }
  }

  return graphingQuadratics.register( 'DirectrixNode', DirectrixNode );
} );<|endoftext|>