<commit_before>/*globals exports*/
(function () {
/*
Todos:
0. Note to self: Integrate research from other jml notes
0. Add tests, including cross-browser checks
0. Allow building of generic XML (pass configuration object)
0. Allow array as single first argument
0. Allow building content internally as a string

Todos inspired by JsonML: https://github.com/mckamey/jsonml/blob/master/jsonml-html.js
if (tag.toLowerCase() === 'style' && document.createStyleSheet) {
    // IE requires this interface for styles
    return document.createStyleSheet();
}
boolean attributes?
DOM attributes?
duplicate attributes?
expand with attr_map
table appending
IE object-param handling
canHaveChildren necessary? (attempts to append to script and img)
*/

    'use strict';

    /**
     * Need this function for IE since options weren't otherwise getting added
     * @private
     * @param {DOMElement} parent The parent to which to append the element
     * @param {DOMNode} el The element or other node to append to the parent
     */
    function _appendNode (parent, el) {
        if (parent.nodeName.toLowerCase() === 'select' && el.nodeName.toLowerCase() === 'option') {
            try {
                parent.add(el, null);
            }
            catch (err) {
                parent.add(el); // IE
            }
        }
        else {
            parent.appendChild(el);
        }
    }

    /**
     * Attach event in a cross-browser fashion
     * @param {DOMElement} el DOM element to which to attach the event
     * @param {String} type The DOM event (without 'on') to attach to the element
     * @param {Function} handler The event handler to attach to the element
     * @param {Boolean} [capturing] Whether or not the event should be
     *                                                              capturing (W3C-browsers only); default is false; NOT IN USE
     */
    function _addEvent (el, type, handler, capturing) {
        if (el.addEventListener) { // W3C
            el.addEventListener(type, handler, !!capturing);
        }
        else if (el.attachEvent) { // IE
            el.attachEvent('on' + type, handler);
        }
        else { // OLDER BROWSERS (DOM0)
            el['on' + type] = handler;
        }
    }

    /**
    * Creates a text node of the result of resolving an entity or character reference
    * @param {'entity'|'decimal'|'hexadecimal'} type Type of reference
    * @param {String} prefix Text to prefix immediately after the "&"
    * @param {String} arg The body of the reference
    * @returns {Text} The text node of the resolved reference
    */
    function _createSafeReference (type, prefix, arg) {
        // For security reasons related to innerHTML, we ensure this string only contains potential entity characters
        if (!arg.match(/^\w+$/)) {
            throw 'Bad ' + type;
        }
        var elContainer = document.createElement('div');
        elContainer.innerHTML = '&' + prefix + arg + ';';
        return document.createTextNode(elContainer.innerHTML);
    }

    /**
    * @param {String} n0 Whole expression match (including "-")
    * @param {String} n1 Lower-case letter match
    * @returns {String} Uppercased letter
    */
    function _upperCase (n0, n1) {
        return n1.toUpperCase();
    }

    /**
     * Creates an XHTML or HTML element (XHTML is preferred, but only in browsers that support);
     * Any element after element can be omitted, and any subsequent type or types added afterwards
     * @param {String} el The element to create (by lower-case name)
     * @param {Object} [atts] Attributes to add with the key as the attribute name and value as the
     *                                               attribute value; important for IE where the input element's type cannot
     *                                               be added later after already added to the page
     * @param {DOMElement[]} [children] The optional children of this element (but raw DOM elements
     *                                                                      required to be specified within arrays since
     *                                                                      could not otherwise be distinguished from siblings being added)
     * @param {DOMElement} [parent] The optional parent to which to attach the element (always the last
     *                                                                  unless followed by null, in which case it is the second-to-last)
     * @param {null} [returning] Can use null to indicate an array of elements should be returned
     * @returns {DOMElement} The newly created (and possibly already appended) element or array of elements
     */
    function jml () {
        var i, arg, procValue, p, p2, attVal, val, elContainer, textnode, k, elsl, j, cl, elem = document.createDocumentFragment(), nodes = [], elStr, atts, child = [], argc = arguments.length, argv = arguments, NS_HTML = 'http://www.w3.org/1999/xhtml',
            _getType = function (item) {
                if (typeof item === 'string') {
                    return 'string';
                }
                if (typeof item === 'object') {
                    if (item === null) {
                        return 'null';
                    }
                    if (Array.isArray(item)) {
                        return 'array';
                    }
                    if (item.nodeType === 1) {
                        return 'element';
                    }
                    return 'object';
                }
            };
        for (i = 0; i < argc; i++) {
            arg = argv[i];
            switch (_getType(arg)) {
                case 'null': // null always indicates a place-holder (only needed for last argument if want array returned)
                    if (i === argc - 1) {
                        return nodes.length <= 1 ? nodes[0] : nodes;
                    }
                    break;
                case 'string': // Strings indicate elements
                    switch (arg) {
                        case '!':
                            nodes[nodes.length] = document.createComment(argv[++i]);
                            break;
                        case '?':
                            arg = argv[++i];
                            procValue = val = argv[++i];
                            if (typeof val === 'object') {
                                procValue = [];
                                for (p in val) {
                                    if (val.hasOwnProperty(p)) {
                                        procValue.push(p + '=' + '"' + val[p].replace(/"/g, '\\"') + '"');
                                    }
                                }
                                procValue = procValue.join(' ');
                            }
                            // Firefox allows instructions with ">" in this method, but not if placed directly!
                            nodes[nodes.length] = document.createProcessingInstruction(arg, procValue);
                            break;
                        // Browsers don't support document.createEntityReference, so we just use this as a convenience
                        case '&':
                            nodes[nodes.length] = _createSafeReference('entity', '', argv[++i]);
                            break;
                        case '#': // // Decimal character reference - '#', ['01234'] // Ӓ // probably easier to use JavaScript Unicode escapes
                            nodes[nodes.length] = _createSafeReference('decimal', arg, argv[++i]);
                            break;
                        case '#x': // Hex character reference - '#x', ['123a'] // ሺ // probably easier to use JavaScript Unicode escapes
                            nodes[nodes.length] = _createSafeReference('hexadecimal', arg, argv[++i]);
                            break;
                        case '![':
                            // '![', ['escaped <&> text'] // <![CDATA[escaped <&> text]]>
                            // CDATA valid in XML only, so we'll just treat as text for mutual compatibility
                            // Todo: config (or detection via some kind of document.documentType property?) of whether in XML
                            try {
                                nodes[nodes.length] = document.createCDATASection(argv[++i]);
                            }
                            catch (e) {
                                nodes[nodes.length] = document.createTextNode(argv[++i]);
                            }
                            break;
                        default: // An element
                            elStr = arg;
                            // Fix this to depend on XML/config, not availability of methods
                            // Todo: Add JsonML code to handle name attribute
                            if (document.createElementNS) {
                                elem = document.createElementNS(NS_HTML, elStr);
                            }
                            else {
                                elem = document.createElement(elStr);
                            }
                            nodes[nodes.length] = elem; // Add to parent
                            break;
                    }
                    break;
                case 'object': // Non-DOM-element objects indicate attribute-value pairs
                    atts = arg;
                    for (p in atts) {
                        if (atts.hasOwnProperty(p)) {
                            attVal = atts[p];
                            switch(p) {
                                /*
                                Todos:
                                0. Support style object?
                                0. add '$a' for array of ordered (prefix-)attribute-value arrays
                                0. Allow "xmlns" to accept prefix-value array or array of prefix-value arrays
                                0. {$: ['xhtml', 'div']} for prefixed elements
                                0. Accept array for any attribute with first item as prefix and second as value?
                                0. Add JsonML fix for style attribute and IE
                                0. JSON mode to prevent event addition?
                                */
                                case '#':
                                    nodes[nodes.length] = jml.apply(null, [attVal]); // Nest within array to avoid confusion with elements
                                    break;
                                case '$on':
                                    for (p2 in attVal) {
                                        if (attVal.hasOwnProperty(p2)) {
                                            val = attVal[p2];
                                            if (typeof val === 'function') {
                                                val = [val, false];
                                            }
                                            _addEvent(elem, p2, val[0], val[1]); // element, event name, handler, capturing
                                        }
                                    }
                                    break;
                                case 'className': case 'class':
                                    elem.className = attVal;
                                    break;
                                case 'dataset':
                                    for (p2 in attVal) { // Map can be keyed with hyphenated or camel-cased properties
                                        if (attVal.hasOwnProperty(p2)) {
                                            elem.dataset[p2.replace(/-([a-z])/g, _upperCase)] = attVal[p2];
                                        }
                                    }
                                    break;
                                // Todo: Disable this by default unless configuration explicitly allows (for security)
                                case 'innerHTML':
                                    elem.innerHTML = attVal;
                                    break;
                                case 'selected' : case 'checked': case 'value':
                                    elem[p] = attVal;
                                    break;
                                // float not needed as for style.cssFloat (or style.styleFloat in IE)
                                case 'htmlFor': case 'for':
                                    if (elStr === 'label') {
                                        elem.htmlFor = attVal;
                                        break;
                                    }
                                    elem.setAttribute(p, attVal);
                                    break;
                                default:
                                    if (p.match(/^on/)) {
                                        _addEvent(elem, p.slice(2), attVal, false);
                                        break;
                                    }
                                    elem.setAttribute(p, attVal);
                                    break;
                            }
                        }
                    }
                    break;
                case 'element':
                    /*
                    1) Last element always the parent (put null if don't want parent and want to return array) unless only atts and children (no other elements)
                    2) Individual elements (DOM elements or sequences of string[/object/array]) get added to parent first-in, first-added
                    */
                    if (i === argc - 1 || (i === argc - 2 && argv[i+1] === null)) { // parent
                        for (k = 0, elsl = nodes.length; k < elsl; k++) {
                            _appendNode(arg, nodes[k]);
                        }
                    }
                    else {
                        nodes[nodes.length] = arg;
                    }
                    break;
                case 'array': // Arrays or arrays of arrays indicate child nodes
                    child = arg;
                    for (j = 0, cl = child.length; j < cl; j++) { // Go through children array container to handle elements
                        if (typeof child[j] === 'string') {
                            elem.appendChild(document.createTextNode(child[j]));
                        }
                        else if (Array.isArray(child[j])) { // Arrays representing child elements
                            _appendNode(elem, jml.apply(null, child[j]));
                        }
                        else if (child[j]['#']) {
                            _appendNode(elem, jml.apply(null, [child[j]['#']]));
                        }
                        else { // Single DOM element children
                            _appendNode(elem, child[j]);
                        }
                    }
                    break;
            }
        }
        return nodes[0] || elem;
    }

    // EXPORTS
    (typeof exports === 'undefined' ? window : exports).jml = jml;

}());
<commit_msg>Fix "cdata", test todos<commit_after>/*globals exports*/
(function () {
/*
Todos:
0. Note to self: Integrate research from other jml notes
0. Allow building of generic XML (pass configuration object)
0. Allow array as single first argument
0. Allow building content internally as a string (though allowing DOM methods, etc.?)

Todos inspired by JsonML: https://github.com/mckamey/jsonml/blob/master/jsonml-html.js
if (tag.toLowerCase() === 'style' && document.createStyleSheet) {
    // IE requires this interface for styles
    return document.createStyleSheet();
}
boolean attributes?
DOM attributes?
duplicate attributes?
expand with attr_map
table appending
IE object-param handling
canHaveChildren necessary? (attempts to append to script and img)
*/

    'use strict';

    /**
     * Need this function for IE since options weren't otherwise getting added
     * @private
     * @param {DOMElement} parent The parent to which to append the element
     * @param {DOMNode} el The element or other node to append to the parent
     */
    function _appendNode (parent, el) {
        if (parent.nodeName.toLowerCase() === 'select' && el.nodeName.toLowerCase() === 'option') {
            try {
                parent.add(el, null);
            }
            catch (err) {
                parent.add(el); // IE
            }
        }
        else {
            parent.appendChild(el);
        }
    }

    /**
     * Attach event in a cross-browser fashion
     * @param {DOMElement} el DOM element to which to attach the event
     * @param {String} type The DOM event (without 'on') to attach to the element
     * @param {Function} handler The event handler to attach to the element
     * @param {Boolean} [capturing] Whether or not the event should be
     *                                                              capturing (W3C-browsers only); default is false; NOT IN USE
     */
    function _addEvent (el, type, handler, capturing) {
        if (el.addEventListener) { // W3C
            el.addEventListener(type, handler, !!capturing);
        }
        else if (el.attachEvent) { // IE
            el.attachEvent('on' + type, handler);
        }
        else { // OLDER BROWSERS (DOM0)
            el['on' + type] = handler;
        }
    }

    /**
    * Creates a text node of the result of resolving an entity or character reference
    * @param {'entity'|'decimal'|'hexadecimal'} type Type of reference
    * @param {String} prefix Text to prefix immediately after the "&"
    * @param {String} arg The body of the reference
    * @returns {Text} The text node of the resolved reference
    */
    function _createSafeReference (type, prefix, arg) {
        // For security reasons related to innerHTML, we ensure this string only contains potential entity characters
        if (!arg.match(/^\w+$/)) {
            throw 'Bad ' + type;
        }
        var elContainer = document.createElement('div');
        elContainer.innerHTML = '&' + prefix + arg + ';';
        return document.createTextNode(elContainer.innerHTML);
    }

    /**
    * @param {String} n0 Whole expression match (including "-")
    * @param {String} n1 Lower-case letter match
    * @returns {String} Uppercased letter
    */
    function _upperCase (n0, n1) {
        return n1.toUpperCase();
    }

    /**
     * Creates an XHTML or HTML element (XHTML is preferred, but only in browsers that support);
     * Any element after element can be omitted, and any subsequent type or types added afterwards
     * @param {String} el The element to create (by lower-case name)
     * @param {Object} [atts] Attributes to add with the key as the attribute name and value as the
     *                                               attribute value; important for IE where the input element's type cannot
     *                                               be added later after already added to the page
     * @param {DOMElement[]} [children] The optional children of this element (but raw DOM elements
     *                                                                      required to be specified within arrays since
     *                                                                      could not otherwise be distinguished from siblings being added)
     * @param {DOMElement} [parent] The optional parent to which to attach the element (always the last
     *                                                                  unless followed by null, in which case it is the second-to-last)
     * @param {null} [returning] Can use null to indicate an array of elements should be returned
     * @returns {DOMElement} The newly created (and possibly already appended) element or array of elements
     */
    function jml () {
        var i, arg, procValue, p, p2, attVal, val, elContainer, textnode, k, elsl, j, cl, elem = document.createDocumentFragment(), nodes = [], elStr, atts, child = [], argc = arguments.length, argv = arguments, NS_HTML = 'http://www.w3.org/1999/xhtml',
            _getType = function (item) {
                if (typeof item === 'string') {
                    return 'string';
                }
                if (typeof item === 'object') {
                    if (item === null) {
                        return 'null';
                    }
                    if (Array.isArray(item)) {
                        return 'array';
                    }
                    if (item.nodeType === 1) {
                        return 'element';
                    }
                    return 'object';
                }
            };
        for (i = 0; i < argc; i++) {
            arg = argv[i];
            switch (_getType(arg)) {
                case 'null': // null always indicates a place-holder (only needed for last argument if want array returned)
                    if (i === argc - 1) {
                        return nodes.length <= 1 ? nodes[0] : nodes;
                    }
                    break;
                case 'string': // Strings indicate elements
                    switch (arg) {
                        case '!':
                            nodes[nodes.length] = document.createComment(argv[++i]);
                            break;
                        case '?':
                            arg = argv[++i];
                            procValue = val = argv[++i];
                            if (typeof val === 'object') {
                                procValue = [];
                                for (p in val) {
                                    if (val.hasOwnProperty(p)) {
                                        procValue.push(p + '=' + '"' + val[p].replace(/"/g, '\\"') + '"');
                                    }
                                }
                                procValue = procValue.join(' ');
                            }
                            // Firefox allows instructions with ">" in this method, but not if placed directly!
                            nodes[nodes.length] = document.createProcessingInstruction(arg, procValue);
                            break;
                        // Browsers don't support document.createEntityReference, so we just use this as a convenience
                        case '&':
                            nodes[nodes.length] = _createSafeReference('entity', '', argv[++i]);
                            break;
                        case '#': // // Decimal character reference - '#', ['01234'] // Ӓ // probably easier to use JavaScript Unicode escapes
                            nodes[nodes.length] = _createSafeReference('decimal', arg, argv[++i]);
                            break;
                        case '#x': // Hex character reference - '#x', ['123a'] // ሺ // probably easier to use JavaScript Unicode escapes
                            nodes[nodes.length] = _createSafeReference('hexadecimal', arg, argv[++i]);
                            break;
                        case '![':
                            // '![', ['escaped <&> text'] // <![CDATA[escaped <&> text]]>
                            // CDATA valid in XML only, so we'll just treat as text for mutual compatibility
                            // Todo: config (or detection via some kind of document.documentType property?) of whether in XML
                            try {
                                nodes[nodes.length] = document.createCDATASection(argv[++i]);
                            }
                            catch (e) {
                                nodes[nodes.length] = document.createTextNode(argv[i]); // i already incremented
                            }
                            break;
                        default: // An element
                            elStr = arg;
                            // Fix this to depend on XML/config, not availability of methods
                            // Todo: Add JsonML code to handle name attribute
                            if (document.createElementNS) {
                                elem = document.createElementNS(NS_HTML, elStr);
                            }
                            else {
                                elem = document.createElement(elStr);
                            }
                            nodes[nodes.length] = elem; // Add to parent
                            break;
                    }
                    break;
                case 'object': // Non-DOM-element objects indicate attribute-value pairs
                    atts = arg;
                    for (p in atts) {
                        if (atts.hasOwnProperty(p)) {
                            attVal = atts[p];
                            switch(p) {
                                /*
                                Todos:
                                0. Support style object? / Add JsonML fix for style attribute and IE
                                0. add '$a' for array of ordered (prefix-)attribute-value arrays
                                0. Allow "xmlns" to accept prefix-value array or array of prefix-value arrays
                                0. {$: ['xhtml', 'div']} for prefixed elements
                                0. Accept array for any attribute with first item as prefix and second as value?
                                0. JSON mode to prevent event addition?
                                */
                                case '#':
                                    nodes[nodes.length] = jml.apply(null, [attVal]); // Nest within array to avoid confusion with elements
                                    break;
                                case '$on':
                                    for (p2 in attVal) {
                                        if (attVal.hasOwnProperty(p2)) {
                                            val = attVal[p2];
                                            if (typeof val === 'function') {
                                                val = [val, false];
                                            }
                                            _addEvent(elem, p2, val[0], val[1]); // element, event name, handler, capturing
                                        }
                                    }
                                    break;
                                case 'className': case 'class':
                                    elem.className = attVal;
                                    break;
                                case 'dataset':
                                    for (p2 in attVal) { // Map can be keyed with hyphenated or camel-cased properties
                                        if (attVal.hasOwnProperty(p2)) {
                                            elem.dataset[p2.replace(/-([a-z])/g, _upperCase)] = attVal[p2];
                                        }
                                    }
                                    break;
                                // Todo: Disable this by default unless configuration explicitly allows (for security)
                                case 'innerHTML':
                                    elem.innerHTML = attVal;
                                    break;
                                case 'selected' : case 'checked': case 'value':
                                    elem[p] = attVal;
                                    break;
                                // float not needed as for style.cssFloat (or style.styleFloat in IE)
                                case 'htmlFor': case 'for':
                                    if (elStr === 'label') {
                                        elem.htmlFor = attVal;
                                        break;
                                    }
                                    elem.setAttribute(p, attVal);
                                    break;
                                default:
                                    if (p.match(/^on/)) {
                                        _addEvent(elem, p.slice(2), attVal, false);
                                        break;
                                    }
                                    elem.setAttribute(p, attVal);
                                    break;
                            }
                        }
                    }
                    break;
                case 'element':
                    /*
                    1) Last element always the parent (put null if don't want parent and want to return array) unless only atts and children (no other elements)
                    2) Individual elements (DOM elements or sequences of string[/object/array]) get added to parent first-in, first-added
                    */
                    if (i === argc - 1 || (i === argc - 2 && argv[i+1] === null)) { // parent
                        for (k = 0, elsl = nodes.length; k < elsl; k++) {
                            _appendNode(arg, nodes[k]);
                        }
                    }
                    else {
                        nodes[nodes.length] = arg;
                    }
                    break;
                case 'array': // Arrays or arrays of arrays indicate child nodes
                    child = arg;
                    for (j = 0, cl = child.length; j < cl; j++) { // Go through children array container to handle elements
                        if (typeof child[j] === 'string') {
                            elem.appendChild(document.createTextNode(child[j]));
                        }
                        else if (Array.isArray(child[j])) { // Arrays representing child elements
                            _appendNode(elem, jml.apply(null, child[j]));
                        }
                        else if (child[j]['#']) {
                            _appendNode(elem, jml.apply(null, [child[j]['#']]));
                        }
                        else { // Single DOM element children
                            _appendNode(elem, child[j]);
                        }
                    }
                    break;
            }
        }
        return nodes[0] || elem;
    }

    // EXPORTS
    (typeof exports === 'undefined' ? window : exports).jml = jml;

}());
<|endoftext|>


<commit_before>var BaseEvent = require('./base-event.js');

module.exports = BaseEvent(keyLambda);

function keyLambda(ev, broadcast) {
    var key = this.opts.key;

    if (ev.keyCode === key) {
        broadcast(this.data);
    }
}
<commit_msg>Add `preventDefault` to key events<commit_after>var BaseEvent = require('./base-event.js');

module.exports = BaseEvent(keyLambda);

function keyLambda(ev, broadcast) {
    var key = this.opts.key;

    if (ev.keyCode === key) {
        if (this.opts.preventDefault && ev.preventDefault) {
            ev.preventDefault();
        }

        broadcast(this.data);
    }
}
<|endoftext|>


<commit_before>var R = require('ramda');

var tokenStates = [
  {
    name: 'space',
    match: /\s/,
    next: ['space'],
    enter: true,
    exit: 'space'
  },
  {
    name: 'num',
    match: /\d/,
    next: ['num', 'dot'],
    enter: true,
    exit: 'literal'
  },
  {
    name: 'dot',
    match: /\./,
    next: ['num_after_dot'],
    enter: true,
    exit: null
  },
  {
    name: 'num_after_dot',
    match: /\d/,
    next: ['num_after_dot'],
    enter: false,
    exit: 'literal'
  },
  {
    name: 'letter',
    match: /[a-zA-Z_]/,
    next: ['letter', 'num_after_letter'],
    enter: true,
    exit: 'name'
  },
  {
    name: 'num_after_letter',
    match: /\d/,
    next: ['letter', 'num_after_letter'],
    enter: false,
    exit: 'name'
  },
  {
    name: 'open_paren',
    match: /\(/,
    next: [],
    enter: true,
    exit: 'paren'
  },
  {
    name: 'open_bracket',
    match: /\[/,
    next: [],
    enter: true,
    exit: 'paren'
  },
  {
    name: 'close_paren',
    match: /\)/,
    next: [],
    enter: true,
    exit: 'paren'
  },
  {
    name: 'close_bracket',
    match: /\]/,
    next: [],
    enter: true,
    exit: 'paren'
  },
  {
    name: 'caret',
    match: /\^/,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'star',
    match: /\*/,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'div',
    match: /\//,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'plus',
    match: /\+/,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'minus',
    match: /\-/,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'less',
    match: /</,
    next: [],
    enter: true,
    exit: 'operator'
  },
  {
    name: 'greater',
    match: />/,
    next: [],
    enter: true,
    exit: 'operator'
  }
];

// [tokenState] -> [names] -> tokenState
var charToken = R.curry(function(tokenStates, allowedStates, chr) {
  return R.find(R.where(R.mixin(
    { match: function(tsRe) { return tsRe.test(chr); } },
    allowedStates
      ? { name: R.flip(R.contains)(allowedStates) }
      : { enter: true }
  )), tokenStates)
});

// str -> (token, str|undefined)
var chomp = function(str) {
  var i = 0,
      token,
      next = null,
      exit = null;
  while (1) {
    token = charToken(tokenStates, next, str[i]);
    if (!token || (i >= str.length)) break;
    next = token.next;
    exit = token.exit;
    i++;
  }

  return {
    token: { type: exit, value: R.substringTo(i, str) },
    remainingStr: R.substringFrom(i, str)
  };
};

// (str -> (a, str|undefined)) -> str -> [a]
var chompReduce = R.curry(function(chomper, str) {
  function reducer(initial, remainingStr) {
    if (!remainingStr) return initial;  // nothing more to eat!
    var result = chomper(remainingStr);
    return reducer(R.append(result.token, initial), result.remainingStr);
  }
  return reducer([], str);
});

module.exports = chompReduce(chomp);
<commit_msg>use regex... since we are using regex...<commit_after>var R = require('ramda');

var tokenStates = [
  {
    name: 'space',
    match: /\s/,
    next: ['space'],
    enter: true,
    exit: 'space'
  },
  {
    name: 'num',
    match: /\d/,
    next: ['num', 'dot'],
    enter: true,
    exit: 'literal'
  },
  {
    name: 'dot',
    match: /\./,
    next: ['num_after_dot'],
    enter: true,
    exit: null
  },
  {
    name: 'num_after_dot',
    match: /\d/,
    next: ['num_after_dot'],
    enter: false,
    exit: 'literal'
  },
  {
    name: 'first_letter',
    match: /[a-zA-Z_]/,  // like \w, but without 0-9
    next: ['nth_letter'],
    enter: true,
    exit: 'name'
  },
  {
    name: 'nth_letter',
    match: /\w/,
    next: ['nth_letter'],
    enter: false,
    exit: 'name'
  },
  {
    name: 'paren',
    match: /[\(\)\[\]]/,
    next: [],
    enter: true,
    exit: 'paren'
  },
  {
    name: 'caret',
    match: /[\+\-\*\/\^<>]/,
    next: [],
    enter: true,
    exit: 'operator'
  },
];

// [tokenState] -> [names] -> tokenState
var charToken = R.curry(function(tokenStates, allowedStates, chr) {
  return R.find(R.where(R.mixin(
    { match: function(tsRe) { return tsRe.test(chr); } },
    allowedStates
      ? { name: R.flip(R.contains)(allowedStates) }
      : { enter: true }
  )), tokenStates)
});

// str -> (token, str|undefined)
var chomp = function(str) {
  var i = 0,
      token,
      next = null,
      exit = null;
  while (1) {
    token = charToken(tokenStates, next, str[i]);
    if (!token || (i >= str.length)) break;
    next = token.next;
    exit = token.exit;
    i++;
  }

  return {
    token: { type: exit, value: R.substringTo(i, str) },
    remainingStr: R.substringFrom(i, str)
  };
};

// (str -> (a, str|undefined)) -> str -> [a]
var chompReduce = R.curry(function(chomper, str) {
  function reducer(initial, remainingStr) {
    if (!remainingStr) return initial;  // nothing more to eat!
    var result = chomper(remainingStr);
    return reducer(R.append(result.token, initial), result.remainingStr);
  }
  return reducer([], str);
});

module.exports = chompReduce(chomp);
<|endoftext|>


<commit_before>'use strict';

let spawn = require( 'child_process' ).spawnSync
const {run_async} = require('./run_async.js')
let {UserException} = require('./util.js')

const SIMPLE_WARNINGS = true

function cmd_was_run(cmd) {
  return (cmd.stderr != null &&
          cmd.stdout != null )
}

function prt(cmd, verbose=true) {
  let result = {}

  result.command = cmd.args.join(' ')

  if (cmd_was_run(cmd)) {
    result.stderr = cmd.stderr.toString().trim()
    result.stdout = cmd.stdout.toString().trim()
  }
  else {
    result.note = 'not run'
  }
  return result
}

function spawn_xtra(cmd, arg_list) {
  // Enhanced spawn, puts back undocumented .args property which disappeared somewhere in between node 7 and node 9
  // Usage: same as spawn e.g. spawn_xtra('ls', ['-lh', '/'])
  let result_shell_obj = spawn(cmd, arg_list)
  if (result_shell_obj.args == undefined) {
    arg_list.splice(0, 0, cmd)  // arg_list.insert(0, cmd)
    result_shell_obj.args = arg_list
  }
  //result_shell_obj.cmd = cmd
  return result_shell_obj
}

class Base {
  constructor(path) {
    this.path = path
    this.is_default = false
    this.result_shell_ls
    this.result_shell_version
    this.result_shell_file_size
    this.version
    this.size
    this.warnings = []
    this.interpret_stderr_as_stdout_for_getting_version_info = false  // subclass to set if needed
    this.report_obj = {}
  }

  get exists() {
    return this.valid(this.result_shell_ls)
  }

  get runs_ok() {
    return this.valid(this.result_shell_version)
  }

  valid(result_shell_obj) {
    if (! cmd_was_run(result_shell_obj))
      return false

    let accept_stderr_msg_as_valid = result_shell_obj.args[1] == "--version" &&
                                     this.interpret_stderr_as_stdout_for_getting_version_info

    if (accept_stderr_msg_as_valid)  // TODO this is a python specific test in a Base class - not good
      return (result_shell_obj.stderr.length > 0 && result_shell_obj.stderr.toString().indexOf('Python') != -1 ) ||
             (result_shell_obj.stdout.length > 0 && result_shell_obj.stdout.toString().indexOf('Python') != -1 )
    else
      return result_shell_obj.stderr.length == 0
  }

  analyse_is_exe_empty() {
    const regex = /^\s+(\d+)\s{1}\//

    let match = regex.exec(this.result_shell_file_size.stdout.toString())  // match things like "     281 /usr/local/bin/pip"
    if (match != null)
      this.size = parseInt(match[1])
  }

  analyse() {
    this.result_shell_ls = spawn_xtra('ls', ['-lh', this.path])
    this.result_shell_version = spawn_xtra(this.path, ['--version'])
    this.result_shell_file_size = spawn_xtra('wc', ['-c', this.path])

    // console.log(`Base analyse() for ${this.path} ls stderr "${this.result_shell_ls.stderr.toString()}" version stderr "${this.result_shell_version.stderr.toString()}"`)

    if (this.exists) {
      this.analyse_is_exe_empty()
      this.analyse_version()  // template pattern - method declared in subclass

      if (this.version == undefined) {
        debugger
        this.add_warning(`version could not be determined`, this.result_shell_version)
      }
      if (this.size == undefined) this.add_warning(`could not determine file size`, this.result_shell_file_size)
      if (this.size == 0) this.add_warning(`executable file exists but is empty?`,
                                              [this.result_shell_ls, this.result_shell_file_size])
    }
    if (this.exists && ! this.runs_ok) this.add_warning(`${this.path} doesn't run properly`,
                                              [this.result_shell_ls, this.result_shell_version])
  }

  analyse_version() {} // Subclasses should override and set 'this.version'

  add_warning(message, shell_results=[]) {
    // console.log('adding warning', message, 'this.report_line=', this.report_line)
    if (SIMPLE_WARNINGS) {
      this.warnings.push(message)
    }
    else {
      let warning = {}

      if (! Array.isArray(shell_results))
        shell_results = [shell_results]

      warning.reason = message

      if (shell_results.length > 0) {
        if (warning['shell results'] == undefined)
          warning['shell results'] = []

        for (let result of shell_results) {
          let shell_result = prt(result)
          warning['shell results'].push(shell_result)
        }

      this.warnings.push(warning)
      }
    }
  }

  report() {
    this.report_obj = {}
    this.report_obj.path = this.path
    // this.report_obj['exists'] = this.exists
    this.report_obj.is_default = this.is_default
    if (this.exists) {
      this.report_obj.runs_ok = this.runs_ok
      this.report_obj.version = this.version
    }
    if (this.warnings.length > 0)
      this.report_obj.warnings = this.warnings
  }
}

class Python extends Base {
  constructor(path) {
    super(path)
    this.interpret_stderr_as_stdout_for_getting_version_info = true  // cope with python 2 bug which reports python version via stderr rather than stdout
    this.result_shell_site_info
    this.result_shell_run_pip_as_module
    this.sys_path = []
    this.pip_module_version
    this.pip_module_site_package_path
    this.analyse()
  }

  analyse() {
    super.analyse()

    // why run these if this python doesn't exist?
    if (! this.exists)
      return

    this.result_shell_site_info = spawn_xtra( this.path, [ '-m', 'site' ] )
    this.result_shell_run_pip_as_module = spawn_xtra( this.path, [ '-m', 'pip', '--version' ] )
    // why analyse these if this python doesn't exist?
    this.analyse_site_info()
    this.analyse_pip_version()
  }

  analyse_version() {
    const regex = /Python (.*)/

    let match = regex.exec(this.result_shell_version.stderr.toString())  // note: python 2 and python < 3.4 reports python version via stderr rather than stdout
    if (match != null)
      this.version = match[1]
    else {
      // try again with stdout
      match = regex.exec(this.result_shell_version.stdout.toString())
      if (match != null)
        this.version = match[1]
    }
  }

  analyse_site_info() {
    if (! this.valid(this.result_shell_site_info))
      return

    let lines = this.result_shell_site_info.stdout.toString().split("\n")
    let scan = false
    let chunk = ''

    for (let line of lines) {
      if (line == 'sys.path = [') {
        chunk = '['
        scan = true
        continue
      }
      if (scan)
        chunk += line
      if (line == ']')
        scan = false
    }

    function eval_local(chunk){
      "use strict"
      // console.log(`chunk is "${chunk}"`)
      let sys_path = eval(`let x = ${chunk} ; x`);
      return sys_path
    }

    this.sys_path = eval_local(chunk)  // idea from http://stackoverflow.com/questions/9781285/specify-scope-for-eval-in-javascript
  }

  analyse_pip_version() {
    const regex = /pip (.*) from (.*)\/site-packages/   // TODO move to base class - or even this whole method.

    if (! this.exists)
      return

    if (this.result_shell_run_pip_as_module.stderr.length > 0) {
      this.add_warning(`pip module not installed`, this.result_shell_run_pip_as_module)
      return
    }

    let match = regex.exec(this.result_shell_run_pip_as_module.stdout.toString())
    if (match != null) {
      this.pip_module_version = match[1]
      this.pip_module_site_package_path = match[2] + '/site-packages'
    }
    if (this.pip_module_version == undefined) this.add_warning(`pip module not installed`, this.result_shell_run_pip_as_module)
  }

  report() {
    super.report()
    if (this.runs_ok) {
      this.report_obj['sys.path'] = `${this.sys_path.length} entries`

      this.report_obj.pip = {}
      this.report_obj.pip.installed = this.pip_module_version != undefined
      this.report_obj.pip.version = this.pip_module_version
      this.report_obj.pip.site = this.pip_module_site_package_path
    }
  }
}

class Pip extends Base {
  constructor(path) {
    super(path);
    this.site_package_path
    this.site_relationships = {}  // hash where keys are python paths, values are boolean
    this.pythons = []
    this.analyse()
  }

  analyse() {
    super.analyse()
  }

  analyse_version() {
    const regex = /pip (.*) from (.*)\/site-packages/
    // const regex2 = /^\/Library\//

    let match = regex.exec(this.result_shell_version.stdout.toString())
    if (match != null) {
      this.version = match[1]
      this.site_package_path = match[2] + '/site-packages'
    }

    // The directory
    //   /Library/Python/2.7/site-packages
    // used to be used by both system python AND python org python
    // but as of python 2.7.13 is only used by mac system python - NOT python org python anymore cos of incompatibilities
    //
    // let match2 = regex2.exec(this.site_package_path)
    // this.site_package_path_is_non_system_python = (match != null) {
  }

  inform_about(python) {
    this.pythons.push(python)

    // Figure out if pip and this python share a site
    this.site_relationships[python.path] = python.sys_path.length > 0 && python.sys_path.indexOf(this.site_package_path) >= 0
  }

  report() {
    super.report()
    this.report_obj.site = this.site_package_path
    this.report_obj.site_relationships = this.site_relationships  // TODO rename 'associations' to 'site_relationships'
  }
}

class Brain {
  constructor() {
    this.pythons = []
    this.pips = []
    this.python_default
    this.pip_default

    this.find_python('/usr/bin/python')
    this.find_python('/usr/local/bin/python')
    this.find_python('/usr/local/bin/python3')
    this.python_default = this.find_default('python', this.pythons, Python)

    this.find_pip('/usr/bin/pip')
    this.find_pip('/usr/local/bin/pip')
    this.find_pip('/usr/local/bin/pip3')
    this.pip_default = this.find_default('pip', this.pips, Pip)

    this.analyse_relationships()  // inform all pips of all other pythons
    this.report()
  }

  report() {
    for (let python of this.pythons)
      python.report()
    for (let pip of this.pips)
      pip.report()
  }

  find_python(path) {
    let python = new Python(path)
    if (python.exists)
      this.pythons.push(python)
  }

  find_pip(path) {
    let pip = new Pip(path)
    if (pip.exists)
      this.pips.push(pip)
  }

  find_default(cmd, collection, Class) {
    /*
     Find default python or pip, add it to the collection.  If its not already in the collection, then
     create an instance of 'Class' and add it.
     Parameters:
       'cmd' is e.g. 'python' or 'pip'
       'collection' is e.g. this.pythons or this.pips
       'Class' is Python or Pip class
     Returns:
       The Python or Pip instance which is the default, which you should assign to this.python_default or this.pip_default
     */
    let result_shell_which = spawn_xtra('which', [cmd])
    if (result_shell_which.stderr.length != 0)
      throw new UserException(`which ${cmd} failed with error "${result_shell_which.stderr.toString()}" thus cannot determine default ${cmd}`)
    let path_default = result_shell_which.stdout.toString().trim()

    if (path_default == '')  // 'which python' command found no default python
      return undefined

    for (let el of collection)
      if (this.paths_same(path_default, el.path)) {
        // this.python_default = el
        el.is_default = true
        return el  // default python pip is an existing one
      }
    let another = new Class(path_default)
    collection.push(another)  // default python is a totally new python we found e.g. miniconda
    another.is_default = true
    return another
  }

  paths_same(path1, path2) {
    // See if the paths are the same.
    // Also check against the 'stat' version of path2, just in case its a symbolic link

    if (path1 == path2)
      return true

    let path2_symbolic = this.symbolic_path(path2)
    if (path2_symbolic != undefined && (path2_symbolic.indexOf(path1) != -1))
      return true

    return false
  }

  symbolic_path(path) {
    // get the real underlying path
    let result_shell_stat = spawn_xtra('stat', ['-F', path])
    if (result_shell_stat.stderr.length != 0)
      throw new UserException(`"stat" failed with error "${result_shell_stat.stderr.toString()}" thus cannot determine symbolic link behind "${path}"`)

    let symbolic_path = result_shell_stat.stdout.toString()  // TODO parse this properly and get the pure absolute path
    return symbolic_path
  }

  get_python(path) {
    for (let python of this.pythons)
      if (path == python.path)
        return python
    return undefined
  }

  get_pip(path) {
    for (let pip of this.pips)
      if (path == pip.path)
        return pip
    return undefined
  }

  analyse_relationships() {
    // inform all pips of all other pythons
    for (let pip of this.pips)
      for (let python of this.pythons)
        pip.inform_about(python)
  }
}

exports.Python = Python
exports.Pip = Pip
exports.Brain = Brain
<commit_msg>get exists() refactored not to use valid()<commit_after>'use strict';

let spawn = require( 'child_process' ).spawnSync
const {run_async} = require('./run_async.js')
let {UserException} = require('./util.js')

const SIMPLE_WARNINGS = true

function cmd_was_run(cmd) {
  return (cmd.stderr != null &&
          cmd.stdout != null )
}

function prt(cmd, verbose=true) {
  let result = {}

  result.command = cmd.args.join(' ')

  if (cmd_was_run(cmd)) {
    result.stderr = cmd.stderr.toString().trim()
    result.stdout = cmd.stdout.toString().trim()
  }
  else {
    result.note = 'not run'
  }
  return result
}

function spawn_xtra(cmd, arg_list) {
  // Enhanced spawn, puts back undocumented .args property which disappeared somewhere in between node 7 and node 9
  // Usage: same as spawn e.g. spawn_xtra('ls', ['-lh', '/'])
  let result_shell_obj = spawn(cmd, arg_list)
  if (result_shell_obj.args == undefined) {
    arg_list.splice(0, 0, cmd)  // arg_list.insert(0, cmd)
    result_shell_obj.args = arg_list
  }
  //result_shell_obj.cmd = cmd
  return result_shell_obj
}

class Base {
  constructor(path) {
    this.path = path
    this.is_default = false
    this.result_shell_ls
    this.result_shell_version
    this.result_shell_file_size
    this.version
    this.size
    this.warnings = []
    this.interpret_stderr_as_stdout_for_getting_version_info = false  // subclass to set if needed
    this.report_obj = {}
  }

  get exists() {
    if (! cmd_was_run(this.result_shell_ls))
      return false
    return this.result_shell_ls.stderr.length == 0
  }

  get runs_ok() {
    return this.valid(this.result_shell_version)
  }

  valid(result_shell_obj) {
    if (! cmd_was_run(result_shell_obj))
      return false

    let accept_stderr_msg_as_valid = result_shell_obj.args[1] == "--version" &&
                                     this.interpret_stderr_as_stdout_for_getting_version_info

    if (accept_stderr_msg_as_valid)  // TODO this is a python specific test in a Base class - not good
      return (result_shell_obj.stderr.length > 0 && result_shell_obj.stderr.toString().indexOf('Python') != -1 ) ||
             (result_shell_obj.stdout.length > 0 && result_shell_obj.stdout.toString().indexOf('Python') != -1 )
    else
      return result_shell_obj.stderr.length == 0
  }

  analyse_is_exe_empty() {
    const regex = /^\s+(\d+)\s{1}\//

    let match = regex.exec(this.result_shell_file_size.stdout.toString())  // match things like "     281 /usr/local/bin/pip"
    if (match != null)
      this.size = parseInt(match[1])
  }

  analyse() {
    this.result_shell_ls = spawn_xtra('ls', ['-lh', this.path])
    this.result_shell_version = spawn_xtra(this.path, ['--version'])
    this.result_shell_file_size = spawn_xtra('wc', ['-c', this.path])

    // console.log(`Base analyse() for ${this.path} ls stderr "${this.result_shell_ls.stderr.toString()}" version stderr "${this.result_shell_version.stderr.toString()}"`)

    if (this.exists) {
      this.analyse_is_exe_empty()
      this.analyse_version()  // template pattern - method declared in subclass

      if (this.version == undefined)
        this.add_warning(`version could not be determined`, this.result_shell_version)
      if (this.size == undefined)
        this.add_warning(`could not determine file size`, this.result_shell_file_size)
      if (this.size == 0)
        this.add_warning(`executable file exists but is empty?`, [this.result_shell_ls, this.result_shell_file_size])
    }
    if (this.exists && ! this.runs_ok)
      this.add_warning(`${this.path} doesn't run properly`, [this.result_shell_ls, this.result_shell_version])
  }

  analyse_version() {} // Subclasses should override and set 'this.version'

  add_warning(message, shell_results=[]) {
    // console.log('adding warning', message, 'this.report_line=', this.report_line)
    if (SIMPLE_WARNINGS) {
      this.warnings.push(message)
    }
    else {
      let warning = {}

      if (! Array.isArray(shell_results))
        shell_results = [shell_results]

      warning.reason = message

      if (shell_results.length > 0) {
        if (warning['shell results'] == undefined)
          warning['shell results'] = []

        for (let result of shell_results) {
          let shell_result = prt(result)
          warning['shell results'].push(shell_result)
        }

      this.warnings.push(warning)
      }
    }
  }

  report() {
    this.report_obj = {}
    this.report_obj.path = this.path
    // this.report_obj['exists'] = this.exists
    this.report_obj.is_default = this.is_default
    if (this.exists) {
      this.report_obj.runs_ok = this.runs_ok
      this.report_obj.version = this.version
    }
    if (this.warnings.length > 0)
      this.report_obj.warnings = this.warnings
  }
}

class Python extends Base {
  constructor(path) {
    super(path)
    this.interpret_stderr_as_stdout_for_getting_version_info = true  // cope with python 2 bug which reports python version via stderr rather than stdout
    this.result_shell_site_info
    this.result_shell_run_pip_as_module
    this.sys_path = []
    this.pip_module_version
    this.pip_module_site_package_path
    this.analyse()
  }

  analyse() {
    super.analyse()

    // this python doesn't exist
    if (! this.exists)
      return

    this.result_shell_site_info = spawn_xtra( this.path, [ '-m', 'site' ] )
    this.result_shell_run_pip_as_module = spawn_xtra( this.path, [ '-m', 'pip', '--version' ] )
    this.analyse_site_info()
    this.analyse_pip_version()
  }

  analyse_version() {
    const regex = /^Python (.*)/

    let match = regex.exec(this.result_shell_version.stderr.toString())  // note: python 2 and python < 3.4 reports python version via stderr rather than stdout
    if (match != null)
      this.version = match[1]
    else {
      // try again with stdout
      match = regex.exec(this.result_shell_version.stdout.toString())
      if (match != null)
        this.version = match[1]
    }
  }

  analyse_site_info() {
    if (! this.valid(this.result_shell_site_info))
      return

    let lines = this.result_shell_site_info.stdout.toString().split("\n")
    let scan = false
    let chunk = ''

    for (let line of lines) {
      if (line == 'sys.path = [') {
        chunk = '['
        scan = true
        continue
      }
      if (scan)
        chunk += line
      if (line == ']')
        scan = false
    }

    function eval_local(chunk){
      "use strict"
      // console.log(`chunk is "${chunk}"`)
      let sys_path = eval(`let x = ${chunk} ; x`);
      return sys_path
    }

    this.sys_path = eval_local(chunk)  // idea from http://stackoverflow.com/questions/9781285/specify-scope-for-eval-in-javascript
  }

  analyse_pip_version() {
    const regex = /pip (.*) from (.*)\/site-packages/   // TODO move to base class - or even this whole method.

    if (! this.exists)
      return

    if (this.result_shell_run_pip_as_module.stderr.length > 0) {
      this.add_warning(`pip module not installed`, this.result_shell_run_pip_as_module)
      return
    }

    let match = regex.exec(this.result_shell_run_pip_as_module.stdout.toString())
    if (match != null) {
      this.pip_module_version = match[1]
      this.pip_module_site_package_path = match[2] + '/site-packages'
    }
    if (this.pip_module_version == undefined) this.add_warning(`pip module not installed`, this.result_shell_run_pip_as_module)
  }

  report() {
    super.report()
    if (this.runs_ok) {
      this.report_obj['sys.path'] = `${this.sys_path.length} entries`

      this.report_obj.pip = {}
      this.report_obj.pip.installed = this.pip_module_version != undefined
      this.report_obj.pip.version = this.pip_module_version
      this.report_obj.pip.site = this.pip_module_site_package_path
    }
  }
}

class Pip extends Base {
  constructor(path) {
    super(path);
    this.site_package_path
    this.site_relationships = {}  // hash where keys are python paths, values are boolean
    this.pythons = []
    this.analyse()
  }

  analyse() {
    super.analyse()
  }

  analyse_version() {
    const regex = /pip (.*) from (.*)\/site-packages/
    // const regex2 = /^\/Library\//

    let match = regex.exec(this.result_shell_version.stdout.toString())
    if (match != null) {
      this.version = match[1]
      this.site_package_path = match[2] + '/site-packages'
    }

    // The directory
    //   /Library/Python/2.7/site-packages
    // used to be used by both system python AND python org python
    // but as of python 2.7.13 is only used by mac system python - NOT python org python anymore cos of incompatibilities
    //
    // let match2 = regex2.exec(this.site_package_path)
    // this.site_package_path_is_non_system_python = (match != null) {
  }

  inform_about(python) {
    this.pythons.push(python)

    // Figure out if pip and this python share a site
    this.site_relationships[python.path] = python.sys_path.length > 0 && python.sys_path.indexOf(this.site_package_path) >= 0
  }

  report() {
    super.report()
    this.report_obj.site = this.site_package_path
    this.report_obj.site_relationships = this.site_relationships  // TODO rename 'associations' to 'site_relationships'
  }
}

class Brain {
  constructor() {
    this.pythons = []
    this.pips = []
    this.python_default
    this.pip_default

    this.find_python('/usr/bin/python')
    this.find_python('/usr/local/bin/python')
    this.find_python('/usr/local/bin/python3')
    this.python_default = this.find_default('python', this.pythons, Python)

    this.find_pip('/usr/bin/pip')
    this.find_pip('/usr/local/bin/pip')
    this.find_pip('/usr/local/bin/pip3')
    this.pip_default = this.find_default('pip', this.pips, Pip)

    this.analyse_relationships()  // inform all pips of all other pythons
    this.report()
  }

  report() {
    for (let python of this.pythons)
      python.report()
    for (let pip of this.pips)
      pip.report()
  }

  find_python(path) {
    let python = new Python(path)
    if (python.exists)
      this.pythons.push(python)
  }

  find_pip(path) {
    let pip = new Pip(path)
    if (pip.exists)
      this.pips.push(pip)
  }

  find_default(cmd, collection, Class) {
    /*
     Find default python or pip, add it to the collection.  If its not already in the collection, then
     create an instance of 'Class' and add it.
     Parameters:
       'cmd' is e.g. 'python' or 'pip'
       'collection' is e.g. this.pythons or this.pips
       'Class' is Python or Pip class
     Returns:
       The Python or Pip instance which is the default, which you should assign to this.python_default or this.pip_default
     */
    let result_shell_which = spawn_xtra('which', [cmd])
    if (result_shell_which.stderr.length != 0)
      throw new UserException(`which ${cmd} failed with error "${result_shell_which.stderr.toString()}" thus cannot determine default ${cmd}`)
    let path_default = result_shell_which.stdout.toString().trim()

    if (path_default == '')  // 'which python' command found no default python
      return undefined

    for (let el of collection)
      if (this.paths_same(path_default, el.path)) {
        // this.python_default = el
        el.is_default = true
        return el  // default python pip is an existing one
      }
    let another = new Class(path_default)
    collection.push(another)  // default python is a totally new python we found e.g. miniconda
    another.is_default = true
    return another
  }

  paths_same(path1, path2) {
    // See if the paths are the same.
    // Also check against the 'stat' version of path2, just in case its a symbolic link

    if (path1 == path2)
      return true

    let path2_symbolic = this.symbolic_path(path2)
    if (path2_symbolic != undefined && (path2_symbolic.indexOf(path1) != -1))
      return true

    return false
  }

  symbolic_path(path) {
    // get the real underlying path
    let result_shell_stat = spawn_xtra('stat', ['-F', path])
    if (result_shell_stat.stderr.length != 0)
      throw new UserException(`"stat" failed with error "${result_shell_stat.stderr.toString()}" thus cannot determine symbolic link behind "${path}"`)

    let symbolic_path = result_shell_stat.stdout.toString()  // TODO parse this properly and get the pure absolute path
    return symbolic_path
  }

  get_python(path) {
    for (let python of this.pythons)
      if (path == python.path)
        return python
    return undefined
  }

  get_pip(path) {
    for (let pip of this.pips)
      if (path == pip.path)
        return pip
    return undefined
  }

  analyse_relationships() {
    // inform all pips of all other pythons
    for (let pip of this.pips)
      for (let python of this.pythons)
        pip.inform_about(python)
  }
}

exports.Python = Python
exports.Pip = Pip
exports.Brain = Brain
<|endoftext|>


<commit_before>'use strict';

const async = require('async');
const fs = require('fs-extra');
const got = require('got');
const ora = require('ora');
const path = require('path');
const Polly = require('aws-sdk/clients/polly').Presigner;
const spawn = require('child_process').spawn;
const tempfile = require('tempfile');
const textchunk = require('textchunk');

const fileExtensions = {
  mp3: 'mp3',
  ogg_vorbis: 'ogg', // eslint-disable-line camelcase
  pcm: 'pcm',
};

// Set up the spinner.
let spinner = ora().start();
spinner.begin = text => {
  spinner.text = text;
  spinner.start();
};
spinner.end = () => {
  spinner.succeed();
};

exports.checkUsage = (args, proc) => {
  spinner.stop();
  const minNumArgs = 1;
  const script = path.basename(proc.argv[1]);
  const usageStatement = `Converts a text file to speech using the AWS Polly API.
Usage:
  ${script} [INPUTFILE] OUTPUTFILE [OPTIONS]
Standard:
  INPUTFILE           The text file to convert (reads from stdin if not specified)
  OUTPUTFILE          The filename to save the audio to
Options:
  --help              Displays this info and exits
  --access-key KEY    AWS access key ID
  --ffmpeg BINARY     Path to the ffmpeg binary (defaults to the one in PATH)
  --format FORMAT     Target audio format ("mp3", "ogg_vorbis", or "pcm") (default "mp3")
  --lexicon NAME      Apply a stored pronunciation lexicon. Can be specified multiple times.
  --region REGION     AWS region to send requests to (default "us-east-1")
  --sample-rate RATE  Audio frequency, in hertz.
  --secret-key KEY    AWS secret access key
  --throttle SIZE     Number of simultaneous requests allowed against the AWS API (default 5)
  --type TYPE         Type of input text ("text" or "ssml") (default "text")
  --voice VOICE       Voice to use for the speech (default "Joanna")
`;
  if (args.help) {
    proc.stderr.write(usageStatement);
    proc.exit(0);
  }
  if (args._.length < minNumArgs) {
    proc.stderr.write(usageStatement);
    proc.exit(1);
  }
};

// Creates an object containing all the data.
let buildInfo = (text, urlCreator, opts) => {
  return {
    opts: opts,
    tempfile: tempfile(`.${fileExtensions[opts.format]}`),
    text: text,
    urlcreator: urlCreator,
  };
};

// Calls AWS Polly with the given info.
let callAws = (info, i, callback) => {
  const secsPerMin = 60;
  const minsInHalfHour = 30;
  const halfHour = secsPerMin * minsInHalfHour;

  spinner.text = spinner.text.replace(/\d+\//, `${i}/`);

  let url = info.urlcreator({
    LexiconNames: info.opts.lexicon,
    OutputFormat: info.opts.format,
    SampleRate: info.opts['sample-rate'] ? String(info.opts['sample-rate']) : undefined,
    Text: info.text,
    TextType: info.opts.type,
    VoiceId: info.opts.voice
  }, halfHour);

  let error;
  let outputStream = fs.createWriteStream(info.tempfile);
  outputStream.on('close', () => {
    callback(error);
    if (error) {
      // Get the error message from Amazon.
      try {
        let response = JSON.parse(fs.readFileSync(info.tempfile, 'utf8'));
        error.message += `: ${response.message}`;
      } catch (err) {}
    }
  });
  got.stream(url).on('error', err => { error = err; }).pipe(outputStream);
};

// Deletes the manifest and its files.
let cleanup = manifestFile => {
  let manifest = fs.readFileSync(manifestFile, 'utf8');
  let regexpState = /^file\s+'(.*)'$/gm;
  let match;
  while ((match = regexpState.exec(manifest)) !== null) {
    fs.removeSync(match[1]);
  }
  fs.removeSync(manifestFile);
};

// Combines MP3 or OGG files into one file.
let combineEncodedAudio = (binary, manifestFile, outputFile) => {
  let args = [
    '-f', 'concat',
    '-safe', '0',
    '-i', manifestFile,
    '-c', 'copy',
    outputFile
  ];
  return new Promise((resolve, reject) => {
    let ffmpeg = spawn(binary, args);
    let stderr = '';
    ffmpeg.stderr.on('data', (data) => {
      stderr += `\n${data}`;
    });
    ffmpeg.on('error', err => {
      reject(new Error('Could not start ffmpeg process'));
    });
    ffmpeg.on('close', code => {
      if (code > 0) {
        spinner.fail();
        return reject(new Error(`ffmpeg returned an error (${code}): ${stderr}`));
      }
      spinner.end();
      resolve();
    });
  });
};

// Concatenates raw PCM audio into one file.
let combineRawAudio = (manifestFile, outputFile) => {
  let manifest = fs.readFileSync(manifestFile, 'utf8');
  let regexpState = /^file\s+'(.*)'$/gm;
  fs.createFileSync(outputFile);
  fs.truncateSync(outputFile);
  let match;
  while ((match = regexpState.exec(manifest)) !== null) {
    let dataBuffer = fs.readFileSync(match[1]);
    fs.appendFileSync(outputFile, dataBuffer);
  }
  return Promise.resolve();
};

// Combines all the parts into one file.
// Resolves with the new filename.
let combine = (manifestFile, opts) => {
  spinner.begin('Combine audio');
  let newFile = tempfile(`.${fileExtensions[opts.format]}`);
  let combiner = opts.format === 'pcm' ?
    combineRawAudio(manifestFile, newFile) :
    combineEncodedAudio(opts.ffmpeg, manifestFile, newFile);
  return combiner.then(() => {
    spinner.begin('Clean up');
    cleanup(manifestFile);
    spinner.end();
    return newFile;
  }).catch(err => {
    cleanup(manifestFile);
    throw err;
  });
};

// Writes down all the temp files for ffmpeg to read in.
// Returns the text filename.
let createManifest = parts => {
  let txtFile = tempfile('.txt');
  let contents = parts.map(info => {
    return `file '${info.tempfile}'`;
  }).join('\n');
  fs.writeFileSync(txtFile, contents, 'utf8');
  return txtFile;
};

// Create an AWS Polly instance.
let createPolly = opts => {
  return new Polly({
    apiVersion: '2016-06-10',
    region: opts.region,
    accessKeyId: opts['access-key'],
    secretAccessKey: opts['secret-key'],
  });
};

// Calls the API for each text part (throttled). Returns a Promise.
let generateAll = (parts, opts, func) => {
  let count = parts.length;
  spinner.begin(`Convert to audio (0/${count})`);
  return (new Promise((resolve, reject) => {
    async.eachOfLimit(
      parts,
      opts.limit,
      func,
      err => {
        if (err) {
          spinner.fail();
          return reject(err);
        }
        spinner.text = spinner.text.replace(/\d+\//, `${count}/`);
        spinner.end();
        resolve(parts);
      }
    );
  }));
};

// Returns a Promise with the temporary audio file.
exports.generateSpeech = (strParts, opts) => {
  // Add in the default options.
  opts = Object.assign({}, {
    'access-key': opts.accessKey,
    ffmpeg: opts.ffmpeg || 'ffmpeg',
    format: opts.format || 'mp3',
    lexicon: opts.lexicon,
    limit: Number(opts.throttle) || 5, // eslint-disable-line no-magic-numbers
    region: opts.region || 'us-east-1',
    'sample-rate': opts.sampleRate,
    'secret-key': opts.secretKey,
    type: opts.type || 'text',
    voice: opts.voice || 'Joanna'
  }, opts);
  if (typeof opts.lexicon !== 'undefined' && !Array.isArray(opts.lexicon)) {
    opts.lexicon = [opts.lexicon];
  }

  let polly = createPolly(opts);

  // Compile the text parts and options together in a packet.
  let parts = strParts.map(part => buildInfo(part, polly.getSynthesizeSpeechUrl.bind(polly), opts));

  return generateAll(parts, opts, callAws)
    .then(createManifest)
    .then(manifest => {
      return combine(manifest, opts);
    });
};

exports.getSpinner = () => {
  return spinner;
};

// Chunk text into pieces.
let chunkText = (text, maxCharacterCount) => {
  let parts = textchunk.chunk(text, maxCharacterCount);
  return Promise.resolve(parts);
};

// Parse and chunk XML.
let chunkXml = (xml, maxCharacterCount) => {
  const parser = require('sax').parser(false, {
    lowercase: true,
    normalize: true,
    trim: true,
  });
  const attributeString = attrs => {
    let str = '';
    for (let prop in attrs) {
      if (attrs.hasOwnProperty(prop)) {
        str += ` ${prop}="${attrs[prop]}"`;
      }
    }
    return str;
  };
  return new Promise((resolve, reject) => {
    let err = null;
    let extraTags = '';
    let tags = [];
    let parts = [];
    parser.onerror = e => {
      err = e;
    };
    parser.ontext = text => {
      let chunks = textchunk.chunk(text, maxCharacterCount).map((chunk, index) => {
        if (index === 0) {
          chunk = `${extraTags}${chunk}`;
        }
        for (let i = tags.length - 1; i >= 0; i--) {
          chunk = `<${tags[i].name}${attributeString(tags[i].attributes)}>${chunk}</${tags[i].name}>`;
        }
        return chunk;
      });
      parts.push(...chunks);
      extraTags = '';
    };
    parser.onopentag = tagData => {
      tags.push(tagData);
    };
    parser.onclosetag = tagName => {
      if (tags[tags.length - 1].name === tagName) {
        let attrs = attributeString(tags[tags.length - 1].attributes);
        extraTags += `<${tagName}${attrs}></${tagName}>`;
        tags.pop();
      }
    };
    parser.onend = () => {
      if (err) {
        reject(err);
      } else {
        resolve(parts);
      }
    };
    parser.write(xml).close();
  });
};

// Read in the text from a file.
// If no file is specified, read from stdin.
exports.readText = (inputFilename, proc) => {
  spinner.begin('Reading text');
  return new Promise((resolve, reject) => {
    if (inputFilename) {
      // Read from a file.
      fs.readFile(inputFilename, 'utf8', (err, data) => {
        if (err) { return reject(err); }
        resolve(data);
      });
    } else {
      // Read from stdin.
      let data = '';
      proc.stdin.setEncoding('utf8');
      proc.stdin.on('readable', () => {
        let chunk = proc.stdin.read();
        if (chunk !== null) { data += chunk; }
      });
      proc.stdin.on('end', () => {
        resolve(data);
      });
    }
  }).then(text => {
    spinner.end();
    return text;
  });
};

// Splits a string of text into chunks.
exports.splitText = (text, maxCharacterCount, opts) => {
  opts = opts || {};
  spinner.begin('Splitting text');
  let chunker = opts.type === 'ssml' ? chunkXml : chunkText;
  return chunker(text, maxCharacterCount).then(parts => {
    return parts.map(str => {
      // Compress whitespace.
      return str.replace(/\s+/g, ' ');
    }).map(str => {
      // Trim whitespace from the ends.
      return str.trim();
    });
  }).then(parts => {
    spinner.end();
    return parts;
  });
};

// Expose the internal functions when testing.
if (process.env.JASMINE_CONFIG_PATH) {
  exports.buildInfo = buildInfo;
  exports.callAws = callAws;
  exports.cleanup = cleanup;
  exports.combine = combine;
  exports.combineEncodedAudio = combineEncodedAudio;
  exports.combineRawAudio = combineRawAudio;
  exports.createManifest = createManifest;
  exports.createPolly = createPolly;
  exports.generateAll = generateAll;
}
<commit_msg>test(coverage): Turn off some coverage for now.<commit_after>'use strict';

const async = require('async');
const fs = require('fs-extra');
const got = require('got');
const ora = require('ora');
const path = require('path');
const Polly = require('aws-sdk/clients/polly').Presigner;
const spawn = require('child_process').spawn;
const tempfile = require('tempfile');
const textchunk = require('textchunk');

const fileExtensions = {
  mp3: 'mp3',
  ogg_vorbis: 'ogg', // eslint-disable-line camelcase
  pcm: 'pcm',
};

// Set up the spinner.
let spinner = ora().start();
spinner.begin = text => {
  spinner.text = text;
  spinner.start();
};
spinner.end = () => {
  spinner.succeed();
};

exports.checkUsage = (args, proc) => {
  spinner.stop();
  const minNumArgs = 1;
  const script = path.basename(proc.argv[1]);
  const usageStatement = `Converts a text file to speech using the AWS Polly API.
Usage:
  ${script} [INPUTFILE] OUTPUTFILE [OPTIONS]
Standard:
  INPUTFILE           The text file to convert (reads from stdin if not specified)
  OUTPUTFILE          The filename to save the audio to
Options:
  --help              Displays this info and exits
  --access-key KEY    AWS access key ID
  --ffmpeg BINARY     Path to the ffmpeg binary (defaults to the one in PATH)
  --format FORMAT     Target audio format ("mp3", "ogg_vorbis", or "pcm") (default "mp3")
  --lexicon NAME      Apply a stored pronunciation lexicon. Can be specified multiple times.
  --region REGION     AWS region to send requests to (default "us-east-1")
  --sample-rate RATE  Audio frequency, in hertz.
  --secret-key KEY    AWS secret access key
  --throttle SIZE     Number of simultaneous requests allowed against the AWS API (default 5)
  --type TYPE         Type of input text ("text" or "ssml") (default "text")
  --voice VOICE       Voice to use for the speech (default "Joanna")
`;
  if (args.help) {
    proc.stderr.write(usageStatement);
    proc.exit(0);
  }
  if (args._.length < minNumArgs) {
    proc.stderr.write(usageStatement);
    proc.exit(1);
  }
};

// Creates an object containing all the data.
let buildInfo = (text, urlCreator, opts) => {
  return {
    opts: opts,
    tempfile: tempfile(`.${fileExtensions[opts.format]}`),
    text: text,
    urlcreator: urlCreator,
  };
};

// Calls AWS Polly with the given info.
let callAws = (info, i, callback) => {
  const secsPerMin = 60;
  const minsInHalfHour = 30;
  const halfHour = secsPerMin * minsInHalfHour;

  spinner.text = spinner.text.replace(/\d+\//, `${i}/`);

  let url = info.urlcreator({
    LexiconNames: info.opts.lexicon,
    OutputFormat: info.opts.format,
    SampleRate: info.opts['sample-rate'] ? String(info.opts['sample-rate']) : undefined,
    Text: info.text,
    TextType: info.opts.type,
    VoiceId: info.opts.voice
  }, halfHour);

  let error;
  let outputStream = fs.createWriteStream(info.tempfile);
  outputStream.on('close', () => {
    callback(error);
    if (error) {
      // Get the error message from Amazon.
      /* istanbul ignore next */
      try {
        let response = JSON.parse(fs.readFileSync(info.tempfile, 'utf8'));
        error.message += `: ${response.message}`;
      } catch (err) {}
    }
  });
  got.stream(url).on('error', err => { error = err; }).pipe(outputStream);
};

// Deletes the manifest and its files.
let cleanup = manifestFile => {
  let manifest = fs.readFileSync(manifestFile, 'utf8');
  let regexpState = /^file\s+'(.*)'$/gm;
  let match;
  while ((match = regexpState.exec(manifest)) !== null) {
    fs.removeSync(match[1]);
  }
  fs.removeSync(manifestFile);
};

// Combines MP3 or OGG files into one file.
let combineEncodedAudio = (binary, manifestFile, outputFile) => {
  let args = [
    '-f', 'concat',
    '-safe', '0',
    '-i', manifestFile,
    '-c', 'copy',
    outputFile
  ];
  return new Promise((resolve, reject) => {
    let ffmpeg = spawn(binary, args);
    let stderr = '';
    ffmpeg.stderr.on('data', (data) => {
      stderr += `\n${data}`;
    });
    ffmpeg.on('error', err => {
      reject(new Error('Could not start ffmpeg process'));
    });
    ffmpeg.on('close', code => {
      if (code > 0) {
        spinner.fail();
        return reject(new Error(`ffmpeg returned an error (${code}): ${stderr}`));
      }
      spinner.end();
      resolve();
    });
  });
};

// Concatenates raw PCM audio into one file.
let combineRawAudio = (manifestFile, outputFile) => {
  let manifest = fs.readFileSync(manifestFile, 'utf8');
  let regexpState = /^file\s+'(.*)'$/gm;
  fs.createFileSync(outputFile);
  fs.truncateSync(outputFile);
  let match;
  while ((match = regexpState.exec(manifest)) !== null) {
    let dataBuffer = fs.readFileSync(match[1]);
    fs.appendFileSync(outputFile, dataBuffer);
  }
  return Promise.resolve();
};

// Combines all the parts into one file.
// Resolves with the new filename.
let combine = (manifestFile, opts) => {
  spinner.begin('Combine audio');
  let newFile = tempfile(`.${fileExtensions[opts.format]}`);
  let combiner = opts.format === 'pcm' ?
    combineRawAudio(manifestFile, newFile) :
    combineEncodedAudio(opts.ffmpeg, manifestFile, newFile);
  return combiner.then(() => {
    spinner.begin('Clean up');
    cleanup(manifestFile);
    spinner.end();
    return newFile;
  }).catch(err => {
    cleanup(manifestFile);
    throw err;
  });
};

// Writes down all the temp files for ffmpeg to read in.
// Returns the text filename.
let createManifest = parts => {
  let txtFile = tempfile('.txt');
  let contents = parts.map(info => {
    return `file '${info.tempfile}'`;
  }).join('\n');
  fs.writeFileSync(txtFile, contents, 'utf8');
  return txtFile;
};

// Create an AWS Polly instance.
let createPolly = opts => {
  return new Polly({
    apiVersion: '2016-06-10',
    region: opts.region,
    accessKeyId: opts['access-key'],
    secretAccessKey: opts['secret-key'],
  });
};

// Calls the API for each text part (throttled). Returns a Promise.
let generateAll = (parts, opts, func) => {
  let count = parts.length;
  spinner.begin(`Convert to audio (0/${count})`);
  return (new Promise((resolve, reject) => {
    async.eachOfLimit(
      parts,
      opts.limit,
      func,
      err => {
        if (err) {
          spinner.fail();
          return reject(err);
        }
        spinner.text = spinner.text.replace(/\d+\//, `${count}/`);
        spinner.end();
        resolve(parts);
      }
    );
  }));
};

// Returns a Promise with the temporary audio file.
exports.generateSpeech = (strParts, opts) => {
  // Add in the default options.
  /* istanbul ignore next */
  opts = Object.assign({}, {
    'access-key': opts.accessKey,
    ffmpeg: opts.ffmpeg || 'ffmpeg',
    format: opts.format || 'mp3',
    lexicon: opts.lexicon,
    limit: Number(opts.throttle) || 5, // eslint-disable-line no-magic-numbers
    region: opts.region || 'us-east-1',
    'sample-rate': opts.sampleRate,
    'secret-key': opts.secretKey,
    type: opts.type || 'text',
    voice: opts.voice || 'Joanna'
  }, opts);
  /* istanbul ignore next */
  if (typeof opts.lexicon !== 'undefined' && !Array.isArray(opts.lexicon)) {
    opts.lexicon = [opts.lexicon];
  }

  /* istanbul ignore next */
  let polly = createPolly(opts);

  // Compile the text parts and options together in a packet.
  /* istanbul ignore next */
  let parts = strParts.map(part => buildInfo(part, polly.getSynthesizeSpeechUrl.bind(polly), opts));

  /* istanbul ignore next */
  return generateAll(parts, opts, callAws)
    .then(createManifest)
    .then(manifest => {
      /* istanbul ignore next */
      return combine(manifest, opts);
    });
};

exports.getSpinner = () => {
  return spinner;
};

// Chunk text into pieces.
let chunkText = (text, maxCharacterCount) => {
  let parts = textchunk.chunk(text, maxCharacterCount);
  return Promise.resolve(parts);
};

// Parse and chunk XML.
let chunkXml = (xml, maxCharacterCount) => {
  const parser = require('sax').parser(false, {
    lowercase: true,
    normalize: true,
    trim: true,
  });
  const attributeString = attrs => {
    let str = '';
    for (let prop in attrs) {
      if (attrs.hasOwnProperty(prop)) {
        str += ` ${prop}="${attrs[prop]}"`;
      }
    }
    return str;
  };
  return new Promise((resolve, reject) => {
    let err = null;
    let extraTags = '';
    let tags = [];
    let parts = [];
    parser.onerror = e => {
      /* istanbul ignore next */
      err = e;
    };
    parser.ontext = text => {
      let chunks = textchunk.chunk(text, maxCharacterCount).map((chunk, index) => {
        if (index === 0) {
          chunk = `${extraTags}${chunk}`;
        }
        for (let i = tags.length - 1; i >= 0; i--) {
          chunk = `<${tags[i].name}${attributeString(tags[i].attributes)}>${chunk}</${tags[i].name}>`;
        }
        return chunk;
      });
      parts.push(...chunks);
      extraTags = '';
    };
    parser.onopentag = tagData => {
      tags.push(tagData);
    };
    parser.onclosetag = tagName => {
      if (tags[tags.length - 1].name === tagName) {
        let attrs = attributeString(tags[tags.length - 1].attributes);
        extraTags += `<${tagName}${attrs}></${tagName}>`;
        tags.pop();
      }
    };
    parser.onend = () => {
      if (err) {
        /* istanbul ignore next */
        reject(err);
      } else {
        resolve(parts);
      }
    };
    parser.write(xml).close();
  });
};

// Read in the text from a file.
// If no file is specified, read from stdin.
exports.readText = (inputFilename, proc) => {
  spinner.begin('Reading text');
  return new Promise((resolve, reject) => {
    if (inputFilename) {
      // Read from a file.
      fs.readFile(inputFilename, 'utf8', (err, data) => {
        if (err) { return reject(err); }
        resolve(data);
      });
    } else {
      // Read from stdin.
      let data = '';
      proc.stdin.setEncoding('utf8');
      proc.stdin.on('readable', () => {
        let chunk = proc.stdin.read();
        if (chunk !== null) { data += chunk; }
      });
      proc.stdin.on('end', () => {
        resolve(data);
      });
    }
  }).then(text => {
    spinner.end();
    return text;
  });
};

// Splits a string of text into chunks.
exports.splitText = (text, maxCharacterCount, opts) => {
  opts = opts || {};
  spinner.begin('Splitting text');
  let chunker = opts.type === 'ssml' ? chunkXml : chunkText;
  return chunker(text, maxCharacterCount).then(parts => {
    return parts.map(str => {
      // Compress whitespace.
      return str.replace(/\s+/g, ' ');
    }).map(str => {
      // Trim whitespace from the ends.
      return str.trim();
    });
  }).then(parts => {
    spinner.end();
    return parts;
  });
};

// Expose the internal functions when testing.
if (process.env.JASMINE_CONFIG_PATH) {
  exports.buildInfo = buildInfo;
  exports.callAws = callAws;
  exports.cleanup = cleanup;
  exports.combine = combine;
  exports.combineEncodedAudio = combineEncodedAudio;
  exports.combineRawAudio = combineRawAudio;
  exports.createManifest = createManifest;
  exports.createPolly = createPolly;
  exports.generateAll = generateAll;
}
<|endoftext|>