<commit_before>window.w3term = function(node, options){
    function Terminal(node, options) {
        // ---------------------------------------------------------------------
        var _commandHistory = [];
        var _commandStep = 0;
        var _options = {
            prompt : "> ",
            historySize : 100,
            processCommand : function(string, args) {
                console.log(string, args);
            }
        };
        var _terminal = this; // because this scoping is broken in JS
        var _inputEnabled = true;
        var _skipPattern = /([A-Za-z][^A-Za-z]|[0-9][^0-9])/g;
        // ---------------------------------------------------------------------

        var _node = node;
        var _hiddenInput;
        var _currentLine;
        var _prompt;
        var _prev;
        var _caret;
        var _next;
        // ---------------------------------------------------------------------
        setOptions(_options, options);

        _node.className += " w3term";
        _hiddenInput = document.createElement("input");
        _hiddenInput.type = "text";
        _hiddenInput.className = "hiddenInput";

        _currentLine = document.createElement("span");
        _currentLine.className = "currentLine";
        _currentLine.tabindex = 0;

        _prompt = document.createElement("span");
        _prompt.className = "prompt";
        _prompt.textContent = _options.prompt;

        _prev = document.createElement("span");
        _next = document.createElement("span");
        _caret = document.createElement("span");
        _caret.textContent = " ";
        _caret.className = "textcursor term-blink";

        _currentLine.appendChild(_prompt);
        _currentLine.appendChild(_prev);
        _currentLine.appendChild(_caret);
        _currentLine.appendChild(_next);
        _node.appendChild(_currentLine);
        _node.appendChild(_hiddenInput);

        // --- define public methods -------------------------------------------

        _terminal.backspace = function() {
            _prev.textContent = _prev.textContent.slice(0,-1);
        };

        _terminal.skipBackspace = function() {
            var matches = [];
            while((match = _skipPattern.exec(
                _prev.textContent.slice(0, -1))) != null)
            {
                matches.push(match.index);
            }
            if(matches.length > 0) {
                // if there was a word separation, jump there
                var newIndex = matches[matches.length-1]+2;
                _prev.textContent = _prev.textContent.slice(0, newIndex);
            }
            else {
                _prev.textContent = "";
            }
        };

        _terminal.delete = function() {
            _caret.textContent = _next.textContent.slice(0,1);
            _next.textContent = _next.textContent.slice(1);
        };

        _terminal.navigate = function(pos) {
            if(pos == _prev.textContent.length) return;
            var wholeText = _prev.textContent
                          + _caret.textContent
                          + _next.textContent;

            if(pos <= 0) {
                pos = 0;
                _prev.textContent = "";
                if(wholeText.length == 0)
                    _caret.textContent = " ";
                else
                    _caret.textContent = wholeText.charAt(0);
                _next.textContent = wholeText.substring(1);
            }
            else if(pos >= wholeText.length) {
                _prev.textContent = wholeText;
                _caret.textContent = " ";
                _next.textContent = "";
            }
            else {
                _prev.textContent = wholeText.substring(0, pos);
                _caret.textContent = wholeText.charAt(pos);
                _next.textContent = wholeText.substring(pos+1);
            }

            _caret.classList.remove("term-blink");
            setTimeout(function() {
                _caret.classList.add("term-blink")
            }, 1000);
        };

        _terminal.moveLeft = function() {
            _terminal.navigate(_prev.textContent.length-1);
        };

        _terminal.skipLeft = function() {
            var textBefore = _prev.textContent;

            if(textBefore == "") return;

            var matches = [];
            while((match = _skipPattern.exec(textBefore.slice(0, -1))) != null) {
                matches.push(match.index);
            }
            // if there was a word separation, jump there
            if(matches.length > 0) {
                var newIndex = matches[matches.length-1]+2;
                _terminal.navigate(newIndex);
            } else {
                // if there was no word separation, perform as if
                // the home button was pressed
                _terminal.home();
            }
        };

        _terminal.moveRight = function() {
            _terminal.navigate(_prev.textContent.length+1);
        };

        _terminal.skipRight = function() {
            var textAfter = _next.textContent;

            match = _skipPattern.exec(textAfter);
            if(match) {
                // if there was a word separation, jump there
                var newIndex = match.index+1;
                _terminal.navigate(newIndex);
            } else {
                // if there was no word separation, perform as if
                // the home button was pressed
                _terminal.end();
            }
        };

        _terminal.home = function() {
            _terminal.navigate(0);
        };

        _terminal.end = function() {
            _terminal.navigate(_prev.textContent
                + _caret.textContent.length
                + _next.textContent.length
                -1);
        };

        _terminal.enter = function() {
            var wholeText = (_prev.textContent
                        + _caret.textContent
                        + _next.textContent).trim();

            var newLine = document.createElement("p");
            var prompt = document.createElement("span");
            prompt.className = "prompt";
            prompt.textContent = _options.prompt;
            newLine.appendChild(prompt);
            newLine.appendChild(document.createTextNode(wholeText));
            _node.insertBefore(newLine, _currentLine);

            // new empty line
            _prev.textContent = "";
            _next.textContent = "";
            _caret.textContent = " ";

            if(wholeText != "") {
                var commandCount = _commandHistory.length;

                if(wholeText != _commandHistory[commandCount-1])
                {
                    // limit the size of the command history
                    if(commandCount >= _options.historySize) {
                        _commandHistory.shift();
                    }
                    _commandHistory.push(wholeText);
                }
                _commandStep = _commandHistory.length;

                // process command
                _options.processCommand(wholeText, parseBashArgs(wholeText));
            }
            _prompt.textContent = _options.prompt;
            _terminal.scrollToBottom();
        };

        _terminal.setInput = function(text) {
            text = text.replace(/(?:\r\n|\r|\n)/g, " ");
            _prev.textContent = text;
            _caret.textContent = " ";
            _next.textContent = "";
        };

        _terminal.clear = function() {
            var paragraph;
            while(paragraph = _node.getElementsByTagName("P")[0]) {
                _node.removeChild(paragraph);
            }
        };

        // print text to the terminal
        _terminal.print = function(text, escapeHtml) {
            if(escapeHtml === undefined){
                escapeHtml = true;
            }
            text = text || "";
            if(escapeHtml) {
                text = text.replace(/&/g, "&");
                text = text.replace(/</g, "<");
                text = text.replace(/>/g, ">");
            }
            var textNode = processColors(text);

            var outputElement = _currentLine.previousSibling;
            if(outputElement.classList.contains("output")) {
                outputElement.appendChild(textNode);
            }
            else {
                var outParagraph = document.createElement("p");
                outParagraph.className = "output";
                outParagraph.appendChild(textNode);
                _node.insertBefore(outParagraph, _currentLine);
            }
            _terminal.scrollToBottom();
        };

        // print text to the terminal (with line ending)
        _terminal.println = function(text, escapeHtml) {
            _terminal.print(text+"\n", escapeHtml);
        };

        // scrolls down to the last line of the terminal
        _terminal.scrollToBottom = function() {
            _node.scrollTop = _node.scrollHeight;
        };

        // hides the caret and disables user input
        _terminal.disableInput = function(){
            _currentLine.style.display = "none";
            _inputEnabled = false;
            _hiddenInput.blur();
        },

        // hides the caret and disables user input
        _terminal.enableInput = function(){
            _currentLine.style.display = "inline";
            _inputEnabled = true;
            _hiddenInput.focus();
        },

        _terminal.setOptions = function (options) {
            setOptions(_options, options);
        }

        // --- define handlers -------------------------------------------------
        // set focus to currentLine when terminal is clicked -------------------
        _node.onclick = function(evt) {
            _hiddenInput.focus();
        };

        _node.focus = function() {
            _hiddenInput.focus();
        };

        // Insert Charcters when a key is pressed ------------------------------
        _node.oninput = function(evt) {
            _prev.textContent += _hiddenInput.value;
            _hiddenInput.value = "";
        };

        // Catch special keys before a character is inserted -------------------
        _node.onkeydown = function(evt) {
            if(!_inputEnabled) return;
            var char = evt.keyCode;

            var textBefore = _prev.textContent;
            var textAfter = _next.textContent;

            switch(char) {
                case 27: // ESCAPE
                    evt.preventDefault();
                    break;
                case 8: // BACKSPACE
                    evt.preventDefault();
                    if(evt.altKey)
                        _terminal.skipBackspace();
                    else {
                        _terminal.backspace();
                    }
                    break;
                case 46: // DEL
                    evt.preventDefault();
                    _terminal.delete();
                    break;
                case 9: // TAB
                    // TODO: implement tab completion
                    break;
                case 37: // LEFT
                    evt.preventDefault();
                    // if the ctrl key is pressed, we move until the next
                    // non-character symbol
                    if(evt.ctrlKey) _terminal.skipLeft();
                    else _terminal.moveLeft();
                    break;
                case 39: // RIGHT
                    evt.preventDefault();
                    // if the ctrl key is pressed, we move until the next
                    // non-character symbol
                    if(evt.ctrlKey) _terminal.skipRight();
                    else _terminal.moveRight();
                    break;
                case 38: // UP
                    evt.preventDefault();
                    var step = _commandStep;
                    if(step > 0) {
                        step--;
                        _terminal.setInput(_commandHistory[step]);
                        _commandStep = step;
                    }
                    break;
                case 40: // DOWN
                    evt.preventDefault();
                    var step = _commandStep;
                    if(step < _commandHistory.length) {
                        step++;
                        if(step == _commandHistory.length) {
                            _terminal.setInput("");
                        } else {
                            _terminal.setInput(_commandHistory[step]);
                        }
                        _commandStep = step;
                    }
                    break;
                case 35: // END
                    evt.preventDefault();
                    _terminal.end();
                    break;
                case 36: // HOME
                    evt.preventDefault();
                    _terminal.home();
                    break;
                case 13: // ENTER
                    evt.preventDefault();
                    _terminal.enter();
                    break;
                default:
                    break;
            }
        };
        // ---------------------------------------------------------------------
        _node.focus();
    }

    // -------------------------------------------------------------------------

    function getNodeArg(node) {
        if(!node.tagName) {
            throw "invalid node, w3term needs a DOM node as an argument";
        }
        else if(node.tagName == "DIV") {
            clearNode(node);
        }
        else {
            newNode = document.createElement("div");
            node.parentNode.replaceChild(newNode, node);
            node = newNode;
        }
        return node;
    }

    // clears all children and text content from a node
    function clearNode(node){
        while(node.firstChild){
            node.removeChild(node.firstChild);
        }
    }

    function processColors(text){
        var rootTag = document.createElement("span");
        var currentTag = rootTag;

        var i = text.indexOf("\x1B[");
        while(i >= 0) {
            currentTag.appendChild(document.createTextNode(text.substr(0, i)));

            // add a new command or reset font?
            if(text.substr(i, 4) == "\x1B[0m"){ // reset
                currentTag = rootTag;
                colorCode = "\x1B[0m";
            }
            else {
                colorCode = text.substr(i, 5);
                if(colorCode.charAt(3) == 'm') {
                    colorCode = colorCode.substr(0, 4);
                }
                var newTag = getColorTag(colorCode);
                currentTag.appendChild(newTag);
                currentTag = newTag;
            }
            // replace the code by the new tag
            text = text.substr(i + colorCode.length);
            i = text.indexOf("\x1B[");
        }
        currentTag.appendChild(document.createTextNode(text));
        return rootTag;
    }

    function getColorTag(colorCode) {
        var tag = document.createElement("span");
        switch(colorCode){
            case "\x1B[31m":
                tag.style.color = "red";
                break;
            case "\x1B[32m":
                tag.style.color = "green";
                break;
            case "\x1B[33m":
                tag.style.color = "yellow";
                break;
            case "\x1B[34m":
                tag.style.color = "blue";
                break;
            case "\x1B[35m":
                tag.style.color = "magenta";
                break;
            case "\x1B[36m":
                tag.style.color = "cyan";
                break;
            case "\x1B[37m":
                tag.style.color = "white";
                break;
            case "\x1B[41m":
                tag.style.backgroundColor = "red";
                break;
            case "\x1B[42m":
                tag.style.backgroundColor = "green";
                break;
            case "\x1B[43m":
                tag.style.backgroundColor = "yellow";
                break;
            case "\x1B[44m":
                tag.style.backgroundColor = "blue";
                break;
            case "\x1B[45m":
                tag.style.backgroundColor = "magenta";
                break;
            case "\x1B[46m":
                tag.style.backgroundColor = "cyan";
                break;
            case "\x1B[47m":
                tag.style.backgroundColor = "white";
                break;
            case "\x1B[1m":
                tag.style.fontWeight = "bold";
                break;
            case "\x1B[3m":
                tag.style.fontStyle = "italic";
                break;
            case "\x1B[4m":
                tag.style.textDecoration = "underline";
                break;
            case "\x1B[5m":
                tag.className = "term-textblink";
                break;
            case "\x1B[7m":
                tag.className = "term-textinverted";
                break;
            case "\x1B[92m": // success
                tag.style.color = "#00EE00";
                break;
            case "\x1B[95m": // header
                tag.style.color = "#660099";
                break;
            case "\x1B[94m": // info
                tag.style.color = "#3399FF";
                break;
            case "\x1B[93m": // warning
                tag.style.color = "#FF9900";
                break;
            case "\x1B[91m": // error
                tag.style.color = "#990000";
                break;
            default:
                break;
        }
        return tag;
    }

    function parseBashArg(char, cmdString, i) {
        var startIndex = i;
        var text = "";
        var escaped = false;
        for(; i < cmdString.length; i++) {
            if(!escaped && cmdString.charAt(i) == "\\") {
                escaped = true;
                continue;
            }
            if(cmdString.charAt(i) == char) {
                if(escaped) {
                    text += cmdString.slice(startIndex, i-1) + char;
                    startIndex = i+1;
                }
                else {
                    break;
                }
            }
            escaped = false;
        }
        return {
            arg : text + cmdString.slice(startIndex, i),
            i : i
        };
    }

    function parseBashArgs(cmdString) {
        var outputArgs = [];
        var escaped = false;
        var inString = false;
        for(var i = 0; i < cmdString.length; i++) {
            if(cmdString.charAt(i) == "\"") {
                result = parseBashArg("\"", cmdString, i+1);
                outputArgs.push(result.arg);
                i = result.i+1; // skip not only the quote, but also whitespace
            }
            else {
                result = parseBashArg(" ", cmdString, i);
                outputArgs.push(result.arg);
                i = result.i;
            }
        }
        if(i < cmdString.length) {
            outputArgs.push(cmdString.slice(i, cmdString.length));
        }
        return outputArgs;
    }

    function setOptions(original, newValues) {
        if(newValues.prompt !== undefined)
            original.prompt = newValues.prompt;

        if(newValues.historySize !== undefined)
            original.historySize = newValues.historySize;

        if(newValues.processCommand !== undefined)
            original.processCommand = newValues.processCommand;
    }

    // -------------------------------------------------------------------------

    node = getNodeArg(node);
    node.w3term = new Terminal(node, options);
    return node;
}
<commit_msg>more work on navigation<commit_after>window.w3term = function(node, options){
    function Terminal(node, options) {
        // ---------------------------------------------------------------------
        var _commandHistory = [];
        var _commandStep = 0;
        var _options = {
            prompt : "> ",
            historySize : 100,
            processCommand : function(string, args) {
                console.log(string, args);
            }
        };
        var _terminal = this; // because this scoping is broken in JS
        var _inputEnabled = true;
        var _skipPattern = /([A-Za-z][^A-Za-z]|[0-9][^0-9])/g;
        // ---------------------------------------------------------------------

        var _node = node;
        var _hiddenInput;
        var _currentLine;
        var _prompt;
        var _prev;
        var _caret;
        var _next;
        // ---------------------------------------------------------------------
        setOptions(_options, options);

        _node.className += " w3term";
        _hiddenInput = document.createElement("input");
        _hiddenInput.type = "text";
        _hiddenInput.className = "hiddenInput";

        _currentLine = document.createElement("span");
        _currentLine.className = "currentLine";
        _currentLine.tabindex = 0;

        _prompt = document.createElement("span");
        _prompt.className = "prompt";
        _prompt.textContent = _options.prompt;

        _prev = document.createElement("span");
        _next = document.createElement("span");
        _caret = document.createElement("span");
        _caret.textContent = " ";
        _caret.className = "textcursor term-blink";

        _currentLine.appendChild(_prompt);
        _currentLine.appendChild(_prev);
        _currentLine.appendChild(_caret);
        _currentLine.appendChild(_next);
        _node.appendChild(_currentLine);
        _node.appendChild(_hiddenInput);

        // --- define public methods -------------------------------------------

        _terminal.backspace = function() {
            _prev.textContent = _prev.textContent.slice(0,-1);
        };

        _terminal.skipBackspace = function() {
            var matches = [];
            while((match = _skipPattern.exec(
                _prev.textContent.slice(0, -1))) != null)
            {
                matches.push(match.index);
            }
            if(matches.length > 0) {
                // if there was a word separation, jump there
                var newIndex = matches[matches.length-1]+2;
                _prev.textContent = _prev.textContent.slice(0, newIndex);
            }
            else {
                _prev.textContent = "";
            }
        };

        _terminal.delete = function() {
            _caret.textContent = _next.textContent.slice(0,1);
            _next.textContent = _next.textContent.slice(1);
        };

        _terminal.navigate = function(pos) {
            if(pos == _prev.textContent.length) return;
            var wholeText = _prev.textContent
                          + _caret.textContent
                          + _next.textContent;

            if(pos <= 0) {
                pos = 0;
                _prev.textContent = "";
                if(wholeText.length == 0)
                    _caret.textContent = " ";
                else
                    _caret.textContent = wholeText.charAt(0);
                _next.textContent = wholeText.substring(1);
            }
            else if(pos >= wholeText.length) {
                _prev.textContent = wholeText.substring(0, wholeText.length-2);
                _caret.textContent = wholeText.charAt(wholeText.length-1);
                _next.textContent = "";
            }
            else {
                _prev.textContent = wholeText.substring(0, pos);
                _caret.textContent = wholeText.charAt(pos);
                _next.textContent = wholeText.substring(pos+1);
            }

            _caret.classList.remove("term-blink");
            setTimeout(function() {
                _caret.classList.add("term-blink")
            }, 1000);
        };

        _terminal.moveLeft = function() {
            _terminal.navigate(_prev.textContent.length-1);
        };

        _terminal.skipLeft = function() {
            var textBefore = _prev.textContent;

            if(textBefore == "") return;

            var matches = [];
            while((match = _skipPattern.exec(textBefore.slice(0, -1))) != null) {
                matches.push(match.index);
            }
            // if there was a word separation, jump there
            if(matches.length > 0) {
                var newIndex = matches[matches.length-1]+2;
                _terminal.navigate(newIndex);
            } else {
                // if there was no word separation, perform as if
                // the home button was pressed
                _terminal.home();
            }
        };

        _terminal.moveRight = function() {
            _terminal.navigate(_prev.textContent.length+1);
        };

        _terminal.skipRight = function() {
            var textAfter = _next.textContent;

            match = _skipPattern.exec(textAfter);
            if(match) {
                // if there was a word separation, jump there
                var newIndex = match.index+2;
                _terminal.navigate(newIndex);
            } else {
                // if there was no word separation, perform as if
                // the home button was pressed
                _terminal.end();
            }
        };

        _terminal.home = function() {
            _terminal.navigate(0);
        };

        _terminal.end = function() {
            _terminal.navigate(_prev.textContent.length
                + _caret.textContent.length
                + _next.textContent.length
                -1);
        };

        _terminal.enter = function() {
            var wholeText = (_prev.textContent
                        + _caret.textContent
                        + _next.textContent).trim();

            var newLine = document.createElement("p");
            var prompt = document.createElement("span");
            prompt.className = "prompt";
            prompt.textContent = _options.prompt;
            newLine.appendChild(prompt);
            newLine.appendChild(document.createTextNode(wholeText));
            _node.insertBefore(newLine, _currentLine);

            // new empty line
            _prev.textContent = "";
            _next.textContent = "";
            _caret.textContent = " ";

            if(wholeText != "") {
                var commandCount = _commandHistory.length;

                if(wholeText != _commandHistory[commandCount-1])
                {
                    // limit the size of the command history
                    if(commandCount >= _options.historySize) {
                        _commandHistory.shift();
                    }
                    _commandHistory.push(wholeText);
                }
                _commandStep = _commandHistory.length;

                // process command
                _options.processCommand(wholeText, parseBashArgs(wholeText));
            }
            _prompt.textContent = _options.prompt;
            _terminal.scrollToBottom();
        };

        _terminal.setInput = function(text) {
            text = text.replace(/(?:\r\n|\r|\n)/g, " ");
            _prev.textContent = text;
            _caret.textContent = " ";
            _next.textContent = "";
        };

        _terminal.clear = function() {
            var paragraph;
            while(paragraph = _node.getElementsByTagName("P")[0]) {
                _node.removeChild(paragraph);
            }
        };

        // print text to the terminal
        _terminal.print = function(text, escapeHtml) {
            if(escapeHtml === undefined){
                escapeHtml = true;
            }
            text = text || "";
            if(escapeHtml) {
                text = text.replace(/&/g, "&");
                text = text.replace(/</g, "<");
                text = text.replace(/>/g, ">");
            }
            var textNode = processColors(text);

            var outputElement = _currentLine.previousSibling;
            if(outputElement.classList.contains("output")) {
                outputElement.appendChild(textNode);
            }
            else {
                var outParagraph = document.createElement("p");
                outParagraph.className = "output";
                outParagraph.appendChild(textNode);
                _node.insertBefore(outParagraph, _currentLine);
            }
            _terminal.scrollToBottom();
        };

        // print text to the terminal (with line ending)
        _terminal.println = function(text, escapeHtml) {
            _terminal.print(text+"\n", escapeHtml);
        };

        // scrolls down to the last line of the terminal
        _terminal.scrollToBottom = function() {
            _node.scrollTop = _node.scrollHeight;
        };

        // hides the caret and disables user input
        _terminal.disableInput = function(){
            _currentLine.style.display = "none";
            _inputEnabled = false;
            _hiddenInput.blur();
        },

        // hides the caret and disables user input
        _terminal.enableInput = function(){
            _currentLine.style.display = "inline";
            _inputEnabled = true;
            _hiddenInput.focus();
        },

        _terminal.setOptions = function (options) {
            setOptions(_options, options);
        }

        // --- define handlers -------------------------------------------------
        // set focus to currentLine when terminal is clicked -------------------
        _node.onclick = function(evt) {
            _hiddenInput.focus();
        };

        _node.focus = function() {
            _hiddenInput.focus();
        };

        // Insert Charcters when a key is pressed ------------------------------
        _node.oninput = function(evt) {
            _prev.textContent += _hiddenInput.value;
            _hiddenInput.value = "";
        };

        // Catch special keys before a character is inserted -------------------
        _node.onkeydown = function(evt) {
            if(!_inputEnabled) return;
            var char = evt.keyCode;

            var textBefore = _prev.textContent;
            var textAfter = _next.textContent;

            switch(char) {
                case 27: // ESCAPE
                    evt.preventDefault();
                    break;
                case 8: // BACKSPACE
                    evt.preventDefault();
                    if(evt.altKey)
                        _terminal.skipBackspace();
                    else {
                        _terminal.backspace();
                    }
                    break;
                case 46: // DEL
                    evt.preventDefault();
                    _terminal.delete();
                    break;
                case 9: // TAB
                    // TODO: implement tab completion
                    break;
                case 37: // LEFT
                    evt.preventDefault();
                    // if the ctrl key is pressed, we move until the next
                    // non-character symbol
                    if(evt.ctrlKey) _terminal.skipLeft();
                    else _terminal.moveLeft();
                    break;
                case 39: // RIGHT
                    evt.preventDefault();
                    // if the ctrl key is pressed, we move until the next
                    // non-character symbol
                    if(evt.ctrlKey) _terminal.skipRight();
                    else _terminal.moveRight();
                    break;
                case 38: // UP
                    evt.preventDefault();
                    var step = _commandStep;
                    if(step > 0) {
                        step--;
                        _terminal.setInput(_commandHistory[step]);
                        _commandStep = step;
                    }
                    break;
                case 40: // DOWN
                    evt.preventDefault();
                    var step = _commandStep;
                    if(step < _commandHistory.length) {
                        step++;
                        if(step == _commandHistory.length) {
                            _terminal.setInput("");
                        } else {
                            _terminal.setInput(_commandHistory[step]);
                        }
                        _commandStep = step;
                    }
                    break;
                case 35: // END
                    evt.preventDefault();
                    _terminal.end();
                    break;
                case 36: // HOME
                    evt.preventDefault();
                    _terminal.home();
                    break;
                case 13: // ENTER
                    evt.preventDefault();
                    _terminal.enter();
                    break;
                default:
                    break;
            }
        };
        // ---------------------------------------------------------------------
        _node.focus();
    }

    // -------------------------------------------------------------------------

    function getNodeArg(node) {
        if(!node.tagName) {
            throw "invalid node, w3term needs a DOM node as an argument";
        }
        else if(node.tagName == "DIV") {
            clearNode(node);
        }
        else {
            newNode = document.createElement("div");
            node.parentNode.replaceChild(newNode, node);
            node = newNode;
        }
        return node;
    }

    // clears all children and text content from a node
    function clearNode(node){
        while(node.firstChild){
            node.removeChild(node.firstChild);
        }
    }

    function processColors(text){
        var rootTag = document.createElement("span");
        var currentTag = rootTag;

        var i = text.indexOf("\x1B[");
        while(i >= 0) {
            currentTag.appendChild(document.createTextNode(text.substr(0, i)));

            // add a new command or reset font?
            if(text.substr(i, 4) == "\x1B[0m"){ // reset
                currentTag = rootTag;
                colorCode = "\x1B[0m";
            }
            else {
                colorCode = text.substr(i, 5);
                if(colorCode.charAt(3) == 'm') {
                    colorCode = colorCode.substr(0, 4);
                }
                var newTag = getColorTag(colorCode);
                currentTag.appendChild(newTag);
                currentTag = newTag;
            }
            // replace the code by the new tag
            text = text.substr(i + colorCode.length);
            i = text.indexOf("\x1B[");
        }
        currentTag.appendChild(document.createTextNode(text));
        return rootTag;
    }

    function getColorTag(colorCode) {
        var tag = document.createElement("span");
        switch(colorCode){
            case "\x1B[31m":
                tag.style.color = "red";
                break;
            case "\x1B[32m":
                tag.style.color = "green";
                break;
            case "\x1B[33m":
                tag.style.color = "yellow";
                break;
            case "\x1B[34m":
                tag.style.color = "blue";
                break;
            case "\x1B[35m":
                tag.style.color = "magenta";
                break;
            case "\x1B[36m":
                tag.style.color = "cyan";
                break;
            case "\x1B[37m":
                tag.style.color = "white";
                break;
            case "\x1B[41m":
                tag.style.backgroundColor = "red";
                break;
            case "\x1B[42m":
                tag.style.backgroundColor = "green";
                break;
            case "\x1B[43m":
                tag.style.backgroundColor = "yellow";
                break;
            case "\x1B[44m":
                tag.style.backgroundColor = "blue";
                break;
            case "\x1B[45m":
                tag.style.backgroundColor = "magenta";
                break;
            case "\x1B[46m":
                tag.style.backgroundColor = "cyan";
                break;
            case "\x1B[47m":
                tag.style.backgroundColor = "white";
                break;
            case "\x1B[1m":
                tag.style.fontWeight = "bold";
                break;
            case "\x1B[3m":
                tag.style.fontStyle = "italic";
                break;
            case "\x1B[4m":
                tag.style.textDecoration = "underline";
                break;
            case "\x1B[5m":
                tag.className = "term-textblink";
                break;
            case "\x1B[7m":
                tag.className = "term-textinverted";
                break;
            case "\x1B[92m": // success
                tag.style.color = "#00EE00";
                break;
            case "\x1B[95m": // header
                tag.style.color = "#660099";
                break;
            case "\x1B[94m": // info
                tag.style.color = "#3399FF";
                break;
            case "\x1B[93m": // warning
                tag.style.color = "#FF9900";
                break;
            case "\x1B[91m": // error
                tag.style.color = "#990000";
                break;
            default:
                break;
        }
        return tag;
    }

    function parseBashArg(char, cmdString, i) {
        var startIndex = i;
        var text = "";
        var escaped = false;
        for(; i < cmdString.length; i++) {
            if(!escaped && cmdString.charAt(i) == "\\") {
                escaped = true;
                continue;
            }
            if(cmdString.charAt(i) == char) {
                if(escaped) {
                    text += cmdString.slice(startIndex, i-1) + char;
                    startIndex = i+1;
                }
                else {
                    break;
                }
            }
            escaped = false;
        }
        return {
            arg : text + cmdString.slice(startIndex, i),
            i : i
        };
    }

    function parseBashArgs(cmdString) {
        var outputArgs = [];
        var escaped = false;
        var inString = false;
        for(var i = 0; i < cmdString.length; i++) {
            if(cmdString.charAt(i) == "\"") {
                result = parseBashArg("\"", cmdString, i+1);
                outputArgs.push(result.arg);
                i = result.i+1; // skip not only the quote, but also whitespace
            }
            else {
                result = parseBashArg(" ", cmdString, i);
                outputArgs.push(result.arg);
                i = result.i;
            }
        }
        if(i < cmdString.length) {
            outputArgs.push(cmdString.slice(i, cmdString.length));
        }
        return outputArgs;
    }

    function setOptions(original, newValues) {
        if(newValues.prompt !== undefined)
            original.prompt = newValues.prompt;

        if(newValues.historySize !== undefined)
            original.historySize = newValues.historySize;

        if(newValues.processCommand !== undefined)
            original.processCommand = newValues.processCommand;
    }

    // -------------------------------------------------------------------------

    node = getNodeArg(node);
    node.w3term = new Terminal(node, options);
    return node;
}
<|endoftext|>


<commit_before>/*
 * js.js
 * Neighbourhood Fix-It JavaScript
 *
 * TODO
 * Investigate jQuery
 * Tidy it all up
 * Selection of pin doesn't really need a server request, but I don't really care
 *
 */

function compass_pan(e, a) {
    YAHOO.util.Event.preventDefault(e);
    pan(a.x, a.y);
}

YAHOO.util.Event.onContentReady('compass', function() {
    var points = this.getElementsByTagName('a');
    YAHOO.util.Event.addListener(points[1], 'click', compass_pan, { x:0, y:tileheight });
    YAHOO.util.Event.addListener(points[3], 'click', compass_pan, { x:tilewidth, y:0 });
    YAHOO.util.Event.addListener(points[4], 'click', compass_pan, { x:-tilewidth, y:0 });
    YAHOO.util.Event.addListener(points[6], 'click', compass_pan, { x:0, y:-tileheight });
    YAHOO.util.Event.addListener(points[0], 'click', compass_pan, { x:tilewidth, y:tileheight });
    YAHOO.util.Event.addListener(points[2], 'click', compass_pan, { x:-tilewidth, y:tileheight });
    YAHOO.util.Event.addListener(points[5], 'click', compass_pan, { x:tilewidth, y:-tileheight });
    YAHOO.util.Event.addListener(points[7], 'click', compass_pan, { x:-tilewidth, y:-tileheight });
});

YAHOO.util.Event.onContentReady('map', function() {
    new YAHOO.util.DDMap('map');
    update_tiles(0, 0, true);
});

YAHOO.util.Event.onContentReady('mapForm', function() {
    this.onsubmit = function() {
        this.x.value = x + 2;
        this.y.value = y + 2;
        return true;
    }
});

YAHOO.util.Event.onContentReady('email_alert', function() {
    YAHOO.util.Event.addListener(this, 'click', function(e) {
        YAHOO.util.Event.preventDefault(e);
        if (YAHOO.util.Dom.getStyle('email_alert_box', 'display') == 'block') {
            YAHOO.util.Dom.setStyle('email_alert_box', 'display', 'none');
        } else {
            var pos = YAHOO.util.Dom.getXY(this);
            pos[0] -= 20; pos[1] += 20;
            YAHOO.util.Dom.setStyle('email_alert_box', 'display', 'block');
            YAHOO.util.Dom.setXY('email_alert_box', pos);
        }
    });
});

// I love the global
var tile_x = 0;
var tile_y = 0;
var tilewidth = 254;
var tileheight = 254;

var myAnim;
function pan(x, y) {
    if (!myAnim || !myAnim.isAnimated()) {
        myAnim = new YAHOO.util.Motion('drag', { points:{by:[x,y]} }, 10, YAHOO.util.Easing.easeOut);
        myAnim.useSeconds = false;
        myAnim.onTween.subscribe(function(){ update_tiles(x/10, y/10, false); });
        myAnim.onComplete.subscribe(function(){ cleanCache(); });
        myAnim.animate();
    }
}

function update_tiles(dx, dy, force) {
    dx = getInt(dx); dy = getInt(dy);
    if (!dx && !dy && !force) return;

    var old_drag_x = drag_x;
    var old_drag_y = drag_y;
    drag_x += dx;
    drag_y += dy;

    var drag = document.getElementById('drag');
    drag.style.left = drag_x + 'px';
    drag.style.top = drag_y + 'px';

    var horizontal = Math.floor(old_drag_x/tilewidth) - Math.floor(drag_x/tilewidth);
    var vertical = Math.floor(old_drag_y/tileheight) - Math.floor(drag_y/tileheight);
    if (!horizontal && !vertical && !force) return;

    x += horizontal;
    tile_x += horizontal;
    y -= vertical;
    tile_y += vertical;

    var url = '/tilma/tileserver/10k-full/' + x + '-' + (x+5) + ',' + y + '-' + (y+5) + '/JSON';
    var req = YAHOO.util.Connect.asyncRequest('GET', url, {
        success: urls_loaded, failure: urls_not_loaded,
        argument: [tile_x, tile_y]
    });
}

function urls_not_loaded(o) { /* Nothing yet */ }

// Load 6x6 grid of tiles around current 2x2
function urls_loaded(o) {
    var tiles = eval(o.responseText);
    var drag = document.getElementById('drag');
    for (var i=0; i<6; i++) {
        var ii = (i + o.argument[1]);
        for (var j=0; j<6; j++) {
            if (tiles[i][j] == null) continue;
            var jj = (j + o.argument[0]);
            var id = 't'+ii+'.'+jj;
            var xx = x+j;
            var yy = y+5-i;
            var img = document.getElementById(id);
            if (img) {
                if (!img.galleryimg) { img.galleryimg = false; }
                img.onclick = drag_check;
                tileCache[id] = { x: xx, y: yy, t: img };
                continue;
            }
            img = cloneNode();
            img.style.top = ((ii-2)*tileheight) + 'px';
            img.style.left = ((jj-2)*tilewidth) + 'px';
            img.name = 'tile_' + xx + '.' + yy;
            img.id = id;
            if (browser) {
                img.style.visibility = 'hidden';
                img.onload=function() { this.style.visibility = 'visible'; }
            }
            img.src = 'http://tilma.mysociety.org/tileserver/10k-full/' + tiles[i][j];
            tileCache[id] = { x: xx, y: yy, t: img };
            drag.appendChild(img);
        }
    }
}

var imgElCache;
function cloneNode() {
    var img = null;
    if (!imgElCache) {
        var form = document.getElementById('mapForm');
        if (form) {
            img = imgElCache = document.createElement('input');
            img.type = 'image';
        } else {
            img = imgElCache = document.createElement('img');
        }
        img.onclick = drag_check;
        img.style.position = 'absolute';
        img.style.width = tilewidth + 'px';
        img.style.height = tileheight + 'px';
        img.galleryimg = false;
        img.alt = 'Loading...';
    } else {
        img = imgElCache.cloneNode(true);
    }
    return img;
}

var tileCache=[];
function cleanCache() {
    for (var i in tileCache) {
        if (tileCache[i].x < x || tileCache[i].x > x+5 || tileCache[i].y < y || tileCache[i].y > y+5) {
            var t = tileCache[i].t;
            t.parentNode.removeChild(t); // de-leak?
            delete tileCache[i];
        }
    }
}

/* Called every mousemove, so on first call, overwrite itself with quicker version */
function get_posn(ev) {
    var posx, posy;
    if (ev.pageX || ev.pageY) {
        get_posn = function(e) {
            return { x: e.pageX, y: e.pageY };
        };
    } else if (ev.clientX || ev.clientY) {
        get_posn = function(e) {
            return {
                x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
                y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
            };
        };
    } else {
        get_posn = function(e) {
            return { x: undef, y: undef };
        };
    }
    return get_posn(ev);
}

function setCursor(s) {
    var drag = document.getElementById('drag');
    var inputs = drag.getElementsByTagName('input');
    for (var i=0; i<inputs.length; i++) {
        inputs[i].style.cursor = s;
    }
}

var in_drag = false;
function drag_check(e) {
    if (in_drag) {
        in_drag = false;
        return false;
    }
    return true;
}

/* Simpler version of DDProxy */
var mouse_pos = {};
YAHOO.util.DDMap = function(id, sGroup, config) {
    this.init(id, sGroup, config);
};
YAHOO.extend(YAHOO.util.DDMap, YAHOO.util.DD, {
    scroll: false,
    b4MouseDown: function(e) { },
    startDrag: function(x, y) {
        mouse_pos = { x: x, y: y };
        setCursor('move');
        in_drag = true;
    },
    b4Drag: function(e) { },
    onDrag: function(e) {
        var point = get_posn(e);
        if (point == mouse_pos) return false;
        var dx = point.x-mouse_pos.x;
        var dy = point.y-mouse_pos.y;
        mouse_pos = point;
        update_tiles(dx, dy, false);
    },
    endDrag: function(e) {
        setCursor('crosshair');
        cleanCache();
    },
    toString: function() {
        return ("DDMap " + this.id);
    }
});

var browser = 1;
var ua=navigator.userAgent.toLowerCase();
if (!/opera|safari|gecko/.test(ua) && typeof document.all!='undefined')
    browser=0;

function getInt(n) {
    n = parseInt(n); return (isNaN(n) ? 0 : n);
}

<commit_msg>Close email alert box if move mouse away.<commit_after>/*
 * js.js
 * Neighbourhood Fix-It JavaScript
 *
 * TODO
 * Investigate jQuery
 * Tidy it all up
 * Selection of pin doesn't really need a server request, but I don't really care
 *
 */

function compass_pan(e, a) {
    YAHOO.util.Event.preventDefault(e);
    pan(a.x, a.y);
}

YAHOO.util.Event.onContentReady('compass', function() {
    var points = this.getElementsByTagName('a');
    YAHOO.util.Event.addListener(points[1], 'click', compass_pan, { x:0, y:tileheight });
    YAHOO.util.Event.addListener(points[3], 'click', compass_pan, { x:tilewidth, y:0 });
    YAHOO.util.Event.addListener(points[4], 'click', compass_pan, { x:-tilewidth, y:0 });
    YAHOO.util.Event.addListener(points[6], 'click', compass_pan, { x:0, y:-tileheight });
    YAHOO.util.Event.addListener(points[0], 'click', compass_pan, { x:tilewidth, y:tileheight });
    YAHOO.util.Event.addListener(points[2], 'click', compass_pan, { x:-tilewidth, y:tileheight });
    YAHOO.util.Event.addListener(points[5], 'click', compass_pan, { x:tilewidth, y:-tileheight });
    YAHOO.util.Event.addListener(points[7], 'click', compass_pan, { x:-tilewidth, y:-tileheight });
});

YAHOO.util.Event.onContentReady('map', function() {
    new YAHOO.util.DDMap('map');
    update_tiles(0, 0, true);
});

YAHOO.util.Event.onContentReady('mapForm', function() {
    this.onsubmit = function() {
        this.x.value = x + 2;
        this.y.value = y + 2;
        return true;
    }
});

var timer;
function email_alert_close() {
    YAHOO.util.Dom.setStyle('email_alert_box', 'display', 'none');
}
YAHOO.util.Event.onContentReady('email_alert', function() {
    YAHOO.util.Event.addListener(this, 'click', function(e) {
        YAHOO.util.Event.preventDefault(e);
        if (YAHOO.util.Dom.getStyle('email_alert_box', 'display') == 'block') {
            email_alert_close();
        } else {
            var pos = YAHOO.util.Dom.getXY(this);
            pos[0] -= 20; pos[1] += 20;
            YAHOO.util.Dom.setStyle('email_alert_box', 'display', 'block');
            YAHOO.util.Dom.setXY('email_alert_box', pos);
            document.getElementById('alert_email').focus();
        }
    });
    YAHOO.util.Event.addListener(this, 'mouseout', function(e) {
        timer = window.setTimeout(email_alert_close, 2000);
    });
    YAHOO.util.Event.addListener(this, 'mouseover', function(e) {
        window.clearTimeout(timer);
    });
});
YAHOO.util.Event.onContentReady('email_alert_box', function() {
    YAHOO.util.Event.addListener(this, 'mouseout', function(e) {
        timer = window.setTimeout(email_alert_close, 2000);
    });
    YAHOO.util.Event.addListener(this, 'mouseover', function(e) {
        window.clearTimeout(timer);
    });
});

// I love the global
var tile_x = 0;
var tile_y = 0;
var tilewidth = 254;
var tileheight = 254;

var myAnim;
function pan(x, y) {
    if (!myAnim || !myAnim.isAnimated()) {
        myAnim = new YAHOO.util.Motion('drag', { points:{by:[x,y]} }, 10, YAHOO.util.Easing.easeOut);
        myAnim.useSeconds = false;
        myAnim.onTween.subscribe(function(){ update_tiles(x/10, y/10, false); });
        myAnim.onComplete.subscribe(function(){ cleanCache(); });
        myAnim.animate();
    }
}

function update_tiles(dx, dy, force) {
    dx = getInt(dx); dy = getInt(dy);
    if (!dx && !dy && !force) return;

    var old_drag_x = drag_x;
    var old_drag_y = drag_y;
    drag_x += dx;
    drag_y += dy;

    var drag = document.getElementById('drag');
    drag.style.left = drag_x + 'px';
    drag.style.top = drag_y + 'px';

    var horizontal = Math.floor(old_drag_x/tilewidth) - Math.floor(drag_x/tilewidth);
    var vertical = Math.floor(old_drag_y/tileheight) - Math.floor(drag_y/tileheight);
    if (!horizontal && !vertical && !force) return;

    x += horizontal;
    tile_x += horizontal;
    y -= vertical;
    tile_y += vertical;

    var url = '/tilma/tileserver/10k-full/' + x + '-' + (x+5) + ',' + y + '-' + (y+5) + '/JSON';
    var req = YAHOO.util.Connect.asyncRequest('GET', url, {
        success: urls_loaded, failure: urls_not_loaded,
        argument: [tile_x, tile_y]
    });
}

function urls_not_loaded(o) { /* Nothing yet */ }

// Load 6x6 grid of tiles around current 2x2
function urls_loaded(o) {
    var tiles = eval(o.responseText);
    var drag = document.getElementById('drag');
    for (var i=0; i<6; i++) {
        var ii = (i + o.argument[1]);
        for (var j=0; j<6; j++) {
            if (tiles[i][j] == null) continue;
            var jj = (j + o.argument[0]);
            var id = 't'+ii+'.'+jj;
            var xx = x+j;
            var yy = y+5-i;
            var img = document.getElementById(id);
            if (img) {
                if (!img.galleryimg) { img.galleryimg = false; }
                img.onclick = drag_check;
                tileCache[id] = { x: xx, y: yy, t: img };
                continue;
            }
            img = cloneNode();
            img.style.top = ((ii-2)*tileheight) + 'px';
            img.style.left = ((jj-2)*tilewidth) + 'px';
            img.name = 'tile_' + xx + '.' + yy;
            img.id = id;
            if (browser) {
                img.style.visibility = 'hidden';
                img.onload=function() { this.style.visibility = 'visible'; }
            }
            img.src = 'http://tilma.mysociety.org/tileserver/10k-full/' + tiles[i][j];
            tileCache[id] = { x: xx, y: yy, t: img };
            drag.appendChild(img);
        }
    }
}

var imgElCache;
function cloneNode() {
    var img = null;
    if (!imgElCache) {
        var form = document.getElementById('mapForm');
        if (form) {
            img = imgElCache = document.createElement('input');
            img.type = 'image';
        } else {
            img = imgElCache = document.createElement('img');
        }
        img.onclick = drag_check;
        img.style.position = 'absolute';
        img.style.width = tilewidth + 'px';
        img.style.height = tileheight + 'px';
        img.galleryimg = false;
        img.alt = 'Loading...';
    } else {
        img = imgElCache.cloneNode(true);
    }
    return img;
}

var tileCache=[];
function cleanCache() {
    for (var i in tileCache) {
        if (tileCache[i].x < x || tileCache[i].x > x+5 || tileCache[i].y < y || tileCache[i].y > y+5) {
            var t = tileCache[i].t;
            t.parentNode.removeChild(t); // de-leak?
            delete tileCache[i];
        }
    }
}

/* Called every mousemove, so on first call, overwrite itself with quicker version */
function get_posn(ev) {
    var posx, posy;
    if (ev.pageX || ev.pageY) {
        get_posn = function(e) {
            return { x: e.pageX, y: e.pageY };
        };
    } else if (ev.clientX || ev.clientY) {
        get_posn = function(e) {
            return {
                x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
                y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
            };
        };
    } else {
        get_posn = function(e) {
            return { x: undef, y: undef };
        };
    }
    return get_posn(ev);
}

function setCursor(s) {
    var drag = document.getElementById('drag');
    var inputs = drag.getElementsByTagName('input');
    for (var i=0; i<inputs.length; i++) {
        inputs[i].style.cursor = s;
    }
}

var in_drag = false;
function drag_check(e) {
    if (in_drag) {
        in_drag = false;
        return false;
    }
    return true;
}

/* Simpler version of DDProxy */
var mouse_pos = {};
YAHOO.util.DDMap = function(id, sGroup, config) {
    this.init(id, sGroup, config);
};
YAHOO.extend(YAHOO.util.DDMap, YAHOO.util.DD, {
    scroll: false,
    b4MouseDown: function(e) { },
    startDrag: function(x, y) {
        mouse_pos = { x: x, y: y };
        setCursor('move');
        in_drag = true;
    },
    b4Drag: function(e) { },
    onDrag: function(e) {
        var point = get_posn(e);
        if (point == mouse_pos) return false;
        var dx = point.x-mouse_pos.x;
        var dy = point.y-mouse_pos.y;
        mouse_pos = point;
        update_tiles(dx, dy, false);
    },
    endDrag: function(e) {
        setCursor('crosshair');
        cleanCache();
    },
    toString: function() {
        return ("DDMap " + this.id);
    }
});

var browser = 1;
var ua=navigator.userAgent.toLowerCase();
if (!/opera|safari|gecko/.test(ua) && typeof document.all!='undefined')
    browser=0;

function getInt(n) {
    n = parseInt(n); return (isNaN(n) ? 0 : n);
}

<|endoftext|>


<commit_before>// Strider Simple Worker
// Niall O'Higgins 2012
//
// A simple, in-process worker implementation for Strider.
//

var exec = require('child_process').exec
var gitane = require('gitane')
var gumshoe = require('gumshoe')
var path = require('path')
var spawn = require('child_process').spawn
var Step = require('step')

// Built-in rules for project-type detection
var DEFAULT_PROJECT_TYPE_RULES = [

  // Node
  {filename:"package.json", grep:/express/i, language:"node.js", framework:"express", prepare:"npm install", test:"npm test"},
  {filename:"package.json", grep:/connect/i, language:"node.js", framework:"connect", prepare:"npm install", test:"npm test"},
  {filename:"package.json", exists:true, language:"node.js", framework:null, prepare:"npm install", test:"npm test"},
  // Python
  {filename:"setup.py", grep:/pyramid/i, language:"python", framework:"pyramid"},
  {filename:"manage.py", grep:/django/i, language:"python", framework:"django"},
  {filename:"setup.py", exists:true, language:"python", framework:null},

]

// detection rules which may be added by worker plugins
// rules must contain a property *test* which can either be a string or a function taking a callback.
// if a string, this is a shell command to be executed to run project tests (e.g. "npm install")
// if a function, this accepts a callback argument of signature function(err) which must be called.
//
// rules may contain a property *prepare* which can be a string or a function like *test*. this is for pre-test
// preparations (e.g. "npm install")
//
var detectionRules = []
// pre-start commands which may be added by worker plugins
// E.g. to start mongodb or postgresql
// pre-start commands may be either a string or a function
// XXX: foreground vs daemonprocesses
// if a string, this is a shell command to be executed to run project tests (e.g. "mongod")
// if a function, this accepts a callback argument of signature function(err) which must be called.
var setupActions = []
// teardown commands which may be added by worker plugins
// E.g. to stop mongodb or postgresql
// teardown commands may be either a string or a function
// XXX: foreground vs daemonprocesses
// if a string, this is a shell command to be executed to run project tests (e.g. "kill $PID")
// if a function, this accepts a callback argument of signature function(err) which must be called.
var teardownActions = []

function registerEvents(emitter) {

  // the queue.new_task event is primary way jobs are submitted
  //

  emitter.on('queue.new_job', function(data) {

    function updateStatus(evType, timeElapsed, opts) {
      var msg = {
        userId:data.user_id,
        jobId:data.job_id,
        timeElapsed:timeElapsed,
        repoUrl:data.repo_config.url,
        stdout: opts.stdout || "",
        stderr: opts.stderr || "",
        stdmerged: opts.stdmerged || "",
        autodetectResult:opts.autodetectResult || null,
        testExitCode: null,
        deployExitCode: null,
      }
      if (opts.testExitCode !== undefined) {
        msg.testExitCode = opts.testExitCode
      }
      if (opts.deployExitCode !== undefined) {
        msg.deployExitCode = opts.deployExitCode
      }

      emitter.emit(evType, msg)
    }

    var stderrBuffer = ""
    var stdoutBuffer = ""
    var stdmergedBuffer = ""

    function forkProc(t1, cwd, shell) {
      var split = shell.split(/\s+/)
      var cmd = split[0]
      var args = split.slice(1)
      var env = process.env
      env.PAAS_NAME = 'strider'
      var proc = spawn(cmd, args, {cwd: cwd, env: env})

      proc.stderrBuffer = ""
      proc.stdoutBuffer = ""
      proc.stdmergedBuffer = ""

      proc.stdout.setEncoding('utf8')
      proc.stderr.setEncoding('utf8')

      proc.stdout.on('data', function(buf) {
        proc.stdoutBuffer += buf
        proc.stdmergedBuffer += buf
        stdoutBuffer += buf
        stdmergedBuffer += buf
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        updateStatus("queue.task_update", elapsed, {stdout:buf})
      })

      proc.stderr.on('data', function(buf) {
        proc.stderrBuffer += buf
        proc.stdmergedBuffer += buf
        stderrBuffer += buf
        stdmergedBuffer += buf
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        updateStatus("queue.task_update", elapsed, {stderr:buf})
      })

      return proc
    }

    function doTestRun(cwd, prepareCmd, testCmd) {
      var preProc = forkProc(t1, cwd, prepareCmd)
      preProc.on('exit', function(exitCode) {
        console.log("process exited with code: %d", exitCode)
        if (exitCode === 0) {
          // Preparatory phase completed OK - continue
          var testProc = forkProc(t1, cwd, testCmd)

          testProc.on('exit', function(exitCode) {
            var t2 = new Date()
            var elapsed = (t2.getTime() - t1.getTime()) / 1000
            updateStatus("queue.task_complete", elapsed, {
              stderr:stderrBuffer,
              stdout:stdoutBuffer,
              stdmerged:stdmergedBuffer,
              testExitCode:exitCode,
              deployExitCode:null
            })
          })
        } else {
          var t2 = new Date()
          var elapsed = (t2.getTime() - t1.getTime()) / 1000
          updateStatus("queue.task_complete", elapsed, {
            stderr:stderrBuffer,
            stdout:stdoutBuffer,
            stdmerged:stdmergedBuffer,
            testExitCode:exitCode,
            deployExitCode:null
          })

        }
      })
    }

    var dir = path.join(__dirname, '_work')
    console.log('new job')
    var t1 = new Date()
    Step(
      function() {
        // XXX: Support incremental builds at some point
        exec('rm -rf ' + dir + ' ; mkdir -p ' + dir, this)
      },
      function(err) {
        if (err) throw err
        console.log("cloning %s", data.repo_ssh_url)
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        var msg = "Starting git clone of repo at " + data.repo_ssh_url + "\n"
        updateStatus("queue.task_update", elapsed, {stdout:msg, stdmerged:msg})
        gitane.run(dir, data.repo_config.privkey, 'git clone ' + data.repo_ssh_url, this)
      },
      function(err, stderr, stdout) {
        if (err) throw err
        this.workingDir = path.join(dir, path.basename(data.repo_ssh_url.replace('.git', '')))
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        var msg = "Git clone complete\n"
        updateStatus("queue.task_update", elapsed, {stdout:stdout, stderr:stderr, stdmerged:stdout+stderr})
        updateStatus("queue.task_update", elapsed, {stdout:msg, stdmerged:msg})
        gumshoe.run(this.workingDir, detectionRules, this)
      },
      function(err, result) {
        if (err) throw err
        // XXX: Setup
        doTestRun(this.workingDir, result.prepare, result.test)
        // XXX: Teardown

      }

    )
  })
}

// Add an array of detection rules
function addDetectionRules(r) {
  detectionRules = detectionRules.concat(r)
}

// Add a single detection rule
function addDetectionRule(r) {
  detectionRules.push(r)

}

module.exports = function(context, cb) {
  // XXX test purposes
  detectionRules = DEFAULT_PROJECT_TYPE_RULES
  // Build a worker context, which is a stripped-down version of the webapp context
  var workerContext = {
    addDetectionRule:addDetectionRule,
    addDetectionRules:addDetectionRules,
    config: context.config,
    emitter: context.emitter,
    extdir: context.extdir,
  }

  Step(
    function() {
      context.loader.initExtensions(context.extdir, "worker", workerContext, null, this)
    },
    function(err, initialized) {
      registerEvents(context.emitter)
      console.log("Strider Simple Worker ready")
      cb(null, null)
    }
  )

}
<commit_msg>comment.<commit_after>// Strider Simple Worker
// Niall O'Higgins 2012
//
// A simple, in-process worker implementation for Strider.
//

var exec = require('child_process').exec
var gitane = require('gitane')
var gumshoe = require('gumshoe')
var path = require('path')
var spawn = require('child_process').spawn
var Step = require('step')

// Built-in rules for project-type detection
var DEFAULT_PROJECT_TYPE_RULES = [

  // Node
  {filename:"package.json", grep:/express/i, language:"node.js", framework:"express", prepare:"npm install", test:"npm test"},
  {filename:"package.json", grep:/connect/i, language:"node.js", framework:"connect", prepare:"npm install", test:"npm test"},
  {filename:"package.json", exists:true, language:"node.js", framework:null, prepare:"npm install", test:"npm test"},
  // Python
  {filename:"setup.py", grep:/pyramid/i, language:"python", framework:"pyramid"},
  {filename:"manage.py", grep:/django/i, language:"python", framework:"django"},
  {filename:"setup.py", exists:true, language:"python", framework:null},

]

// detection rules which may be added by worker plugins
// rules must contain a property *test* which can either be a string or a function taking a callback.
// if a string, this is a shell command to be executed to run project tests (e.g. "npm install")
// if a function, this accepts a callback argument of signature function(err) which must be called.
//
// rules may contain a property *prepare* which can be a string or a function like *test*. this is for pre-test
// preparations (e.g. "npm install")
//
var detectionRules = []
// pre-start commands which may be added by worker plugins
// E.g. to start mongodb or postgresql
// pre-start commands may be either a string or a function
// XXX: foreground vs daemonprocesses
// if a string, this is a shell command to be executed to run project tests (e.g. "mongod")
// if a function, this accepts a callback argument of signature function(err) which must be called.
var setupActions = []
// teardown commands which may be added by worker plugins
// E.g. to stop mongodb or postgresql
// teardown commands may be either a string or a function
// XXX: foreground vs daemonprocesses
// if a string, this is a shell command to be executed to run project tests (e.g. "kill $PID")
// if a function, this accepts a callback argument of signature function(err) which must be called.
var teardownActions = []

function registerEvents(emitter) {

  // the queue.new_task event is primary way jobs are submitted
  //

  emitter.on('queue.new_job', function(data) {

    function updateStatus(evType, timeElapsed, opts) {
      var msg = {
        userId:data.user_id,
        jobId:data.job_id,
        timeElapsed:timeElapsed,
        repoUrl:data.repo_config.url,
        stdout: opts.stdout || "",
        stderr: opts.stderr || "",
        stdmerged: opts.stdmerged || "",
        autodetectResult:opts.autodetectResult || null,
        testExitCode: null,
        deployExitCode: null,
      }
      if (opts.testExitCode !== undefined) {
        msg.testExitCode = opts.testExitCode
      }
      if (opts.deployExitCode !== undefined) {
        msg.deployExitCode = opts.deployExitCode
      }

      emitter.emit(evType, msg)
    }

    // cross-process (per-job) output buffers
    var stderrBuffer = ""
    var stdoutBuffer = ""
    var stdmergedBuffer = ""

    function forkProc(t1, cwd, shell) {
      var split = shell.split(/\s+/)
      var cmd = split[0]
      var args = split.slice(1)
      var env = process.env
      env.PAAS_NAME = 'strider'
      var proc = spawn(cmd, args, {cwd: cwd, env: env})

      // per-process output buffers
      proc.stderrBuffer = ""
      proc.stdoutBuffer = ""
      proc.stdmergedBuffer = ""

      proc.stdout.setEncoding('utf8')
      proc.stderr.setEncoding('utf8')

      proc.stdout.on('data', function(buf) {
        proc.stdoutBuffer += buf
        proc.stdmergedBuffer += buf
        stdoutBuffer += buf
        stdmergedBuffer += buf
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        updateStatus("queue.task_update", elapsed, {stdout:buf})
      })

      proc.stderr.on('data', function(buf) {
        proc.stderrBuffer += buf
        proc.stdmergedBuffer += buf
        stderrBuffer += buf
        stdmergedBuffer += buf
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        updateStatus("queue.task_update", elapsed, {stderr:buf})
      })

      return proc
    }

    function doTestRun(cwd, prepareCmd, testCmd) {
      var preProc = forkProc(t1, cwd, prepareCmd)
      preProc.on('exit', function(exitCode) {
        console.log("process exited with code: %d", exitCode)
        if (exitCode === 0) {
          // Preparatory phase completed OK - continue
          var testProc = forkProc(t1, cwd, testCmd)

          testProc.on('exit', function(exitCode) {
            var t2 = new Date()
            var elapsed = (t2.getTime() - t1.getTime()) / 1000
            updateStatus("queue.task_complete", elapsed, {
              stderr:stderrBuffer,
              stdout:stdoutBuffer,
              stdmerged:stdmergedBuffer,
              testExitCode:exitCode,
              deployExitCode:null
            })
          })
        } else {
          var t2 = new Date()
          var elapsed = (t2.getTime() - t1.getTime()) / 1000
          updateStatus("queue.task_complete", elapsed, {
            stderr:stderrBuffer,
            stdout:stdoutBuffer,
            stdmerged:stdmergedBuffer,
            testExitCode:exitCode,
            deployExitCode:null
          })

        }
      })
    }

    var dir = path.join(__dirname, '_work')
    console.log('new job')
    var t1 = new Date()
    Step(
      function() {
        // XXX: Support incremental builds at some point
        exec('rm -rf ' + dir + ' ; mkdir -p ' + dir, this)
      },
      function(err) {
        if (err) throw err
        console.log("cloning %s", data.repo_ssh_url)
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        var msg = "Starting git clone of repo at " + data.repo_ssh_url + "\n"
        updateStatus("queue.task_update", elapsed, {stdout:msg, stdmerged:msg})
        gitane.run(dir, data.repo_config.privkey, 'git clone ' + data.repo_ssh_url, this)
      },
      function(err, stderr, stdout) {
        if (err) throw err
        this.workingDir = path.join(dir, path.basename(data.repo_ssh_url.replace('.git', '')))
        var t2 = new Date()
        var elapsed = (t2.getTime() - t1.getTime()) / 1000
        var msg = "Git clone complete\n"
        updateStatus("queue.task_update", elapsed, {stdout:stdout, stderr:stderr, stdmerged:stdout+stderr})
        updateStatus("queue.task_update", elapsed, {stdout:msg, stdmerged:msg})
        gumshoe.run(this.workingDir, detectionRules, this)
      },
      function(err, result) {
        if (err) throw err
        // XXX: Setup
        doTestRun(this.workingDir, result.prepare, result.test)
        // XXX: Teardown

      }

    )
  })
}

// Add an array of detection rules
function addDetectionRules(r) {
  detectionRules = detectionRules.concat(r)
}

// Add a single detection rule
function addDetectionRule(r) {
  detectionRules.push(r)

}

module.exports = function(context, cb) {
  // XXX test purposes
  detectionRules = DEFAULT_PROJECT_TYPE_RULES
  // Build a worker context, which is a stripped-down version of the webapp context
  var workerContext = {
    addDetectionRule:addDetectionRule,
    addDetectionRules:addDetectionRules,
    config: context.config,
    emitter: context.emitter,
    extdir: context.extdir,
  }

  Step(
    function() {
      context.loader.initExtensions(context.extdir, "worker", workerContext, null, this)
    },
    function(err, initialized) {
      registerEvents(context.emitter)
      console.log("Strider Simple Worker ready")
      cb(null, null)
    }
  )

}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Vanderbilt University, All rights reserved.
 *
 * Author: Tamas Kecskes
 */

var requirejs = require('requirejs');
requirejs.config({
	nodeRequire: require,
	baseUrl: __dirname,
    paths:{
        "logManager": "common/LogManager"
    }
});

module.exports = {
	clientStorage: requirejs('storage/clientstorage'),
	serverStorage: requirejs('storage/serverstorage'),
	core: requirejs('core/core'),
    standaloneServer: requirejs('server/standalone')
};
<commit_msg>now server user storage also available with library access<commit_after>/*
 * Copyright (C) 2013 Vanderbilt University, All rights reserved.
 *
 * Author: Tamas Kecskes
 */

var requirejs = require('requirejs');
requirejs.config({
	nodeRequire: require,
	baseUrl: __dirname,
    paths:{
        "logManager": "common/LogManager"
    }
});

module.exports = {
	clientStorage: requirejs('storage/clientstorage'),
	serverStorage: requirejs('storage/serverstorage'),
    serverUserStorage: requirejs('storage/serveruserstorage'),
	core: requirejs('core/core'),
    standaloneServer: requirejs('server/standalone')
};
<|endoftext|>


<commit_before>describe('OPATDischargeCtrl', function (){
    var $controller, $scope, $httpBackend, $modalInstance, $modal;
    var Episode, Item;
    var controller, growl;
    var episode, options, tags;

    var columns = {
        "default": [
            {
                name: 'demographics',
                single: true,
                fields: [
                    {name: 'name', type: 'string'},
                    {name: 'date_of_birth', type: 'date'},
                ]},
            {
                name: 'location',
                single: true,
                fields: [
                    {name: 'category', type: 'string'},
                    {name: 'hospital', type: 'string'},
                    {name: 'ward', type: 'string'},
                    {name: 'bed', type: 'string'},
                    {name: 'date_of_admission', type: 'date'},
                    {name: 'tags', type: 'list'},
                ]},
            {
                name: 'diagnosis',
                single: false,
                fields: [
                    {name: 'condition', type: 'string'},
                    {name: 'provisional', type: 'boolean'},
                ]},
            {
                name: 'tagging',
                single: true,
                fields: [
                    { name: 'mine', type: 'boolean' }
                ]
            },
            {
                name: 'opat_meta',
                single: false,
                fields: [
                    { name: 'review_date', type: 'date' },
                    { name: 'reason_for_stopping', type: 'string' },
                    { name: 'unplanned_stop_reason', type: 'string' },
                    { name: 'stopping_iv_details', type: 'string' },
                    { name: 'treatment_outcome', type: 'string' },
                    { name: 'deceased', type: 'boolean' },
                    { name: 'death_category', type: 'string' },
                    { name: 'cause_of_death', type: 'string' },
                    { name: 'readmitted', type: 'boolean' },
                    { name: 'readmission_cause', type: 'string' },
                    { name: 'notes', type: 'text' },
                ]
            },
            {
                name  : 'opat_outcome',
                single: false,
                fields: [
                ]
            }

        ]
    };

    beforeEach(module('opal.controllers'));

    beforeEach(function(){
        inject(function($injector){
            $rootScope   = $injector.get('$rootScope');
            $scope       = $rootScope.$new();
            $modal       = $injector.get('$modal');
            $controller  = $injector.get('$controller');
            $httpBackend = $injector.get('$httpBackend');
            Episode      = $injector.get('Episode');
            Item         = $injector.get('Item');

            fields = {}
            _.each(columns.default, function(c){fields[c.name] = c});
            $rootScope.fields = fields;

            $modalInstance = $modal.open({template: 'Not a real template'});
            episode = new Episode({id: 33, tagging: [{opat: true}], demographics: [{}]});
            options = {};
            tags    = {};
            growl   = {success: jasmine.createSpy('Growl.success')}

            controller = $controller('OPATDischargeCtrl', {
                $scope        : $scope,
                $modalInstance: $modalInstance,
                episode       : episode,
                options       : options,
                tags          : tags,
                growl         : growl
            });

        })
    });

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('Should set up state', function () {
        expect($scope.episode).toBe(episode);
    });

    describe('completed_therapy()', function (){
        var metavars;

        beforeEach(function(){
            metavars = {
                review_date   : '22/12/1999',
                outcome       : 'died',
                died          : true,
                cause_of_death: 'negligence',
                death_category: 'preventable',
                readmitted    : false,
                outcome       : 'death',
                notes         : 'whoops'
            }
            $scope.meta = metavars;
            var meta2 = angular.copy(metavars);
            meta2.episode_id = 33;
            meta2.review_date = '1999-12-22';
            meta2.treatment_outcome = 'death';
            meta2.deceased = true;
            delete meta2['outcome'];
            delete meta2['died'];

            // Should save the metadata
            $httpBackend.expectPOST('/api/v0.1/opat_meta/', meta2).respond('Yes');
            // Should update the teams
            var taggingdata = {opat_current: false, opat_followup: false, id: 33}
            $httpBackend.expectPUT('/api/v0.1/tagging/33/', taggingdata).respond('Yes');
            // Should set the discharge date
            var episode_data = {
                discharge_date: moment().format('YYYY-MM-DD'),
                id: 33
            }
            $httpBackend.expectPUT('/episode/33/', episode_data).respond('Yes');
        });

        it('Should close the mdoal', function () {
            spyOn($modalInstance, 'close');
            $scope.completed_therapy();
            $httpBackend.flush();
            expect($modalInstance.close).toHaveBeenCalledWith('discharged');
        });

        it('Should send a growl message', function () {
            $scope.completed_therapy();
            $httpBackend.flush();
            expect(growl.success).toHaveBeenCalled();
        });
    });

});
<commit_msg>Don't delete the expectations that we expect.<commit_after>describe('OPATDischargeCtrl', function (){
    var $controller, $scope, $httpBackend, $modalInstance, $modal;
    var Episode, Item;
    var controller, growl;
    var episode, options, tags;

    var columns = {
        "default": [
            {
                name: 'demographics',
                single: true,
                fields: [
                    {name: 'name', type: 'string'},
                    {name: 'date_of_birth', type: 'date'},
                ]},
            {
                name: 'location',
                single: true,
                fields: [
                    {name: 'category', type: 'string'},
                    {name: 'hospital', type: 'string'},
                    {name: 'ward', type: 'string'},
                    {name: 'bed', type: 'string'},
                    {name: 'date_of_admission', type: 'date'},
                    {name: 'tags', type: 'list'},
                ]},
            {
                name: 'diagnosis',
                single: false,
                fields: [
                    {name: 'condition', type: 'string'},
                    {name: 'provisional', type: 'boolean'},
                ]},
            {
                name: 'tagging',
                single: true,
                fields: [
                    { name: 'mine', type: 'boolean' }
                ]
            },
            {
                name: 'opat_meta',
                single: false,
                fields: [
                    { name: 'review_date', type: 'date' },
                    { name: 'reason_for_stopping', type: 'string' },
                    { name: 'unplanned_stop_reason', type: 'string' },
                    { name: 'stopping_iv_details', type: 'string' },
                    { name: 'treatment_outcome', type: 'string' },
                    { name: 'deceased', type: 'boolean' },
                    { name: 'death_category', type: 'string' },
                    { name: 'cause_of_death', type: 'string' },
                    { name: 'readmitted', type: 'boolean' },
                    { name: 'readmission_cause', type: 'string' },
                    { name: 'notes', type: 'text' },
                ]
            },
            {
                name  : 'opat_outcome',
                single: false,
                fields: [
                ]
            }

        ]
    };

    beforeEach(module('opal.controllers'));

    beforeEach(function(){
        inject(function($injector){
            $rootScope   = $injector.get('$rootScope');
            $scope       = $rootScope.$new();
            $modal       = $injector.get('$modal');
            $controller  = $injector.get('$controller');
            $httpBackend = $injector.get('$httpBackend');
            Episode      = $injector.get('Episode');
            Item         = $injector.get('Item');

            fields = {}
            _.each(columns.default, function(c){fields[c.name] = c});
            $rootScope.fields = fields;

            $modalInstance = $modal.open({template: 'Not a real template'});
            episode = new Episode({id: 33, tagging: [{opat: true}], demographics: [{}]});
            options = {};
            tags    = {};
            growl   = {success: jasmine.createSpy('Growl.success')}

            controller = $controller('OPATDischargeCtrl', {
                $scope        : $scope,
                $modalInstance: $modalInstance,
                episode       : episode,
                options       : options,
                tags          : tags,
                growl         : growl
            });

        })
    });

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('Should set up state', function () {
        expect($scope.episode).toBe(episode);
    });

    describe('completed_therapy()', function (){
        var metavars;

        beforeEach(function(){
            metavars = {
                review_date   : '22/12/1999',
                outcome       : 'died',
                died          : true,
                cause_of_death: 'negligence',
                death_category: 'preventable',
                readmitted    : false,
                outcome       : 'death',
                notes         : 'whoops'
            }
            $scope.meta = metavars;
            var meta2 = angular.copy(metavars);
            meta2.episode_id = 33;
            meta2.review_date = '1999-12-22';
            meta2.treatment_outcome = 'death';
            meta2.deceased = true;
            delete meta2['outcome'];
            delete meta2['died'];

            // Should save the metadata
            $httpBackend.expectPOST('/api/v0.1/opat_meta/', meta2).respond('Yes');
            // Should update the teams
            var taggingdata = {opat_current: false, opat_followup: false, id: 33}
            $httpBackend.expectPUT('/api/v0.1/tagging/33/', taggingdata).respond('Yes');
            // Should set the discharge date
            var episode_data = {
                discharge_date: moment().format('YYYY-MM-DD'),
                id: 33
            }
            $httpBackend.expectPUT('/episode/33/', episode_data).respond('Yes');
            $httpBackend.expectPOST('/api/v0.1/opat_outcome/').respond('Yes');
        });

        it('Should close the mdoal', function () {
            spyOn($modalInstance, 'close');
            $scope.completed_therapy();
            $httpBackend.flush();
            expect($modalInstance.close).toHaveBeenCalledWith('discharged');
        });

        it('Should send a growl message', function () {
            $scope.completed_therapy();
            $httpBackend.flush();
            expect(growl.success).toHaveBeenCalled();
        });
    });

});
<|endoftext|>


<commit_before>// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// Author Larry Kuhns 2011

import { StaveModifier } from './stavemodifier';
import { Glyph } from './glyph';

export class Repetition extends StaveModifier {
  static get CATEGORY() { return 'repetitions'; }
  static get type() {
    return {
      NONE: 1,         // no coda or segno
      CODA_LEFT: 2,    // coda at beginning of stave
      CODA_RIGHT: 3,   // coda at end of stave
      SEGNO_LEFT: 4,   // segno at beginning of stave
      SEGNO_RIGHT: 5,  // segno at end of stave
      DC: 6,           // D.C. at end of stave
      DC_AL_CODA: 7,   // D.C. al coda at end of stave
      DC_AL_FINE: 8,   // D.C. al Fine end of stave
      DS: 9,           // D.S. at end of stave
      DS_AL_CODA: 10,  // D.S. al coda at end of stave
      DS_AL_FINE: 11,  // D.S. al Fine at end of stave
      FINE: 12,        // Fine at end of stave
      TO_CODA: 13,     // To Coda at end of stave
    };
  }

  constructor(type, x, y_shift) {
    super();
    this.setAttribute('type', 'Repetition');

    this.symbol_type = type;
    this.x = x;
    this.x_shift = 0;
    this.y_shift = y_shift;
    this.font = {
      family: 'times',
      size: 12,
      weight: 'bold italic',
    };
  }

  getCategory() { return Repetition.CATEGORY; }
  setShiftX(x) { this.x_shift = x; return this; }
  setShiftY(y) { this.y_shift = y; return this; }

  setX(x) {
    this.x = x; return this;
  }

  draw(stave, x) {
    this.setRendered();

    switch (this.symbol_type) {
      case Repetition.type.CODA_RIGHT:
        this.drawCodaFixed(stave, x + stave.width);
        break;
      case Repetition.type.CODA_LEFT:
        this.drawSymbolText(stave, x, 'Coda', true);
        break;
      case Repetition.type.SEGNO_LEFT:
        this.drawSignoFixed(stave, x);
        break;
      case Repetition.type.SEGNO_RIGHT:
        this.drawSignoFixed(stave, x + stave.width);
        break;
      case Repetition.type.DC:
        this.drawSymbolText(stave, x, 'D.C.', false);
        break;
      case Repetition.type.DC_AL_CODA:
        this.drawSymbolText(stave, x, 'D.C. al', true);
        break;
      case Repetition.type.DC_AL_FINE:
        this.drawSymbolText(stave, x, 'D.C. al Fine', false);
        break;
      case Repetition.type.DS:
        this.drawSymbolText(stave, x, 'D.S.', false);
        break;
      case Repetition.type.DS_AL_CODA:
        this.drawSymbolText(stave, x, 'D.S. al', true);
        break;
      case Repetition.type.DS_AL_FINE:
        this.drawSymbolText(stave, x, 'D.S. al Fine', false);
        break;
      case Repetition.type.FINE:
        this.drawSymbolText(stave, x, 'Fine', false);
        break;
      // VexFlowPatch: added TO_CODA type, handling
      case Repetition.type.TO_CODA:
        this.drawSymbolText(stave, x, 'To', true);
        break;
      default:
        break;
    }

    return this;
  }

  drawCodaFixed(stave, x) {
    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift;
    Glyph.renderGlyph(stave.context, this.x + x + this.x_shift, y + 25, 40, 'v4d', true);
    return this;
  }

  drawSignoFixed(stave, x) {
    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift;
    Glyph.renderGlyph(stave.context, this.x + x + this.x_shift, y + 25, 30, 'v8c', true);
    return this;
  }

  drawSymbolText(stave, x, text, draw_coda) {
    const ctx = stave.checkContext();

    ctx.save();
    ctx.setFont(this.font.family, this.font.size, this.font.weight);
    // Default to right symbol
    let text_x = 0 + this.x_shift;
    let symbol_x = x + this.x_shift;
    if (this.symbol_type === Repetition.type.CODA_LEFT) {
      // Offset Coda text to right of stave beginning
      text_x = this.x + stave.options.vertical_bar_width;
      symbol_x = text_x + ctx.measureText(text).width + 12;
    } else if (this.symbol_type === Repetition.type.TO_CODA) {
      // text_x = x + this.x + this.x_shift + stave.options.vertical_bar_width;
      // symbol_x = text_x + ctx.measureText(text).width + 12;

      // VexFlowPatch: fix placement, like for DS_AL_CODA
      this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
      // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
      text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
      symbol_x = text_x + ctx.measureText(text).width + 12;
    } else if (this.symbol_type === Repetition.type.DS_AL_CODA) {
      this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
      // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
      text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
      symbol_x = text_x + ctx.measureText(text).width + 12;
    } else {
      // Offset Signo text to left stave end
      symbol_x = this.x + x + stave.width - 5 + this.x_shift;
      text_x = symbol_x - + ctx.measureText(text).width - 12;
    }

    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift + 25;
    if (draw_coda) {
      Glyph.renderGlyph(ctx, symbol_x, y, 40, 'v4d', true);
    }

    ctx.fillText(text, text_x, y + 5);
    ctx.restore();

    return this;
  }
}
<commit_msg>fix(RepetitionSymbols): fix d.c., d.s. x position (#990)<commit_after>// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// Author Larry Kuhns 2011

import { StaveModifier } from './stavemodifier';
import { Glyph } from './glyph';

export class Repetition extends StaveModifier {
  static get CATEGORY() { return 'repetitions'; }
  static get type() {
    return {
      NONE: 1,         // no coda or segno
      CODA_LEFT: 2,    // coda at beginning of stave
      CODA_RIGHT: 3,   // coda at end of stave
      SEGNO_LEFT: 4,   // segno at beginning of stave
      SEGNO_RIGHT: 5,  // segno at end of stave
      DC: 6,           // D.C. at end of stave
      DC_AL_CODA: 7,   // D.C. al coda at end of stave
      DC_AL_FINE: 8,   // D.C. al Fine end of stave
      DS: 9,           // D.S. at end of stave
      DS_AL_CODA: 10,  // D.S. al coda at end of stave
      DS_AL_FINE: 11,  // D.S. al Fine at end of stave
      FINE: 12,        // Fine at end of stave
      TO_CODA: 13,     // To Coda at end of stave
    };
  }

  constructor(type, x, y_shift) {
    super();
    this.setAttribute('type', 'Repetition');

    this.symbol_type = type;
    this.x = x;
    this.x_shift = 0;
    this.y_shift = y_shift;
    this.font = {
      family: 'times',
      size: 12,
      weight: 'bold italic',
    };
  }

  getCategory() { return Repetition.CATEGORY; }
  setShiftX(x) { this.x_shift = x; return this; }
  setShiftY(y) { this.y_shift = y; return this; }

  setX(x) {
    this.x = x; return this;
  }

  draw(stave, x) {
    this.setRendered();

    switch (this.symbol_type) {
      case Repetition.type.CODA_RIGHT:
        this.drawCodaFixed(stave, x + stave.width);
        break;
      case Repetition.type.CODA_LEFT:
        this.drawSymbolText(stave, x, 'Coda', true);
        break;
      case Repetition.type.SEGNO_LEFT:
        this.drawSignoFixed(stave, x);
        break;
      case Repetition.type.SEGNO_RIGHT:
        this.drawSignoFixed(stave, x + stave.width);
        break;
      case Repetition.type.DC:
        this.drawSymbolText(stave, x, 'D.C.', false);
        break;
      case Repetition.type.DC_AL_CODA:
        this.drawSymbolText(stave, x, 'D.C. al', true);
        break;
      case Repetition.type.DC_AL_FINE:
        this.drawSymbolText(stave, x, 'D.C. al Fine', false);
        break;
      case Repetition.type.DS:
        this.drawSymbolText(stave, x, 'D.S.', false);
        break;
      case Repetition.type.DS_AL_CODA:
        this.drawSymbolText(stave, x, 'D.S. al', true);
        break;
      case Repetition.type.DS_AL_FINE:
        this.drawSymbolText(stave, x, 'D.S. al Fine', false);
        break;
      case Repetition.type.FINE:
        this.drawSymbolText(stave, x, 'Fine', false);
        break;
      // VexFlowPatch: added TO_CODA type, handling
      case Repetition.type.TO_CODA:
        this.drawSymbolText(stave, x, 'To', true);
        break;
      default:
        break;
    }

    return this;
  }

  drawCodaFixed(stave, x) {
    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift;
    Glyph.renderGlyph(stave.context, this.x + x + this.x_shift, y + 25, 40, 'v4d', true);
    return this;
  }

  drawSignoFixed(stave, x) {
    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift;
    Glyph.renderGlyph(stave.context, this.x + x + this.x_shift, y + 25, 30, 'v8c', true);
    return this;
  }

  drawSymbolText(stave, x, text, draw_coda) {
    const ctx = stave.checkContext();

    ctx.save();
    ctx.setFont(this.font.family, this.font.size, this.font.weight);
    // Default to right symbol
    let text_x = 0 + this.x_shift;
    let symbol_x = x + this.x_shift;
    if (this.symbol_type === Repetition.type.CODA_LEFT) {
      // Offset Coda text to right of stave beginning
      text_x = this.x + stave.options.vertical_bar_width;
      symbol_x = text_x + ctx.measureText(text).width + 12;
    } else {
      // VexFlowPatch: fix placement, like for DS_AL_CODA
      this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
      // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
      text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
      symbol_x = text_x + ctx.measureText(text).width + 12;
    }
    // earlier, we applied this to most elements individually, not necessary:
    // } else if (this.symbol_type === Repetition.type.TO_CODA) {
    //   // text_x = x + this.x + this.x_shift + stave.options.vertical_bar_width;
    //   // symbol_x = text_x + ctx.measureText(text).width + 12;

    //   // VexFlowPatch: fix placement, like for DS_AL_CODA
    //   this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
    //   // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
    //   text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
    //   symbol_x = text_x + ctx.measureText(text).width + 12;
    // } else if (this.symbol_type === Repetition.type.DS_AL_CODA) {
    //   this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
    //   // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
    //   text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
    //   symbol_x = text_x + ctx.measureText(text).width + 12;
    // } else {
    //   // Offset Signo text to left stave end
    //   symbol_x = this.x + x + stave.width - 5 + this.x_shift;
    //   text_x = symbol_x - + ctx.measureText(text).width - 12;
    // }

    const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift + 25;
    if (draw_coda) {
      Glyph.renderGlyph(ctx, symbol_x, y, 40, 'v4d', true);
    }

    ctx.fillText(text, text_x, y + 5);
    ctx.restore();

    return this;
  }
}
<|endoftext|>