<commit_before>/**
 * Rainbow is a simple code syntax highlighter
 *
 * @author Craig Campbell <iamcraigcampbell@gmail.com>
 */
window.Rainbow = (function() {

    /**
     * array of replacements and positions
     *
     * @var {Object}
     */
    var replacements = {},
        replacement_positions = {},
        language_patterns = {},
        bypass_defaults = {},
        CURRENT_LEVEL = 0,
        DEFAULT_LANGUAGE = 0;

    /**
     * cross browser get attribute for an element
     *
     * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
     *
     * @param {Object} element
     * @param {String} attribute you are trying to get
     * @returns {String}
     */
    function _attr(el, attr) {
        var result = (el.getAttribute && el.getAttribute(attr)) || null;

        if (!result) {
            var attrs = el.attributes,
                length = attrs.length,
                i;

            for(i = 0; i < length; ++i) {
                if (attr[i].nodeName === attr) {
                    result = attr[i].nodeValue;
                }
            }
        }

        return result;
    }

    /**
     * determines if a new match intersects with an existing one
     *
     * @param {number} start1    start position of existing match
     * @param {number} end1      end position of existing match
     * @param {number} start2    start position of new match
     * @param {number} end2      end position of new match
     * @returns {boolean}
     */
    function _intersects(start1, end1, start2, end2) {
        if (start2 >= start1 && start2 < end1) {
            return true;
        }

        return end2 > start1 && end2 < end1;
    }

    /**
     * determines if two different matches have complete overlap with each other
     *
     * @param {number} start1   start position of existing match
     * @param {number} end1     end position of existing match
     * @param {number} start2   start position of new match
     * @param {number} end2     end position of new match
     * @returns {boolean}
     */
    function _hasCompleteOverlap(start1, end1, start2, end2) {
        if (start2 == start1 && end2 == end1) {
            return false;
        }

        return start2 <= start1 && end2 >= end1;
    }

    /**
     * determines if the match passed in falls inside of an existing match
     * this prevents a regex pattern from matching inside of a bigger pattern
     *
     * @param {number} start - start position of new match
     * @param {number} end - end position of new match
     * @returns {boolean}
     */
    function _matchIsInsideOtherMatch(start, end) {
        for (var key in replacement_positions[CURRENT_LEVEL]) {

            // if this block completely overlaps with another block
            // then we should remove the other block and return false
            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
                delete replacement_positions[CURRENT_LEVEL][key];
                delete replacements[CURRENT_LEVEL][key];
            }

            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
                return true;
            }
        }

        return false;
    }

    /**
     * takes a string of code and wraps it in a span tag based on the name
     *
     * @param {string} name     name of the pattern (ie keyword.regex)
     * @param {string} code     block of code to wrap
     * @returns {string}
     */
    function _wrapCodeInSpan(name, code) {
        return '' + code + '';
    }

    /**
     * finds out the position of group match for a regular expression
     *
     * @param {RegExp} match
     * @param {number} group_number
     * @returns {number}
     */
    function _indexOfGroup(match, group_number) {
        var index= match.index;
        for (var i = 1; i < group_number; ++i) {
            index += match[i].length;
        }
        return index;
    }

    /**
     * matches a regex pattern against a block of code
     * finds all matches that should be processed and stores the positions
     * of where they should be replaced within the string
     *
     * this is where pretty much all the work is done but it should not
     * be called directly
     *
     * @param {RegExp} pattern
     * @param {string} code
     * @returns void
     */
    function _processPattern(regex, pattern, code, callback)
    {
        var match = regex.exec(code);

        if (!match) {
            return callback();
        }

        var replacement = match[0],
            start_pos = match.index,
            end_pos = match[0].length + start_pos;

        /**
         * callback to process the next match of this pattern
         */
        var processNext = function() {
            setTimeout(function() {
                _processPattern(regex, pattern, code, callback);
            }, 1);
        };

        // if this is not a child match and it falls inside of another
        // match that already happened we should skip it and continue processing
        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
            return processNext();
        }

        /**
         * callback for when a match was successfully processed
         */
        var onMatchSuccess = function(replacement) {
            // if this match has a name then wrap it in a span tag
            if (pattern['name']) {
                replacement = _wrapCodeInSpan(pattern['name'], replacement);
            }

            console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);

            // store what needs to be replaced with what at this position
            if (!replacements[CURRENT_LEVEL]) {
                replacements[CURRENT_LEVEL] = {};
                replacement_positions[CURRENT_LEVEL] = {};
            }

            replacements[CURRENT_LEVEL][start_pos] = {
                'replace': match[0],
                'with': replacement
            };

            // store the range of this match so we can use it for comparisons
            // with other matches later
            replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;

            // process the next match
            processNext();
        };

        // if this pattern has sub matches for different groups in the regex
        // then we should process them one at a time by rerunning them through
        // this function to generate the new replacement
        var group_keys = keys(pattern['matches']);

        /**
         * callback for processing a sub group
         */
        var processGroup = function(i, group_keys, callback) {
            if (i >= group_keys.length) {
                return callback(replacement);
            }

            // if there is no match here then move on
            if (!match[group_keys[i]]) {
                return processGroup(++i, group_keys, callback);
            }

            var group = pattern['matches'][group_keys[i]],
                block = match[group_keys[i]];

            // if this is a sublanguage go and process the block using that language
            var language = group['language'];
            if (language) {
                _highlightBlockForLanguage(block, language, function(code) {
                    replacement = replacement.replace(block, code);
                    return processGroup(++i, group_keys, callback);
                });
                return;
            }

            // if this is a submatch go and process the block using the specified pattern
            var name = group['name'];
            if (name) {
                _processCodeWithPatterns(block, [{
                    'name': group['name'],
                    'pattern': group['pattern']
                }], function(code) {
                    replacement = replacement.replace(block, code);
                    return processGroup(++i, group_keys, callback);
                });
                return;
            }

            var group_match_position = _indexOfGroup(match, group_keys[i]) - match.index;
            replacement = _replaceAtPosition(group_match_position, match[group_keys[i]], _wrapCodeInSpan(group, match[group_keys[i]]), replacement);
            return processGroup(++i, group_keys, callback);
        };

        processGroup(0, group_keys, onMatchSuccess);
        // onMatchSuccess(replacement);

    }

    /**
     * should a language bypass the default patterns?
     *
     * if you call Rainbow.extend() and pass true as the third argument
     * it will bypass the defaults
     */
    function _bypassDefaultPatterns(language)
    {
        return bypass_defaults[language] == 1;
    }

    /**
     * returns a list of regex patterns for this language
     *
     * @param {string} language
     * @returns {Array}
     */
    function _getPatternsForLanguage(language) {
        var patterns = language_patterns[language] || [],
            default_patterns = language_patterns[DEFAULT_LANGUAGE];

        if (_bypassDefaultPatterns(language)) {
            return patterns;
        }

        return patterns.concat(default_patterns);
    }

    /**
     * substring replace call to replace part of a string at a certain position
     *
     * @param {number} position         the position where the replacement should happen
     * @param {string} replace          the text we want to replace
     * @param {string} replace_with     the text we want to replace it with
     * @param {string} code             the code we are doing the replacing in
     * @returns {string}
     */
    function _replaceAtPosition(position, replace, replace_with, code) {
        var sub_string = code.substr(position);
        code = code.replace(sub_string, sub_string.replace(replace, replace_with));
        return code;
    }

   /**
     * sorts an object by index descending
     *
     * @param {Object} object
     * @return {array}
     */
    function keys(object) {
        var locations = [],
            replacement,
            pos;

        for(var location in object) {
            if (object.hasOwnProperty(location)) {
                locations.push(location);
            }
        }

        // numeric descending
        return locations.sort(function(a,b) {
            return b - a;
        });
    }

    /**
     * processes a block of code using specified patterns
     *
     * @param {string} code
     * @param {array} patterns
     * @returns {string}
     */
    function _processCodeWithPatterns(code, patterns, callback)
    {
        // we have to increase the level here so that the
        // replacements will not conflict with each other when
        // processing sub blocks of code
        ++CURRENT_LEVEL;

        function _workOnPatterns(patterns, i, onComplete)
        {
            if (i < patterns.length) {
                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
                    setTimeout(function () {
                        _workOnPatterns(patterns, ++i, onComplete);
                    }, 1);
                });
            }
            onComplete();
        }

        var onComplete = function() {

            // actually do the replacing
            var string_positions = keys(replacements[CURRENT_LEVEL]);
            for (i = 0; i < string_positions.length; ++i) {
                pos = string_positions[i];
                replacement = replacements[CURRENT_LEVEL][pos];
                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
            }

            // we are done at this level so we can go back down
            delete replacements[CURRENT_LEVEL];
            delete replacement_positions[CURRENT_LEVEL];
            --CURRENT_LEVEL;
            callback(code);
        };

        _workOnPatterns(patterns, 0, onComplete);
    }

    /**
     * takes a string of code and highlights it according to the language specified
     *
     * @param {string} code
     * @param {string} language
     * @returns {string}
     */
    function _highlightBlockForLanguage(code, language, callback) {
        var patterns = _getPatternsForLanguage(language);
        _processCodeWithPatterns(code, patterns, function (code) {
            callback(code);
        });
    }

    /**
     * highlight an individual code block
     *
     * @param {Element} code_block
     * @returns void
     */
    function _highlightCodeBlock(code_blocks, i) {
        if (i >= code_blocks.length) {
            return;
        }

        var language = _attr(code_blocks[i], 'data-language').toLowerCase();

        if (language) {
            code_blocks[i].className = language;

            _highlightBlockForLanguage(code_blocks[i].innerHTML, language, function (code) {
                code_blocks[i].innerHTML = code;

                // reset the replacement arrays
                replacements = {};
                replacement_positions = {};

                _highlightCodeBlock(code_blocks, ++i);
            });
        }
    }

    /**
     * start highlighting all the code blocks
     *
     * @returns void
     */
    function _highlight() {
        var code_blocks = document.getElementsByTagName('code');
        _highlightCodeBlock(code_blocks, 0);
    }

    return {

        /**
         * extends the language pattern matches
         *
         * @param {string} language     name of language
         * @param {Array} patterns      array of patterns to add on
         * @param {boolean} bypass      if true this will bypass the default language patterns
         */
        extend: function(language, patterns, bypass) {

            // if there is only one argument then we assume that we want to
            // extend the default language rules
            if (arguments.length == 1) {
                patterns = language;
                language = DEFAULT_LANGUAGE;
            }

            if (bypass) {
                bypass_defaults[language] = 1;
            }

            language_patterns[language] = patterns.concat(language_patterns[language] || []);
        },

        /**
         * starts the magic rainbow
         *
         * @returns void
         */
        init: function() {
            _highlight();
        }
    };
}) ();

/**
 * adds event listener to start highlighting
 */
(function (w) {
    if (w.addEventListener) {
        return w.addEventListener('load', Rainbow.init, false);
    }
    w.attachEvent('onload', Rainbow.init);
}) (window);

/**
 * add the default language patterns
 */
Rainbow.extend([
    {
        'name': 'comment',
        'pattern': /\/\*[\s\S]*?\*\/|\/\/[\s\S]*?$|\#[\s\S]*?$/gm
    },
    {
        'matches': {
            2: 'string'
        },
        'pattern': /(\(|\s|\[)(('|")[\s\S]*?(\3))/gm
    },
    {
        'name': 'integer',
        'pattern': /\b(0x[\da-f]+|\d+)\b/g
    },
    {
        'name': 'constant',
        'pattern': /\b[A-Z0-9_]{2,}\b/g
    },
    {
        'matches': {
            1: 'keyword'
        },
        'pattern': /\b(and|or|xor|import|print|echo|def|for|do|while|foreach|as|return|die|exit|if|then|else|elseif|new|delete|try|throw|catch|finally|class|function|string|array|object|resource|var|bool|boolean|int|integer|float|double|real|string|array|global|const|static|public|private|protected|published|extends|switch|void|this|self|struct|char|signed|unsigned|short|long)(?=\(|\b)/g
    },
    {
        'name': 'constant.language',
        'pattern': /true|false|null/g
    },
    {
        'name': 'keyword.operator',
        'pattern': /\+|\!|\-|>|<|&|\||\*|\=/g
    },
    {
        'matches': {
            1: 'function.call'
        },
        'pattern': /(\w+?)(?=\()/g
    },
    {
        'matches': {
            1: 'keyword',
            2: 'meta.function-call'
        },
        'pattern': /(function)\s(.*?)(?=\()/g
    }
]);
<commit_msg>A couple of updates<commit_after>/**
 * Rainbow is a simple code syntax highlighter
 *
 * @author Craig Campbell <iamcraigcampbell@gmail.com>
 */
window.Rainbow = (function() {

    /**
     * array of replacements and positions
     *
     * @var {Object}
     */
    var replacements = {},
        replacement_positions = {},
        language_patterns = {},
        bypass_defaults = {},
        CURRENT_LEVEL = 0,
        DEFAULT_LANGUAGE = 0;

    /**
     * cross browser get attribute for an element
     *
     * @see http://stackoverflow.com/questions/3755227/cross-browser-javascript-getattribute-method
     *
     * @param {Object} element
     * @param {String} attribute you are trying to get
     * @returns {String}
     */
    function _attr(el, attr) {
        var result = (el.getAttribute && el.getAttribute(attr)) || null;

        if (!result) {
            var attrs = el.attributes,
                length = attrs.length,
                i;

            for(i = 0; i < length; ++i) {
                if (attr[i].nodeName === attr) {
                    result = attr[i].nodeValue;
                }
            }
        }

        return result;
    }

    /**
     * determines if a new match intersects with an existing one
     *
     * @param {number} start1    start position of existing match
     * @param {number} end1      end position of existing match
     * @param {number} start2    start position of new match
     * @param {number} end2      end position of new match
     * @returns {boolean}
     */
    function _intersects(start1, end1, start2, end2) {
        if (start2 >= start1 && start2 < end1) {
            return true;
        }

        return end2 > start1 && end2 < end1;
    }

    /**
     * determines if two different matches have complete overlap with each other
     *
     * @param {number} start1   start position of existing match
     * @param {number} end1     end position of existing match
     * @param {number} start2   start position of new match
     * @param {number} end2     end position of new match
     * @returns {boolean}
     */
    function _hasCompleteOverlap(start1, end1, start2, end2) {
        if (start2 == start1 && end2 == end1) {
            return false;
        }

        return start2 <= start1 && end2 >= end1;
    }

    /**
     * determines if the match passed in falls inside of an existing match
     * this prevents a regex pattern from matching inside of a bigger pattern
     *
     * @param {number} start - start position of new match
     * @param {number} end - end position of new match
     * @returns {boolean}
     */
    function _matchIsInsideOtherMatch(start, end) {
        for (var key in replacement_positions[CURRENT_LEVEL]) {

            // if this block completely overlaps with another block
            // then we should remove the other block and return false
            if (_hasCompleteOverlap(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
                delete replacement_positions[CURRENT_LEVEL][key];
                delete replacements[CURRENT_LEVEL][key];
            }

            if (_intersects(key, replacement_positions[CURRENT_LEVEL][key], start, end)) {
                return true;
            }
        }

        return false;
    }

    /**
     * takes a string of code and wraps it in a span tag based on the name
     *
     * @param {string} name     name of the pattern (ie keyword.regex)
     * @param {string} code     block of code to wrap
     * @returns {string}
     */
    function _wrapCodeInSpan(name, code) {
        return '' + code + '';
    }

    /**
     * finds out the position of group match for a regular expression
     *
     * @param {RegExp} match
     * @param {number} group_number
     * @returns {number}
     */
    function _indexOfGroup(match, group_number) {
        var index = match.index;
        for (var i = 1; i < group_number; ++i) {
            index += match[i].length;
        }
        return index;
    }

    /**
     * matches a regex pattern against a block of code
     * finds all matches that should be processed and stores the positions
     * of where they should be replaced within the string
     *
     * this is where pretty much all the work is done but it should not
     * be called directly
     *
     * @param {RegExp} pattern
     * @param {string} code
     * @returns void
     */
    function _processPattern(regex, pattern, code, callback)
    {
        var match = regex.exec(code);

        if (!match) {
            return callback();
        }

        var replacement = match[0],
            start_pos = match.index,
            end_pos = match[0].length + start_pos;

        /**
         * callback to process the next match of this pattern
         */
        var processNext = function() {
            setTimeout(function() {
                _processPattern(regex, pattern, code, callback);
            }, 0);
        };

        // if this is not a child match and it falls inside of another
        // match that already happened we should skip it and continue processing
        if (_matchIsInsideOtherMatch(start_pos, end_pos)) {
            return processNext();
        }

        /**
         * callback for when a match was successfully processed
         */
        var onMatchSuccess = function(replacement) {
            // if this match has a name then wrap it in a span tag
            if (pattern['name']) {
                replacement = _wrapCodeInSpan(pattern['name'], replacement);
            }

            // console.log('LEVEL', CURRENT_LEVEL, 'replace', match[0], 'with', replacement, 'at position', start_pos, 'to', end_pos);

            // store what needs to be replaced with what at this position
            if (!replacements[CURRENT_LEVEL]) {
                replacements[CURRENT_LEVEL] = {};
                replacement_positions[CURRENT_LEVEL] = {};
            }

            replacements[CURRENT_LEVEL][start_pos] = {
                'replace': match[0],
                'with': replacement
            };

            // store the range of this match so we can use it for comparisons
            // with other matches later
            replacement_positions[CURRENT_LEVEL][start_pos] = end_pos;

            // process the next match
            processNext();
        };

        // if this pattern has sub matches for different groups in the regex
        // then we should process them one at a time by rerunning them through
        // this function to generate the new replacement
        var group_keys = keys(pattern['matches']);

        /**
         * callback for processing a sub group
         */
        var processGroup = function(i, group_keys, callback) {
            if (i >= group_keys.length) {
                return callback(replacement);
            }

            var processNextGroup = function() {
                processGroup(++i, group_keys, callback);
            };

            // if there is no match here then move on
            if (!match[group_keys[i]]) {
                return processNextGroup();
            }

            var group = pattern['matches'][group_keys[i]],
                block = match[group_keys[i]];

            // if this is a sublanguage go and process the block using that language
            var language = group['language'];
            if (language) {
                _highlightBlockForLanguage(block, language, function(code) {
                    replacement = replacement.replace(block, code);
                    processNextGroup();
                });
                return;
            }

            // if this is a submatch go and process the block using the specified pattern
            var name = group['name'];
            if (name) {
                _processCodeWithPatterns(block, [{
                    'name': group['name'],
                    'pattern': group['pattern']
                }], function(code) {
                    replacement = replacement.replace(block, code);
                    processNextGroup();
                });
                return;
            }

            var group_match_position = _indexOfGroup(match, group_keys[i]) - match.index;
            replacement = _replaceAtPosition(group_match_position, match[group_keys[i]], _wrapCodeInSpan(group, match[group_keys[i]]), replacement);
            return processNextGroup();
        };

        processGroup(0, group_keys, onMatchSuccess);
    }

    /**
     * should a language bypass the default patterns?
     *
     * if you call Rainbow.extend() and pass true as the third argument
     * it will bypass the defaults
     */
    function _bypassDefaultPatterns(language)
    {
        return bypass_defaults[language] == 1;
    }

    /**
     * returns a list of regex patterns for this language
     *
     * @param {string} language
     * @returns {Array}
     */
    function _getPatternsForLanguage(language) {
        var patterns = language_patterns[language] || [],
            default_patterns = language_patterns[DEFAULT_LANGUAGE];

        if (_bypassDefaultPatterns(language)) {
            return patterns;
        }

        return patterns.concat(default_patterns);
    }

    /**
     * substring replace call to replace part of a string at a certain position
     *
     * @param {number} position         the position where the replacement should happen
     * @param {string} replace          the text we want to replace
     * @param {string} replace_with     the text we want to replace it with
     * @param {string} code             the code we are doing the replacing in
     * @returns {string}
     */
    function _replaceAtPosition(position, replace, replace_with, code) {
        var sub_string = code.substr(position);
        code = code.replace(sub_string, sub_string.replace(replace, replace_with));
        return code;
    }

   /**
     * sorts an object by index descending
     *
     * @param {Object} object
     * @return {array}
     */
    function keys(object) {
        var locations = [],
            replacement,
            pos;

        for(var location in object) {
            if (object.hasOwnProperty(location)) {
                locations.push(location);
            }
        }

        // numeric descending
        return locations.sort(function(a,b) {
            return b - a;
        });
    }

    /**
     * processes a block of code using specified patterns
     *
     * @param {string} code
     * @param {array} patterns
     * @returns {string}
     */
    function _processCodeWithPatterns(code, patterns, callback)
    {
        // we have to increase the level here so that the
        // replacements will not conflict with each other when
        // processing sub blocks of code
        ++CURRENT_LEVEL;

        function _workOnPatterns(patterns, i, onComplete)
        {
            if (i < patterns.length) {
                return _processPattern(patterns[i]['pattern'], patterns[i], code, function() {
                    setTimeout(function () {
                        _workOnPatterns(patterns, ++i, onComplete);
                    }, 0);
                });
            }
            onComplete();
        }

        var onComplete = function() {
            // actually do the replacing
            var string_positions = keys(replacements[CURRENT_LEVEL]);
            for (i = 0; i < string_positions.length; ++i) {
                pos = string_positions[i];
                replacement = replacements[CURRENT_LEVEL][pos];
                code = _replaceAtPosition(pos, replacement['replace'], replacement['with'], code);
            }

            // we are done at this level so we can go back down
            delete replacements[CURRENT_LEVEL];
            delete replacement_positions[CURRENT_LEVEL];
            --CURRENT_LEVEL;
            callback(code);
        };

        _workOnPatterns(patterns, 0, onComplete);
    }

    /**
     * takes a string of code and highlights it according to the language specified
     *
     * @param {string} code
     * @param {string} language
     * @returns {string}
     */
    function _highlightBlockForLanguage(code, language, callback) {
        var patterns = _getPatternsForLanguage(language);
        _processCodeWithPatterns(code, patterns, function (code) {
            callback(code);
        });
    }

    /**
     * highlight an individual code block
     *
     * @param {Element} code_block
     * @returns void
     */
    function _highlightCodeBlock(code_blocks, i) {
        if (i >= code_blocks.length) {
            return;
        }

        var language = _attr(code_blocks[i], 'data-language').toLowerCase();

        if (language) {
            code_blocks[i].className = language;

            _highlightBlockForLanguage(code_blocks[i].innerHTML, language, function (code) {
                code_blocks[i].innerHTML = code;

                // reset the replacement arrays
                replacements = {};
                replacement_positions = {};

                _highlightCodeBlock(code_blocks, ++i);
            });
        }
    }

    /**
     * start highlighting all the code blocks
     *
     * @returns void
     */
    function _highlight() {
        var code_blocks = document.getElementsByTagName('code');
        _highlightCodeBlock(code_blocks, 0);
    }

    return {

        /**
         * extends the language pattern matches
         *
         * @param {string} language     name of language
         * @param {Array} patterns      array of patterns to add on
         * @param {boolean} bypass      if true this will bypass the default language patterns
         */
        extend: function(language, patterns, bypass) {

            // if there is only one argument then we assume that we want to
            // extend the default language rules
            if (arguments.length == 1) {
                patterns = language;
                language = DEFAULT_LANGUAGE;
            }

            if (bypass) {
                bypass_defaults[language] = 1;
            }

            language_patterns[language] = patterns.concat(language_patterns[language] || []);
        },

        /**
         * starts the magic rainbow
         *
         * @returns void
         */
        init: function() {
            _highlight();
        }
    };
}) ();

/**
 * adds event listener to start highlighting
 */
(function (w) {
    if (w.addEventListener) {
        return w.addEventListener('load', Rainbow.init, false);
    }
    w.attachEvent('onload', Rainbow.init);
}) (window);

/**
 * add the default language patterns
 */
Rainbow.extend([
    {
        'name': 'comment',
        'pattern': /\/\*[\s\S]*?\*\/|\/\/[\s\S]*?$|\#[\s\S]*?$/gm
    },
    {
        'matches': {
            2: 'string'
        },
        'pattern': /(\(|\s|\[)(('|")[\s\S]*?(\3))/gm
    },
    {
        'name': 'integer',
        'pattern': /\b(0x[\da-f]+|\d+)\b/g
    },
    {
        'name': 'constant',
        'pattern': /\b[A-Z0-9_]{2,}\b/g
    },
    {
        'matches': {
            1: 'keyword'
        },
        'pattern': /\b(and|or|xor|import|print|echo|def|for|do|while|foreach|as|return|die|exit|if|then|else|elseif|new|delete|try|throw|catch|finally|class|function|string|array|object|resource|var|bool|boolean|int|integer|float|double|real|string|array|global|const|static|public|private|protected|published|extends|switch|void|this|self|struct|char|signed|unsigned|short|long)(?=\(|\b)/g
    },
    {
        'name': 'constant.language',
        'pattern': /true|false|null/g
    },
    {
        'name': 'keyword.operator',
        'pattern': /\+|\!|\-|>|<|&|\||\*|\=/g
    },
    {
        'matches': {
            1: 'function.call'
        },
        'pattern': /(\w+?)(?=\()/g
    },
    {
        'matches': {
            1: 'keyword',
            2: 'meta.function-call'
        },
        'pattern': /(function)\s(.*?)(?=\()/g
    }
]);
<|endoftext|>


<commit_before> var player;
            var vid;
            var videos=[];
            var current=0;
            var hash;
            var ui_pinned=false;
            var after_id;
            var chanShowTimeOut;
            // Suggestions Welcome! Please Sort Alphabetically
            // Except First two.
            var channels=[
                    'YoutubeHaiku',
                    'Videos',

                    'AccidentalComedy',
                    'ArtisanVideos',
                    'AwfulCommercials',
                    'CombatFootage',
                    'CommercialCuts',
                    'ContagiousLaughter',
                    'ConTalks',
                    'CookingVideos',
                    'Cringe',
                    'CuriousVideos',
                    'DeepIntoYouTube',
                    'Documentaries',
                    'EducativeVideos',
                    'FastWorkers',
                    'fifthworldvideos',
                    'FightPorn',
                    'FuckingWithNature',
                    'HappyCrowds',
                    'IdiotsFightingThings',
                    'InterdimensionalCable',
                    'Lectures',
                    'MealtimeVideos',
                    'MotivationVideos',
                    'Music',
                    'ObscureMedia',
                    'Playitagainsam',
                    'PrematureCelebration',
                    'PublicFreakout',
                    'RoadCam',
                    'StandUpComedy',
                    'StreetFights',
                    'SweetJustice',
                    'TheWayWeWereOnVideo',
                    'Trailers',
                    'UnexpectedThugLife',
                    'VideoPorn',
                    'Vids',
                    'Virtualfreakout',
                    'WoahTube',
            ];
            var subreddit=channels[0];
            // autoplay video
            function onPlayerReady(event) {
                event.target.playVideo();
            }

            function showChannelName () {
                try{clearTimeout(chanShowTimeOut);}catch(e){}
                $("#channel-name").hide();
                $("#channel-name").text("/r/"+window.location.hash.substring(1));
                $("#channel-name").show();
                chanShowTimeOut = setTimeout(function() { $("#channel-name").hide(); }, 2000);
            }

            function togglePin () {
                ui_pinned=!ui_pinned;
            }

            // when video ends
            function onPlayerStateChange(event) {
                if(event.data === 0) {
                    nextVideo();
                }
            }

            function nextVideo () {
                try{
                    player.destroy();
                }catch(e){}
                showLoading();
                playVideo(videos[current]);
                current+=1;
                if(current>videos.length-1){
                    fetchRedditPage (subreddit,after_id);
                }
            }

            function showLoading () {
                $('#title').html(''); //
                $('#desc').html('');
            }

            function previousVideo () {
                if(current>0){
                    try{
                        player.destroy();
                    }catch(e){}
                    $('#title').html('');//
                    $('#desc').html("");
                    current-=1;
                    playVideo(videos[current]);
                    if(current>videos.length-1){
                        fetchRedditPage (subreddit,after_id);
                    }
                }
            }

            $(document).ready(function() {
                showLoading();
                for (var i = 0; i < channels.length; i++) {
                    $('#channels-list').html($('#channels-list').html()+'
r/'+channels[i]+'
');
                }

                hash = window.location.hash.substring(1);
                if(!hash)
                    window.location.hash=subreddit;
                else{
                    subreddit=hash;
                    fetchRedditPage(subreddit);
                }
                showChannelName();

                // prevent missing videos from stopping
                // auto playback
                setInterval(function(){
                    if(player)
                        if(player.getPlayerState()==-1)
                            nextVideo();
                },10000);

            });

            function toggleChannels () {
                $("#channels").fadeToggle();
            }

            function closeChannels() {
                $('#channels').fadeOut();
            }

            $(window).on('hashchange', function() {
                showLoading();
                //closeChannels();
                subreddit = window.location.hash.substring(1);
                if(player)
                    player.destroy();
                videos=[];
                current=0;
                after_id=null;
                fetchRedditPage(subreddit);

            });

            $(window).keydown(function (e) {
              if (e.keyCode === 39) {
                e.preventDefault();
                nextVideo();
              }else if (e.keyCode === 37) {
                e.preventDefault();
                previousVideo();
              }else if (e.keyCode === 99|| e.keyCode === 67) {
                e.preventDefault();
                toggleChannels();
              }else if (e.keyCode === 27) {
                e.preventDefault();
                closeChannels();
              }else if (e.keyCode === 38) {
                e.preventDefault();
                channelUp();
              }else if (e.keyCode === 40) {
                e.preventDefault();
                channelDown();
              }else if (e.keyCode === 32 || e.keyCode === 75|| e.keyCode === 107) {
                e.preventDefault();
                togglePlay();
              }
            });

            function channelUp () {
                chnum = channels.indexOf(subreddit);
                chnum-=1;
                if(chnum>=0){
                    window.location.hash=channels[chnum];
                    changeChannelIndicator(channels[chnum]);
                }
                showChannelName ();
            }

            function channelDown () {
                chnum = channels.indexOf(subreddit);
                chnum+=1;
                if(chnum<channels.length){
                    window.location.hash=channels[chnum];
                    changeChannelIndicator(channels[chnum]);
                }
                showChannelName ();
            }

            function changeChannelIndicator (ch) {
                $(".channel-button").removeClass('selected-channel');
                $("#"+ch+"-button").addClass('selected-channel');
            }

            function fetchRedditPage (subreddit,after) {
                changeChannelIndicator(subreddit);
                var link='https://www.reddit.com/r/'+subreddit+'.json?after='+after;
                $.get(link,function(s){
                    for (var i = 0; i < s.data.children.length; i++) {
                        try{
                            var video={};
                            if(s.data.children[i].data.domain==='youtube.com'){
                                video.type='youtube.com';
                                video.id=s.data.children[i].data.media.oembed.html.split('embed/')[1].split('?')[0];
                                video.title=s.data.children[i].data.media.oembed.title;
                                video.author=s.data.children[i].data.author;
                                video.score=s.data.children[i].data.score;
                                video.permalink=s.data.children[i].data.permalink;
                                videos.push(video);
                            }

                        }catch(e){
                            //TODO: help! i need more video embed support!
                            console.log(e,'Currently not supported: ',s.data.children[i]);
                        }
                    }
                    if(!after_id){
                        playVideo(videos[current]);
                        current+=1;
                    }
                    after_id=s.data.after;
                });
            }

            function togglePlay () {
                if(player.getPlayerState()==2){
                    player.playVideo();
                    $("#playbutton").removeClass();
                    $("#playbutton").addClass('ic-button fa fa-pause');
                }
                else if(player.getPlayerState()==1){
                    player.pauseVideo();
                    $("#playbutton").removeClass();
                    $("#playbutton").addClass('ic-button fa fa-play');
                }

            }

            function playVideo (video) {
                  player = new YT.Player('player', {
                  height: $(window).height(),
                  width: $(window).width(),
                  videoId: video.id,
                  playerVars: {
                         'controls': 0,
                         'rel' : 0,
                         'showinfo':0,
                         'iv_load_policy':3,
                         'autoplay': 1,
                  },
                  events: {
                    'onReady': onPlayerReady,
                    'onStateChange': onPlayerStateChange
                  }
                });
                $('#title').text(video.title);
                // &nbsp&nbsp
                // un pinning ui
                $('#desc').html('&nbsp·&nbsp&nbsp&nbsp&nbsp&nbsp · /r/'+subreddit+" · /u/"+video.author+' ·  '+video.score);
                $("#infolink").attr("href", "http://www.reddit.com"+video.permalink);
            }<commit_msg>fixed bug for embedly embeds<commit_after> var player;
            var vid;
            var videos=[];
            var current=0;
            var hash;
            var ui_pinned=false;
            var after_id;
            var chanShowTimeOut;
            // Suggestions Welcome! Please Sort Alphabetically
            // Except First two.
            var channels=[
                    'YoutubeHaiku',
                    'Videos',

                    'AccidentalComedy',
                    'ArtisanVideos',
                    'AwfulCommercials',
                    'CombatFootage',
                    'CommercialCuts',
                    'ContagiousLaughter',
                    'ConTalks',
                    'CookingVideos',
                    'Cringe',
                    'CuriousVideos',
                    'DeepIntoYouTube',
                    'Documentaries',
                    'EducativeVideos',
                    'FastWorkers',
                    'fifthworldvideos',
                    'FightPorn',
                    'FuckingWithNature',
                    'HappyCrowds',
                    'IdiotsFightingThings',
                    'InterdimensionalCable',
                    'Lectures',
                    'MealtimeVideos',
                    'MotivationVideos',
                    'Music',
                    'ObscureMedia',
                    'Playitagainsam',
                    'PrematureCelebration',
                    'PublicFreakout',
                    'RoadCam',
                    'StandUpComedy',
                    'StreetFights',
                    'SweetJustice',
                    'TheWayWeWereOnVideo',
                    'Trailers',
                    'UnexpectedThugLife',
                    'VideoPorn',
                    'Vids',
                    'Virtualfreakout',
                    'WoahTube',
            ];
            var subreddit=channels[0];
            // autoplay video
            function onPlayerReady(event) {
                event.target.playVideo();
            }

            function showChannelName () {
                try{clearTimeout(chanShowTimeOut);}catch(e){}
                $("#channel-name").hide();
                $("#channel-name").text("/r/"+window.location.hash.substring(1));
                $("#channel-name").show();
                chanShowTimeOut = setTimeout(function() { $("#channel-name").hide(); }, 2000);
            }

            function togglePin () {
                ui_pinned=!ui_pinned;
            }

            // when video ends
            function onPlayerStateChange(event) {
                if(event.data === 0) {
                    nextVideo();
                }
            }

            function nextVideo () {
                try{
                    player.destroy();
                }catch(e){}
                showLoading();
                playVideo(videos[current]);
                current+=1;
                if(current>videos.length-1){
                    fetchRedditPage (subreddit,after_id);
                }
            }

            function showLoading () {
                $('#title').html(''); //
                $('#desc').html('');
            }

            function previousVideo () {
                if(current>0){
                    try{
                        player.destroy();
                    }catch(e){}
                    $('#title').html('');//
                    $('#desc').html("");
                    current-=1;
                    playVideo(videos[current]);
                    if(current>videos.length-1){
                        fetchRedditPage (subreddit,after_id);
                    }
                }
            }

            $(document).ready(function() {
                showLoading();
                for (var i = 0; i < channels.length; i++) {
                    $('#channels-list').html($('#channels-list').html()+'
r/'+channels[i]+'
');
                }

                hash = window.location.hash.substring(1);
                if(!hash)
                    window.location.hash=subreddit;
                else{
                    subreddit=hash;
                    fetchRedditPage(subreddit);
                }
                showChannelName();

                // prevent missing videos from stopping
                // auto playback
                setInterval(function(){
                    if(player)
                        if(player.getPlayerState()==-1)
                            nextVideo();
                },10000);

            });

            function toggleChannels () {
                $("#channels").fadeToggle();
            }

            function closeChannels() {
                $('#channels').fadeOut();
            }

            $(window).on('hashchange', function() {
                showLoading();
                //closeChannels();
                subreddit = window.location.hash.substring(1);
                if(player)
                    player.destroy();
                videos=[];
                current=0;
                after_id=null;
                fetchRedditPage(subreddit);

            });

            $(window).keydown(function (e) {
              if (e.keyCode === 39) {
                e.preventDefault();
                nextVideo();
              }else if (e.keyCode === 37) {
                e.preventDefault();
                previousVideo();
              }else if (e.keyCode === 99|| e.keyCode === 67) {
                e.preventDefault();
                toggleChannels();
              }else if (e.keyCode === 27) {
                e.preventDefault();
                closeChannels();
              }else if (e.keyCode === 38) {
                e.preventDefault();
                channelUp();
              }else if (e.keyCode === 40) {
                e.preventDefault();
                channelDown();
              }else if (e.keyCode === 32 || e.keyCode === 75|| e.keyCode === 107) {
                e.preventDefault();
                togglePlay();
              }
            });

            function channelUp () {
                chnum = channels.indexOf(subreddit);
                chnum-=1;
                if(chnum>=0){
                    window.location.hash=channels[chnum];
                    changeChannelIndicator(channels[chnum]);
                }
                showChannelName ();
            }

            function channelDown () {
                chnum = channels.indexOf(subreddit);
                chnum+=1;
                if(chnum<channels.length){
                    window.location.hash=channels[chnum];
                    changeChannelIndicator(channels[chnum]);
                }
                showChannelName ();
            }

            function changeChannelIndicator (ch) {
                $(".channel-button").removeClass('selected-channel');
                $("#"+ch+"-button").addClass('selected-channel');
            }

            function fetchRedditPage (subreddit,after) {
                changeChannelIndicator(subreddit);
                var link='https://www.reddit.com/r/'+subreddit+'.json?after='+after;
                $.get(link,function(s){
                    for (var i = 0; i < s.data.children.length; i++) {
                        try{
                            var video={};
                            if(s.data.children[i].data.domain.indexOf('youtu')!=-1 ){
                                video.type='youtube.com';
                                embed_code=s.data.children[i].data.media.oembed.html;
                                if(embed_code.split('embed/').length>1)
                                    video.id=embed_code.split('embed/')[1].split('?')[0];
                                else
                                    video.id = s.data.children[i].data.media.oembed.url.split("v=")[1];
                                video.title=s.data.children[i].data.media.oembed.title;
                                video.author=s.data.children[i].data.author;
                                video.score=s.data.children[i].data.score;
                                video.permalink=s.data.children[i].data.permalink;
                                videos.push(video);
                            }

                        }catch(e){
                            //TODO: help! i need more video embed support!
                            //I think Vines should be easy
                            console.log(e,'Currently not supported: ',s.data.children[i]);
                        }
                    }
                    if(!after_id){
                        playVideo(videos[current]);
                        current+=1;
                    }
                    after_id=s.data.after;
                });
            }

            function togglePlay () {
                if(player.getPlayerState()==2){
                    player.playVideo();
                    $("#playbutton").removeClass();
                    $("#playbutton").addClass('ic-button fa fa-pause');
                }
                else if(player.getPlayerState()==1){
                    player.pauseVideo();
                    $("#playbutton").removeClass();
                    $("#playbutton").addClass('ic-button fa fa-play');
                }

            }

            function playVideo (video) {
                  player = new YT.Player('player', {
                  height: $(window).height(),
                  width: $(window).width(),
                  videoId: video.id,
                  playerVars: {
                         'controls': 0,
                         'rel' : 0,
                         'showinfo':0,
                         'iv_load_policy':3,
                         'autoplay': 1,
                  },
                  events: {
                    'onReady': onPlayerReady,
                    'onStateChange': onPlayerStateChange
                  }
                });
                $('#title').text(video.title);
                // &nbsp&nbsp
                // un pinning ui
                $('#desc').html('&nbsp·&nbsp&nbsp&nbsp&nbsp&nbsp · /r/'+subreddit+" · /u/"+video.author+' ·  '+video.score);
                $("#infolink").attr("href", "http://www.reddit.com"+video.permalink);
            }<|endoftext|>


<commit_before>var merge = require('merge');
var url = require('url');

var next_id = 0;
// FIXME: this object grows forever - entries are never removed.  Should they
// be removed by expiry (time), and/or a REST API call?
var jobs = {};

var Job = function () {
};

Job.prototype.doError = function (error) {
  if (!this.completionValues && !this.timedOut) {
    this.threw = error;
  }
};

Job.prototype.doCompletion = function (errorValue, successValue) {
  if (!this.threw && !this.timedOut) {
    this.completionValues = [ errorValue, successValue ];
  }
};

Job.prototype.doTimedOut = function () {
  if (!this.threw && !this.completionValues) {
    this.timedOut = true;
  }
};

var region = function () {
    return process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION || process.env.AMAZON_REGION || "xx-dummy-0";
};

var defaultContextObject = function () {
  return {
    // Fixed, but reasonably representative
    functionName: "via-aws-lambda-runner",
    functionVersion: "$LATEST",
    invokedFunctionArn: "arn:aws:lambda:" + region() + ":000000000000:function:via-aws-lambda-runner:$LATEST",
    memoryLimitInMB: 100,
    awsRequestId: "00000000-0000-0000-0000-000000000000",
    logGroupName: "/aws/lambda/via-aws-lambda-runner",
    logStreamName: "some-log-stream-name",
  };
};

var makeContextObject = function (timeout, overrides) {
  var context = merge(true, defaultContextObject(), overrides);

  var approximateEndTime = (new Date().getTime()) + timeout;

  context.getRemainingTimeInMillis = function () {
    return approximateEndTime - (new Date().getTime());
  };

  context.fail = function(err) { context.done(err, null); };
  context.succeed = function(data) { context.done(null, data); };

  return context;
};

var startJob = function (job, requestObject, handler, opts) {
  setTimeout(function() {
    job.doTimedOut();
  }, opts.timeout);

  var event = requestObject.event;

  var context = makeContextObject(opts.timeout, requestObject.context || {});
  context.done = function (err, result) {
    job.doCompletion(err, result);
    if (err) {
      console.warn('Error:', err);
    }
  };

  try {
    handler(event, context);
  } catch (e) {
    console.log("Handler crashed", e);
    job.doError(e);
  }
};

var doCreateJob = function (req, res, opts, handler) {
  var id = ++next_id;
  var job = jobs[id] = new Job();

  var requestBody = '';

  req.on('data', function(chunk) {
    requestBody += chunk.toString();
  });

  req.on('end', function() {
    var requestObject;

    try {
      requestObject = JSON.parse(requestBody);
    } catch (e) {
      console.log("POSTed bad json: " + e.toString());
      res.writeHead(400, {'Content-Type': 'text/plain'});
      res.end(e.toString());
      return;
    }

    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(String(id));

    startJob(job, requestObject, handler, opts);
  });
};

var getJobStatus = function (result) {
  var status = null;
  var responseBody = null;

  if (result.completionValues) {
    if (result.completionValues[0] !== null && result.completionValues[0] !== undefined) {
      status = 502;
      responseBody = result.completionValues[0];
    } else {
      status = 201;
      responseBody = result.completionValues[1];
    }
  } else if (result.threw) {
    status = 500;
      responseBody = result.threw.toString();
  } else if (result.timedOut) {
    status = 504;
  } else {
    // still in progress
    status = 200;
  }

  return { status: status, data: responseBody };
};

exports.request = function(req, res, opts, handler, server) {

  if (req.method === 'POST') {

    doCreateJob(req, res, opts, handler);

  } else if (req.method === 'DELETE') {

    (function () {
      // FIXME untested
      res.writeHead(202, {'Content-Type': 'text/plain'});
      var terminationMessage = 'Terminating server at http://[localhost]:' + opts.port + ' for ' + opts['module-path'] + ' / ' + opts.handler;
      res.end(terminationMessage + '\n');
      console.info(terminationMessage);
      if (server) {
        server.close();
      }
    })();

  } else if (req.method === 'GET') {

    (function () {
      var request_id = parseInt(url.parse(req.url, true).query.id);
      var result = jobs[request_id];
      var answer = result ? getJobStatus(result) : { status: 404, data: null };

      // non-standard stringification of undefined
      if (answer.data === undefined) answer.data = null;

      res.writeHead(answer.status, {'Content-Type': 'application/json'});
      res.end(JSON.stringify(answer.data) + '\n');
    })();

  } else {

    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.end('Not implemented: ' + req.method + '\n');

  }

};

// vi: set sw=2 et :
<commit_msg>MEDIASERVICES-12079: Add support for AWS Lambda API for NodeJS 4.3+<commit_after>var merge = require('merge');
var url = require('url');

var next_id = 0;
// FIXME: this object grows forever - entries are never removed.  Should they
// be removed by expiry (time), and/or a REST API call?
var jobs = {};

var Job = function () {
};

Job.prototype.doError = function (error) {
  if (!this.completionValues && !this.timedOut) {
    this.threw = error;
  }
};

Job.prototype.doCompletion = function (errorValue, successValue) {
  if (!this.threw && !this.timedOut) {
    this.completionValues = [ errorValue, successValue ];
  }
};

Job.prototype.doTimedOut = function () {
  if (!this.threw && !this.completionValues) {
    this.timedOut = true;
  }
};

var region = function () {
    return process.env.AWS_DEFAULT_REGION || process.env.AWS_REGION || process.env.AMAZON_REGION || "xx-dummy-0";
};

var defaultContextObject = function () {
  return {
    // Fixed, but reasonably representative
    functionName: "via-aws-lambda-runner",
    functionVersion: "$LATEST",
    invokedFunctionArn: "arn:aws:lambda:" + region() + ":000000000000:function:via-aws-lambda-runner:$LATEST",
    memoryLimitInMB: 100,
    awsRequestId: "00000000-0000-0000-0000-000000000000",
    logGroupName: "/aws/lambda/via-aws-lambda-runner",
    logStreamName: "some-log-stream-name",
  };
};

var makeContextObject = function (timeout, overrides) {
  var context = merge(true, defaultContextObject(), overrides);

  var approximateEndTime = (new Date().getTime()) + timeout;

  context.getRemainingTimeInMillis = function () {
    return approximateEndTime - (new Date().getTime());
  };

  context.fail = function(err) { context.done(err, null); };
  context.succeed = function(data) { context.done(null, data); };

  return context;
};

var startJob = function (job, requestObject, handler, opts) {
  setTimeout(function() {
    job.doTimedOut();
  }, opts.timeout);

  var event = requestObject.event;

  var context = makeContextObject(opts.timeout, requestObject.context || {});
  context.done = function (err, result) {
    job.doCompletion(err, result);
    if (err) {
      console.warn('Error:', err);
    }
  };

  try {
    if (parseInt(process.versions['node'].split('.')[0], 10) >= 4) {
      delete context.fail;
      delete context.succeed;
      handler(event, context, context.done);
    }
    else {
      handler(event, context);
    }
  } catch (e) {
    console.log("Handler crashed", e);
    job.doError(e);
  }
};

var doCreateJob = function (req, res, opts, handler) {
  var id = ++next_id;
  var job = jobs[id] = new Job();

  var requestBody = '';

  req.on('data', function(chunk) {
    requestBody += chunk.toString();
  });

  req.on('end', function() {
    var requestObject;

    try {
      requestObject = JSON.parse(requestBody);
    } catch (e) {
      console.log("POSTed bad json: " + e.toString());
      res.writeHead(400, {'Content-Type': 'text/plain'});
      res.end(e.toString());
      return;
    }

    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end(String(id));

    startJob(job, requestObject, handler, opts);
  });
};

var getJobStatus = function (result) {
  var status = null;
  var responseBody = null;

  if (result.completionValues) {
    if (result.completionValues[0] !== null && result.completionValues[0] !== undefined) {
      status = 502;
      responseBody = result.completionValues[0];
    } else {
      status = 201;
      responseBody = result.completionValues[1];
    }
  } else if (result.threw) {
    status = 500;
      responseBody = result.threw.toString();
  } else if (result.timedOut) {
    status = 504;
  } else {
    // still in progress
    status = 200;
  }

  return { status: status, data: responseBody };
};

exports.request = function(req, res, opts, handler, server) {

  if (req.method === 'POST') {

    doCreateJob(req, res, opts, handler);

  } else if (req.method === 'DELETE') {

    (function () {
      // FIXME untested
      res.writeHead(202, {'Content-Type': 'text/plain'});
      var terminationMessage = 'Terminating server at http://[localhost]:' + opts.port + ' for ' + opts['module-path'] + ' / ' + opts.handler;
      res.end(terminationMessage + '\n');
      console.info(terminationMessage);
      if (server) {
        server.close();
      }
    })();

  } else if (req.method === 'GET') {

    (function () {
      var request_id = parseInt(url.parse(req.url, true).query.id);
      var result = jobs[request_id];
      var answer = result ? getJobStatus(result) : { status: 404, data: null };

      // non-standard stringification of undefined
      if (answer.data === undefined) answer.data = null;

      res.writeHead(answer.status, {'Content-Type': 'application/json'});
      res.end(JSON.stringify(answer.data) + '\n');
    })();

  } else {

    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.end('Not implemented: ' + req.method + '\n');

  }

};

// vi: set sw=2 et :
<|endoftext|>


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

/**
 * Main entry point for PhET translation web app. This is where ExpressJS gets configured and the routes are set up.
 *
 * @author John Blanco
 * @author Aaron Davis
 */

/* eslint-env node */
'use strict';

// modules
var assert = require( 'assert' );
var bodyParser = require( 'body-parser' ); // eslint-disable-line require-statement-match
var dateformat = require( 'dateformat' );
var doT = require( 'express-dot' ); // eslint-disable-line require-statement-match
var express = require( 'express' );
var fs = require( 'fs' );
var parseArgs = require( 'minimist' ); // eslint-disable-line require-statement-match
var query = require( 'pg-query' ); // eslint-disable-line require-statement-match
var winston = require( 'winston' );

// constants
var LISTEN_PORT = 16372;
var PREFERENCES_FILE;

// The following flag is used to take this utility off line and show a "down for maintenance" sort of page to users.
// This is generally set by editing the in situ version, and should never be committed to the code base as false.
var ENABLED = true;

/*
 * Rosetta is run under user "phet-admin" on the dev and production servers. However, "process.env.HOME" will get
 * the user who is starting the process's home directory, not phet-admin's home directory, therefore we need to use
 * a different approach to get the home directory.
 */
if ( !/^win/.test( process.platform ) ) {
  var passwdUser = require( 'passwd-user' ); // eslint-disable-line require-statement-match
  PREFERENCES_FILE = passwdUser.sync( process.getuid() ).homedir + '/.phet/build-local.json';
}
else {
  PREFERENCES_FILE = process.env.HOME + '/.phet/build-local.json';
}

// ensure that the preferences file exists and has the required fields
assert( fs.existsSync( PREFERENCES_FILE ), 'missing preferences file ' + PREFERENCES_FILE );
var preferences = require( PREFERENCES_FILE );
assert( preferences.githubUsername, 'githubUsername is missing from ' + PREFERENCES_FILE );
assert( preferences.githubPassword, 'githubPassword is missing from ' + PREFERENCES_FILE );
assert( preferences.buildServerAuthorizationCode, 'buildServerAuthorizationCode is missing from ' + PREFERENCES_FILE );
assert( preferences.rosettaSessionSecret, 'rosettaSessionSecret is missing from ' + PREFERENCES_FILE +
                                          '. To set this up for local testing add any string as the value for "rosettaSessionSecret"' );
assert( preferences.htmlSimsDirectory, 'Missing htmlSimsDirectory from ' + PREFERENCES_FILE );

preferences.productionServerURL = preferences.productionServerURL || 'https://phet.colorado.edu';
preferences.productionServerName = preferences.productionServerName || 'phet-server.int.colorado.edu';

/*
 * Add "babelBranch": "tests" in build-local.json for rosetta testing, so that commits will change the 'tests' branch
 * of babel instead of master. Make sure to checkout the tests branch in babel on the server as well.
 */
preferences.babelBranch = preferences.babelBranch || 'master';

assert( preferences.babelBranch === 'master' || preferences.babelBranch === 'tests', 'BRANCH must be set to either master or tests' );

// initialize globals
global.preferences = preferences;

// must be required after global.preferences has been initialized
var routes = require( __dirname + '/routes' );

// configure postgres connection
if ( preferences.pgConnectionString ) {
  query.connectionParameters = preferences.pgConnectionString;
}
else {
  query.connectionParameters = 'postgresql://localhost/rosetta';
}

// Handle command line input
// First 2 args provide info about executables, ignore
var commandLineArgs = process.argv.slice( 2 );

var parsedCommandLineOptions = parseArgs( commandLineArgs, {
  boolean: true
} );

var defaultOptions = {

  // options for supporting help, currently no other options are supported, but this might change
  help: false,
  h: false
};

for ( var key in parsedCommandLineOptions ) {
  if ( key !== '_' && parsedCommandLineOptions.hasOwnProperty( key ) && !defaultOptions.hasOwnProperty( key ) ) {
    console.log( 'Unrecognized option: ' + key );
    console.log( 'try --help for usage information.' );
    return; // eslint-disable-line
  }
}

// If help flag, print help and usage info
if ( parsedCommandLineOptions.hasOwnProperty( 'help' ) || parsedCommandLineOptions.hasOwnProperty( 'h' ) ) {
  console.log( 'Usage:' );
  console.log( '  node rosetta.js [options]' );
  console.log( '' );
  console.log( 'Options:' );
  console.log(
    '  --help (print usage and exit)\n' +
    '    type: bool  default: false\n' );
  return;
}

// add timestamps
winston.remove( winston.transports.Console );
winston.add( winston.transports.Console, {
  'timestamp': function() {
    var now = new Date();
    return dateformat( now, 'mmm dd yyyy HH:MM:ss Z' );
  }
} );

// log startup message
winston.log( 'info', '---- rosetta starting up, about to create ExpressJS app ----' );

// Create and configure the ExpressJS app
var app = express();
app.set( 'views', __dirname + '/html/views' );
app.set( 'view engine', 'dot' );
app.engine( 'html', doT.__express );

// set static directories for css, img, and js
app.use( '/translate/css', express.static( __dirname + '/css' ) );
app.use( '/translate/img', express.static( __dirname + '/img' ) );
app.use( '/translate/js', express.static( __dirname + '/js' ) );

// need cookieParser middleware before we can do anything with cookies
app.use( express.cookieParser() );
app.use( express.session( { secret: preferences.rosettaSessionSecret } ) );
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );

//----------------------------------------------------------------------------
// Set up the routes.  The order matters.
//----------------------------------------------------------------------------

// route for showing 'down for maintenance' page when needed
if ( !ENABLED ) {
  app.get( '/translate', routes.showOffLinePage );
}

// route that checks whether the user is logged in
app.get( '/translate*', routes.checkForValidSession );

// landing page for the translation utility
app.get( '/translate', routes.chooseSimulationAndLanguage );

// route for translating a specific sim to a specific language
app.get( '/translate/sim/:simName?/:targetLocale?', routes.translateSimulation );
app.post( '/translate/sim/save/:simName?/:targetLocale?', routes.saveStrings );
app.post( '/translate/sim/:simName?/:targetLocale?', routes.submitStrings );

// route for extracting strings from a sim
app.get( '/translate/extractStrings', routes.extractStringsAPI );

app.get( '/translate/logout', routes.logout );

// fall through route
app.get( '/*', routes.pageNotFound );

// start the server
app.listen( LISTEN_PORT, function() { winston.log( 'info', 'Listening on port ' + LISTEN_PORT ); } );
<commit_msg>attempt to fix paths, see #40<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * Main entry point for PhET translation web app. This is where ExpressJS gets configured and the routes are set up.
 *
 * @author John Blanco
 * @author Aaron Davis
 */

/* eslint-env node */
'use strict';

// modules
var assert = require( 'assert' );
var bodyParser = require( 'body-parser' ); // eslint-disable-line require-statement-match
var dateformat = require( 'dateformat' );
var doT = require( 'express-dot' ); // eslint-disable-line require-statement-match
var express = require( 'express' );
var fs = require( 'fs' );
var parseArgs = require( 'minimist' ); // eslint-disable-line require-statement-match
var query = require( 'pg-query' ); // eslint-disable-line require-statement-match
var winston = require( 'winston' );

// constants
var LISTEN_PORT = 16372;
var PREFERENCES_FILE;

// The following flag is used to take this utility off line and show a "down for maintenance" sort of page to users.
// This is generally set by editing the in situ version, and should never be committed to the code base as false.
var ENABLED = true;

/*
 * Rosetta is run under user "phet-admin" on the dev and production servers. However, "process.env.HOME" will get
 * the user who is starting the process's home directory, not phet-admin's home directory, therefore we need to use
 * a different approach to get the home directory.
 */
if ( !/^win/.test( process.platform ) ) {
  var passwdUser = require( 'passwd-user' ); // eslint-disable-line require-statement-match
  PREFERENCES_FILE = passwdUser.sync( process.getuid() ).homedir + '/.phet/build-local.json';
}
else {
  PREFERENCES_FILE = process.env.HOME + '/.phet/build-local.json';
}

// ensure that the preferences file exists and has the required fields
assert( fs.existsSync( PREFERENCES_FILE ), 'missing preferences file ' + PREFERENCES_FILE );
var preferences = require( PREFERENCES_FILE );
assert( preferences.githubUsername, 'githubUsername is missing from ' + PREFERENCES_FILE );
assert( preferences.githubPassword, 'githubPassword is missing from ' + PREFERENCES_FILE );
assert( preferences.buildServerAuthorizationCode, 'buildServerAuthorizationCode is missing from ' + PREFERENCES_FILE );
assert( preferences.rosettaSessionSecret, 'rosettaSessionSecret is missing from ' + PREFERENCES_FILE +
                                          '. To set this up for local testing add any string as the value for "rosettaSessionSecret"' );
assert( preferences.htmlSimsDirectory, 'Missing htmlSimsDirectory from ' + PREFERENCES_FILE );

preferences.productionServerURL = preferences.productionServerURL || 'https://phet.colorado.edu';
preferences.productionServerName = preferences.productionServerName || 'phet-server.int.colorado.edu';

/*
 * Add "babelBranch": "tests" in build-local.json for rosetta testing, so that commits will change the 'tests' branch
 * of babel instead of master. Make sure to checkout the tests branch in babel on the server as well.
 */
preferences.babelBranch = preferences.babelBranch || 'master';

assert( preferences.babelBranch === 'master' || preferences.babelBranch === 'tests', 'BRANCH must be set to either master or tests' );

// initialize globals
global.preferences = preferences;

// must be required after global.preferences has been initialized
var routes = require( __dirname + '/routes' );

// configure postgres connection
if ( preferences.pgConnectionString ) {
  query.connectionParameters = preferences.pgConnectionString;
}
else {
  query.connectionParameters = 'postgresql://localhost/rosetta';
}

// Handle command line input
// First 2 args provide info about executables, ignore
var commandLineArgs = process.argv.slice( 2 );

var parsedCommandLineOptions = parseArgs( commandLineArgs, {
  boolean: true
} );

var defaultOptions = {

  // options for supporting help, currently no other options are supported, but this might change
  help: false,
  h: false
};

for ( var key in parsedCommandLineOptions ) {
  if ( key !== '_' && parsedCommandLineOptions.hasOwnProperty( key ) && !defaultOptions.hasOwnProperty( key ) ) {
    console.log( 'Unrecognized option: ' + key );
    console.log( 'try --help for usage information.' );
    return; // eslint-disable-line
  }
}

// If help flag, print help and usage info
if ( parsedCommandLineOptions.hasOwnProperty( 'help' ) || parsedCommandLineOptions.hasOwnProperty( 'h' ) ) {
  console.log( 'Usage:' );
  console.log( '  node rosetta.js [options]' );
  console.log( '' );
  console.log( 'Options:' );
  console.log(
    '  --help (print usage and exit)\n' +
    '    type: bool  default: false\n' );
  return;
}

// add timestamps
winston.remove( winston.transports.Console );
winston.add( winston.transports.Console, {
  'timestamp': function() {
    var now = new Date();
    return dateformat( now, 'mmm dd yyyy HH:MM:ss Z' );
  }
} );

// log startup message
winston.log( 'info', '---- rosetta starting up, about to create ExpressJS app ----' );

// Create and configure the ExpressJS app
var app = express();
app.set( 'views', __dirname + '../html/views' );
app.set( 'view engine', 'dot' );
app.engine( 'html', doT.__express );

// set static directories for css, img, and js
app.use( '/translate/css', express.static( __dirname + '../css' ) );
app.use( '/translate/img', express.static( __dirname + '../img' ) );
app.use( '/translate/js', express.static( __dirname ) );

// need cookieParser middleware before we can do anything with cookies
app.use( express.cookieParser() );
app.use( express.session( { secret: preferences.rosettaSessionSecret } ) );
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );

//----------------------------------------------------------------------------
// Set up the routes.  The order matters.
//----------------------------------------------------------------------------

// route for showing 'down for maintenance' page when needed
if ( !ENABLED ) {
  app.get( '/translate', routes.showOffLinePage );
}

// route that checks whether the user is logged in
app.get( '/translate*', routes.checkForValidSession );

// landing page for the translation utility
app.get( '/translate', routes.chooseSimulationAndLanguage );

// route for translating a specific sim to a specific language
app.get( '/translate/sim/:simName?/:targetLocale?', routes.translateSimulation );
app.post( '/translate/sim/save/:simName?/:targetLocale?', routes.saveStrings );
app.post( '/translate/sim/:simName?/:targetLocale?', routes.submitStrings );

// route for extracting strings from a sim
app.get( '/translate/extractStrings', routes.extractStringsAPI );

app.get( '/translate/logout', routes.logout );

// fall through route
app.get( '/*', routes.pageNotFound );

// start the server
app.listen( LISTEN_PORT, function() { winston.log( 'info', 'Listening on port ' + LISTEN_PORT ); } );
<|endoftext|>


<commit_before>// Copyright 2015-2020, University of Colorado Boulder

/**
 * TODO: Relocate code in this file that doesn't have to do with configuring the app, setting up routes, etc.
 * TODO: Write a less generic and vague description once Rosetta has undergone modularization.
 * Main entry point for PhET translation web app. This is where Express.js gets configured and the routes are set up.
 *
 * @author John Blanco
 * @author Aaron Davis
 */

'use strict';

// Modules
const bodyParser = require( 'body-parser' ); // eslint-disable-line require-statement-match
const childProcess = require( 'child_process' ); // eslint-disable-line require-statement-match
const cookieParser = require( 'cookie-parser' ); // eslint-disable-line require-statement-match
const doT = require( 'express-dot' ); // eslint-disable-line require-statement-match
const express = require( 'express' );
const getRosettaConfig = require( './getRosettaConfig' );
const { Pool } = require( 'pg' ); // eslint-disable-line
const winston = require( 'winston' );

// Order-Dependent Modules
const session = require( 'express-session' ); // eslint-disable-line require-statement-match
const MemoryStore = require( 'memorystore' )( session );

// Constants
const LISTEN_PORT = 16372;
const { format } = winston;

//===========================================================================//
// Configure Winston (the logger). Use Winston to display information.       //
//===========================================================================//

// Configure the logger. Uses default logging level, which can be updated once configuration is read.
const consoleTransport = new winston.transports.Console( {
  format: format.combine(
    format.timestamp( {
      format: 'YYYY-MM-DD HH:mm:ss'
    } ),
    format.printf( i => `${i.timestamp} | ${i.level} - ${i.message}` )
  )
} );
winston.add( consoleTransport );

// Add a global handler for unhandled promise rejections.
process.on( 'unhandledRejection', error => {

  // If these are logged, issues should be tracked down and fixed.
  winston.error( `Unhandled rejection. Error Message: ${error.message}.` );
  winston.error( `Error Stack: ${error.stack}.` );
} );

// Log startup message.
winston.info( '========== Rosetta is starting up! ==========' );
winston.info( `Node Version ${process.version}.` );

// Log Rosetta's SHA. This might make it easier to duplicate issues and track them down.
try {

  // For some reason, "sha.toString()" has a newline. I want the log to look nice, so I'm taking it out.
  let sha = childProcess.execSync( 'git rev-parse HEAD' );
  sha = sha.toString();
  sha = sha.replace( /\r?\n|\r/, '' );
  winston.info( `Current SHA is ${sha}.` );
}
catch( error ) {
  winston.warn( `Unable to get SHA from Git. Error: ${error}.` );
}

// TODO: Move this to a more appropriate location.
// TODO: Determine if setting a variable for the config is preferable to this approach.
// Get configuration and assign it to a global. This also sets up some process variables.
global.config = getRosettaConfig();

// Log the config.
winston.info( 'Check your config below and make sure it looks correct!' );
winston.info( JSON.stringify( global.config, null, 2 ) );

// TODO: Determine if it's possible to get the config before setting up the logger. (So we don't have to update it.)
// Update the logging level in case it was set in the config info.
consoleTransport.level = global.config.loggingLevel;

// TODO: Move this into the "shortTermStorage" object once it exists.
// Check that the database is running and that a basic query can be performed.
winston.info( 'Testing database connection...' );
const pool = new Pool();
pool.query( 'SELECT NOW();', ( error, result ) => {
  if ( error ) {
    winston.error( 'Database test using "SELECT NOW()" failed. Error: ' + JSON.stringify( error ) );
    winston.error( 'Short term storage will be unavailable.' );
  }
  else {
    winston.info( `Database test using "SELECT NOW()" succeeded. Now: ${result.rows[ 0 ].now}.` );
  }
} );

//===========================================================================//
// Set up the app.                                                           //
//===========================================================================//

// TODO: Determine if the use of "__dirname" is correct.
// Create and configure the Express.js app.
const app = express();
app.set( 'views', __dirname + '/../html/views' );
app.set( 'view engine', 'dot' );
app.engine( 'html', doT.__express );

// Set static directories for CSS, images, and JavaScript.
app.use( '/translate/css', express.static( __dirname + '/../css' ) );
app.use( '/translate/img', express.static( __dirname + '/../img' ) );
app.use( '/translate/js', express.static( __dirname ) );

// Set up handling for cookies.
app.use( cookieParser() );
app.use( session( {
  secret: global.config.rosettaSessionSecret,
  resave: false,
  saveUninitialized: false,
  store: new MemoryStore( {
    checkPeriod: 86400000 // In milliseconds. Set to 1 day.
  } )
} ) );
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );

//===========================================================================//
// Set up the routes. The order in which they are set up matters!            //
//===========================================================================//

// Add route handlers. Must be after global.config has been initialized and logger configured.
const routeHandlers = require( __dirname + '/routeHandlers' );

// Set up route for the 'down for maintenance' page.
if ( !global.config.enabled ) {
  app.get( '/translate', routeHandlers.showOfflinePage );
}

// Set up route for checking if a user is logged in.
app.get( '/translate*', routeHandlers.checkForValidSession );

// Set up routes for debugging.
app.post( '/translate*', function( request, response, next ) {
  winston.debug( `Post request received. URL: ${request.url}.` );
  next();
} );
app.get( '/translate*', function( request, response, next ) {
  winston.debug( `Get request received. URL: ${request.url}.` );
  next();
} );

// Set up landing page for the translation utility.
app.get( '/translate', routeHandlers.chooseSimulationAndLanguage );

// Set up page where user can submit their translated strings.
app.get( '/translate/sim/:simName?/:targetLocale?', routeHandlers.renderTranslationPage );

// Set up post route for testing translated strings. This doesn't save the translated strings.
app.post( '/translate/sim/test/:simName?', routeHandlers.testStrings );

// Set up post route for short-term storage of strings.
app.post( '/translate/sim/save/:simName?/:targetLocale?', routeHandlers.saveStrings );

// Set up post route for long-term storage of strings.
app.post( '/translate/sim/:simName?/:targetLocale?', routeHandlers.submitStrings );

// Set up logout.
app.get( '/translate/logout', routeHandlers.logout );

// Trigger the build of a simulation for a given sim, locale, and user ID. Only used by team members to fix problems.
app.get( '/translate/trigger-build/:simName?/:targetLocale?/:userID?', routeHandlers.triggerBuild );

// Set up testing routes.
app.get( '/translate/test/', routeHandlers.displayTestPage );
app.get( '/translate/runSpecificTest/:testID', routeHandlers.runSpecificTest );
app.get( '/translate/runAllTests/', routeHandlers.runAllTests );

// Set up routes for incorrect URL patterns.
app.get( '/*', routeHandlers.pageNotFound );
app.post( '/*', routeHandlers.pageNotFound );

// Start the server.
app.listen( LISTEN_PORT, () => {
  winston.info( `Listening on port ${LISTEN_PORT}.` );
  winston.info( `Open your browser to http://localhost:${LISTEN_PORT}/translate.` );
} );
<commit_msg>Removing more unnecessary double quotes from logs in rosetta.js.<commit_after>// Copyright 2015-2020, University of Colorado Boulder

/**
 * TODO: Relocate code in this file that doesn't have to do with configuring the app, setting up routes, etc.
 * TODO: Write a less generic and vague description once Rosetta has undergone modularization.
 * Main entry point for PhET translation web app. This is where Express.js gets configured and the routes are set up.
 *
 * @author John Blanco
 * @author Aaron Davis
 */

'use strict';

// Modules
const bodyParser = require( 'body-parser' ); // eslint-disable-line require-statement-match
const childProcess = require( 'child_process' ); // eslint-disable-line require-statement-match
const cookieParser = require( 'cookie-parser' ); // eslint-disable-line require-statement-match
const doT = require( 'express-dot' ); // eslint-disable-line require-statement-match
const express = require( 'express' );
const getRosettaConfig = require( './getRosettaConfig' );
const { Pool } = require( 'pg' ); // eslint-disable-line
const winston = require( 'winston' );

// Order-Dependent Modules
const session = require( 'express-session' ); // eslint-disable-line require-statement-match
const MemoryStore = require( 'memorystore' )( session );

// Constants
const LISTEN_PORT = 16372;
const { format } = winston;

//===========================================================================//
// Configure Winston (the logger). Use Winston to display information.       //
//===========================================================================//

// Configure the logger. Uses default logging level, which can be updated once configuration is read.
const consoleTransport = new winston.transports.Console( {
  format: format.combine(
    format.timestamp( {
      format: 'YYYY-MM-DD HH:mm:ss'
    } ),
    format.printf( i => `${i.timestamp} | ${i.level} - ${i.message}` )
  )
} );
winston.add( consoleTransport );

// Add a global handler for unhandled promise rejections.
process.on( 'unhandledRejection', error => {

  // If these are logged, issues should be tracked down and fixed.
  winston.error( `Unhandled rejection. Error Message: ${error.message}.` );
  winston.error( `Error Stack: ${error.stack}.` );
} );

// Log startup message.
winston.info( '========== Rosetta is starting up! ==========' );
winston.info( `Node Version ${process.version}.` );

// Log Rosetta's SHA. This might make it easier to duplicate issues and track them down.
try {

  // For some reason, "sha.toString()" has a newline. I want the log to look nice, so I'm taking it out.
  let sha = childProcess.execSync( 'git rev-parse HEAD' );
  sha = sha.toString();
  sha = sha.replace( /\r?\n|\r/, '' );
  winston.info( `Current SHA is ${sha}.` );
}
catch( error ) {
  winston.warn( `Unable to get SHA from Git. Error: ${error}.` );
}

// TODO: Move this to a more appropriate location.
// TODO: Determine if setting a variable for the config is preferable to this approach.
// Get configuration and assign it to a global. This also sets up some process variables.
global.config = getRosettaConfig();

// Log the config.
winston.info( 'Check your config below and make sure it looks correct!' );
winston.info( JSON.stringify( global.config, null, 2 ) );

// TODO: Determine if it's possible to get the config before setting up the logger. (So we don't have to update it.)
// Update the logging level in case it was set in the config info.
consoleTransport.level = global.config.loggingLevel;

// TODO: Move this into the "shortTermStorage" object once it exists.
// Check that the database is running and that a basic query can be performed.
winston.info( 'Testing database connection...' );
const pool = new Pool();
pool.query( 'SELECT NOW();', ( error, result ) => {
  if ( error ) {
    winston.error( 'Database test using SELECT NOW() failed. Error: ' + JSON.stringify( error ) );
    winston.error( 'Short term storage will be unavailable.' );
  }
  else {
    winston.info( `Database test using SELECT NOW() succeeded. Now: ${result.rows[ 0 ].now}.` );
  }
} );

//===========================================================================//
// Set up the app.                                                           //
//===========================================================================//

// TODO: Determine if the use of "__dirname" is correct.
// Create and configure the Express.js app.
const app = express();
app.set( 'views', __dirname + '/../html/views' );
app.set( 'view engine', 'dot' );
app.engine( 'html', doT.__express );

// Set static directories for CSS, images, and JavaScript.
app.use( '/translate/css', express.static( __dirname + '/../css' ) );
app.use( '/translate/img', express.static( __dirname + '/../img' ) );
app.use( '/translate/js', express.static( __dirname ) );

// Set up handling for cookies.
app.use( cookieParser() );
app.use( session( {
  secret: global.config.rosettaSessionSecret,
  resave: false,
  saveUninitialized: false,
  store: new MemoryStore( {
    checkPeriod: 86400000 // In milliseconds. Set to 1 day.
  } )
} ) );
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );

//===========================================================================//
// Set up the routes. The order in which they are set up matters!            //
//===========================================================================//

// Add route handlers. Must be after global.config has been initialized and logger configured.
const routeHandlers = require( __dirname + '/routeHandlers' );

// Set up route for the 'down for maintenance' page.
if ( !global.config.enabled ) {
  app.get( '/translate', routeHandlers.showOfflinePage );
}

// Set up route for checking if a user is logged in.
app.get( '/translate*', routeHandlers.checkForValidSession );

// Set up routes for debugging.
app.post( '/translate*', function( request, response, next ) {
  winston.debug( `Post request received. URL: ${request.url}.` );
  next();
} );
app.get( '/translate*', function( request, response, next ) {
  winston.debug( `Get request received. URL: ${request.url}.` );
  next();
} );

// Set up landing page for the translation utility.
app.get( '/translate', routeHandlers.chooseSimulationAndLanguage );

// Set up page where user can submit their translated strings.
app.get( '/translate/sim/:simName?/:targetLocale?', routeHandlers.renderTranslationPage );

// Set up post route for testing translated strings. This doesn't save the translated strings.
app.post( '/translate/sim/test/:simName?', routeHandlers.testStrings );

// Set up post route for short-term storage of strings.
app.post( '/translate/sim/save/:simName?/:targetLocale?', routeHandlers.saveStrings );

// Set up post route for long-term storage of strings.
app.post( '/translate/sim/:simName?/:targetLocale?', routeHandlers.submitStrings );

// Set up logout.
app.get( '/translate/logout', routeHandlers.logout );

// Trigger the build of a simulation for a given sim, locale, and user ID. Only used by team members to fix problems.
app.get( '/translate/trigger-build/:simName?/:targetLocale?/:userID?', routeHandlers.triggerBuild );

// Set up testing routes.
app.get( '/translate/test/', routeHandlers.displayTestPage );
app.get( '/translate/runSpecificTest/:testID', routeHandlers.runSpecificTest );
app.get( '/translate/runAllTests/', routeHandlers.runAllTests );

// Set up routes for incorrect URL patterns.
app.get( '/*', routeHandlers.pageNotFound );
app.post( '/*', routeHandlers.pageNotFound );

// Start the server.
app.listen( LISTEN_PORT, () => {
  winston.info( `Listening on port ${LISTEN_PORT}.` );
  winston.info( `Open your browser to http://localhost:${LISTEN_PORT}/translate.` );
} );
<|endoftext|>


<commit_before>/*
* Created by ManeBit.
* Copyright 2014 - 2015
*/

// Config Vars
var img1 = "img/special.png";
var img2 = "img/special2.png";
var imgElement = "awesome"; // The element id for the title image.
var titleSound = "sounds/duh.mp3";
var dir = "sounds/"; // The directory of all the audio clips.
var ext = ".mp3";
var takes = 3; // There are 3 takes for each audio clip.

// Instance Vars
var ip = "";
var sounds = new Array();
var curSound = null;
var START_POINT = -1;
var END_POINT = -1;

/*
* The main init function, called upon ip retrieval
*/
function main(json)
{
	// Getting the IP
	ip = json.ip; //".0123456789"
	END_POINT = ip.length;

	// Set up sounds 0 - 9
	for (var i = 0; i <= 9; i++)
	{
		// Nested array for random sounds 1 - 3
		sounds[i.toString()] = new Array();
		for (var j = 1; j <= takes; j++) // Add random sounds 1 - 3 for this digit
		{
			sounds[i.toString()][j] = new Audio(dir + i + "-" + j + ext);
		}
	}

	// Set up special char sounds
	sounds['.'] = new Array();
	sounds['starting'] = new Array();
	sounds['ending'] = new Array();

	for (var i = 1; i <= takes; i++)
	{
		sounds['.'][i] = new Audio(dir + "dot-" + i + ext);
		sounds['starting'][i] = new Audio(dir + "starting-" + i + ext);
		sounds['ending'][i] = new Audio(dir + "ending-" + i + ext);
	}
}

/*
* This function increments through the IP and plays the random associated sounds.
*/
function playIP(i)
{
	// If past all the chars in IP, we're done.
	if (i > END_POINT)
		return;

	// Randomly selected sound file number.
	var rand = Math.floor(Math.random() * takes) + 1;

	if (i == START_POINT) // If we are starting from the beginning, play start sound.
		curSound = sounds['starting'][rand];
	else if (i == END_POINT) // We're now at the end.
	{
		curSound = sounds['ending'][rand];
		switchImg(false);
	}
	else // Else we're just strolling through the middle of the IP.
		curSound = sounds[ip.charAt(i)][rand];

	// Play the current character!
	curSound.play();

	// Now we wait. Don't play the next sound until the current one is finished.
	curSound.onended = function() {
    	playIP(++i);
	};
}

/*
* Switches the two title images.
*/
function switchImg(playing)
{
	document.getElementById(imgElement).src = (playing) ? img2 : img1;
}

/*
* Plays the title sound of the page.
*/
function playTitleSound()
{
	// Is sound already playing? If so, get out of here!
	if(curSound != null && !curSound.ended)
		return;

	switchImg(true);

	// Create temp var to hold the sound.
	var title = new Audio(titleSound);

	// Play the sound.
	title.play();

	// Wait until the sound is done then switch the image back.
	title.onended = function() {
    	switchImg(false);
	};

}

/*
* Basically the main function. Defines itself.
*/
function onButtonClick()
{
	// Is sound already playing? If so, get out of here!
	if(curSound != null && !curSound.ended)
		return;

	switchImg(true);

	// Play the IP sound!
	playIP(START_POINT);
}<commit_msg>Username update<commit_after>/*
* Created by David Meyer.
* Copyright 2014 - 2015
*/

// Config Vars
var img1 = "img/special.png";
var img2 = "img/special2.png";
var imgElement = "awesome"; // The element id for the title image.
var titleSound = "sounds/duh.mp3";
var dir = "sounds/"; // The directory of all the audio clips.
var ext = ".mp3";
var takes = 3; // There are 3 takes for each audio clip.

// Instance Vars
var ip = "";
var sounds = new Array();
var curSound = null;
var START_POINT = -1;
var END_POINT = -1;

/*
* The main init function, called upon ip retrieval
*/
function main(json)
{
	// Getting the IP
	ip = json.ip; //".0123456789"
	END_POINT = ip.length;

	// Set up sounds 0 - 9
	for (var i = 0; i <= 9; i++)
	{
		// Nested array for random sounds 1 - 3
		sounds[i.toString()] = new Array();
		for (var j = 1; j <= takes; j++) // Add random sounds 1 - 3 for this digit
		{
			sounds[i.toString()][j] = new Audio(dir + i + "-" + j + ext);
		}
	}

	// Set up special char sounds
	sounds['.'] = new Array();
	sounds['starting'] = new Array();
	sounds['ending'] = new Array();

	for (var i = 1; i <= takes; i++)
	{
		sounds['.'][i] = new Audio(dir + "dot-" + i + ext);
		sounds['starting'][i] = new Audio(dir + "starting-" + i + ext);
		sounds['ending'][i] = new Audio(dir + "ending-" + i + ext);
	}
}

/*
* This function increments through the IP and plays the random associated sounds.
*/
function playIP(i)
{
	// If past all the chars in IP, we're done.
	if (i > END_POINT)
		return;

	// Randomly selected sound file number.
	var rand = Math.floor(Math.random() * takes) + 1;

	if (i == START_POINT) // If we are starting from the beginning, play start sound.
		curSound = sounds['starting'][rand];
	else if (i == END_POINT) // We're now at the end.
	{
		curSound = sounds['ending'][rand];
		switchImg(false);
	}
	else // Else we're just strolling through the middle of the IP.
		curSound = sounds[ip.charAt(i)][rand];

	// Play the current character!
	curSound.play();

	// Now we wait. Don't play the next sound until the current one is finished.
	curSound.onended = function() {
    	playIP(++i);
	};
}

/*
* Switches the two title images.
*/
function switchImg(playing)
{
	document.getElementById(imgElement).src = (playing) ? img2 : img1;
}

/*
* Plays the title sound of the page.
*/
function playTitleSound()
{
	// Is sound already playing? If so, get out of here!
	if(curSound != null && !curSound.ended)
		return;

	switchImg(true);

	// Create temp var to hold the sound.
	var title = new Audio(titleSound);

	// Play the sound.
	title.play();

	// Wait until the sound is done then switch the image back.
	title.onended = function() {
    	switchImg(false);
	};

}

/*
* Basically the main function. Defines itself.
*/
function onButtonClick()
{
	// Is sound already playing? If so, get out of here!
	if(curSound != null && !curSound.ended)
		return;

	switchImg(true);

	// Play the IP sound!
	playIP(START_POINT);
}
<|endoftext|>