<commit_before>/*global JSONPath, getJSON, exports*/
/*jslint vars:true, todo:true*/
(function (undef) {'use strict';

var jsonpath = JSONPath; // Satisfy JSLint

/**
* @private
* @static
*/
function _triggerEqualPriorityError (config) {
    if (config.errorOnEqualPriority) {
        throw "You have configured JSONPathTransformer to throw errors on finding templates of equal priority and these have been found.";
    }
}

/**
* @private
* @static
*/
function _makeAbsolute (select) {
    // See todo in JSONPath to avoid need for '$' (but may still need to add ".")
    return select[0] !== '$' ? '$' + (select[0] === '[' ? '$' : '$.') + select : select;
}

function _getPriorityBySpecificity (path) {
    if (typeof path === 'string') {
        path = JSONPath.toPathArray(path);
    }
    // Todo: Let's also, unlike XSLT, give options for higher priority to absolute fixed paths over recursive descent and priority to longer paths and lower to wildcard terminal points

    var terminal = path.slice(-1);
    if (terminal.match(/\^|\*|@.*?\(\)$/)) { // *, ^, @string() (comparable to XSLT's *, @*, and node tests)
        return -0.5;
    }
    if (terminal.match(/^(\.+|\[.*?\])$/))) { // ., .., [], [()], [(?)] (comparable to XSLT's /, //, or [])
        return 0.5;
    }
    return 0; // single name (i.e., $..someName or someName if allowing such relative paths)
}

// Todo: utilize
function JSONTransformerEvaluator (config, templates) {
    this._config = config;
    this._templates = templates;
}

JSONTransformerEvaluator.prototype.applyTemplates = function (select, mode) {
    // Todo: adapt this code to only find (and apply) templates per context
    var that = this;
    if (select && typeof select === 'object') {
        mode = select.mode;
        select = select.select;
    }
    if (!this.hasOwnProperty('_contextNode')) {
        this._contextNode = this.config.data;
        this._parent = this.config.parent || null;
        this._parentProperty = this.config.parentProperty || null;
        select = select || '$';
    }
    else {
        select = select || '*';
    }
    select = _makeAbsolute(select);
    var results;
    var modeMatchedTemplates = this.templates.filter(function (template) {
        return ((mode && mode === template.mode) && (!mode && !template.mode));
    });
    var found = jsonpath({path: select, json: this._contextNode, wrap: false, returnType: 'all', callback: function (preferredOutput) {
        /*
        // Utilize
        preferredOutput.value;
        that._contextNode;
        that._parent;
        that._parentProperty;
        */

        var pathMatchedTemplates = modeMatchedTemplates.filter(function (template) {
            return jsonpath({path: _makeAbsolute(template.path), json: that._contextNode, resultType: 'path', wrap: true}).includes(preferredOutput.path);
        });

        if (!pathMatchedTemplates) {
            // Todo: deal with any default templates (by default, should have all defined), including the object and array ones containing this.applyTemplates('*', mode); and this.getDefaultPriority(preferredOutput.path);

            return;
        }

        var matched = pathMatchedTemplates.sort(function (a, b) {

            // Todo: deal with priority, specificity, order

            var aPriority = typeof a.priority === 'number' ? a.priority : _getPriorityBySpecificity(a.path);
            var bPriority = typeof b.priority === 'number' ? b.priority : _getPriorityBySpecificity(a.path);

            if (aPriority === bPriority) {
                _triggerEqualPriorityError(this._config);
            }

            return (aPriority > bPriority) ? -1 : 1; // We want equal conditions to go in favor of the later (b)
        }).some(function (templateObj) {
            var path = templateObj.path;
            var json = that._contextNode;
            var values = jsonpath({json: json, path: path, resultType: 'value', wrap: false, callback: function (parent, property, value, path) {

            }});
            if (values) {
                templateObj.template(values, path, json);
            }
            return true;
        });

        if (!matched) { // Should not get here with default template rules in place
            throw "No template rules matched";
        }

        results.add(matched.shift());
    }});
    if (!found) {

    }
    return results;
};
JSONTransformerEvaluator.prototype.callTemplate = function (name, withParam) {
    var value;
    if (name && typeof name === 'object') {
        withParam = name.withParam;
        name = name.name;
    }
    var template = this.templates.find(function (template) {
        value = withParam.value; // Todo: Support withParam.select (with reference to current context)
        return template.name === name;
    });
    if (!template) {
        throw "Template, " + name + ", cannot be called as it was not found.";
    }
    return template(value); // Todo: provide context
};
JSONTransformerEvaluator.prototype.forEach = function () {

};
JSONTransformerEvaluator.prototype.valueOf = function () {

};

/**
* @param {boolean} config.errorOnEqualPriority
*/
function JSONPathTransformer (config) {
    if (!(this instanceof JSONPathTransformer)) {
        return new JSONPathTransformer(config);
    }
    var that = this;
    var map = {};
    this.config = config;
    this.rootTemplates = [];
    this.templates.forEach(function (template, i, templates) {
        if (template.name && map[template.name]) {
            throw "Templates must all have different names.";
        }
        map[template.name] = true;
        if (template.path === '$') {
            that.rootTemplates.concat(templates.splice(i, 1));
        }
    });
    map = null;
    this.transform();
}

JSONPathTransformer.prototype.defaultRootTemplate = function () {
    return this.applyTemplates();
};

JSONPathTransformer.prototype.transform = function () {
    var jte = new JSONTransformerEvaluator(this.config, this.templates);
    var len = this.rootTemplates.length;
    if (!len) {
        return this.defaultRootTemplate.call(jte);
    }
    if (len === 1) {
        _triggerEqualPriorityError(this.config);
    }
    var templateObj = this.rootTemplates.pop();
    var path = templateObj.path;
    var json = this.config.data;
    return templateObj.template.call(jte, json, path, json);
};

JSONPathTransformer.DefaultTemplateRules = [
    // Todo: Apply default template rules
    /*
    / | *                                : apply-templates          (this.applyTemplates())
    / | * mode                           : apply-templates mode     (this.config.mode)
    text() | @*                          : value-of select="."      (this.valueOf({select: '.'}) or this.valueOf('.'))
    processing-instruction() | comment() : (nothing); optionally allow JSON equivalents or Jamilih
    (an XSLT processor is not to copy any part of the namespace node to the output); optionally allow JSON equivalents or Jamilih
    */
];

/**
* For templates/queries, one may choose among config.query, config.template, or config.templates, but one must be present and of valid type. For the source json, one must use either a valid config.ajaxData or config.data parameter.
* @param {function} config.success A callback supplied with a single argument that is the result of this instance's transform() method.
* @param {array} [config.templates] An array of template objects
* @param {object|function} [config.template] A function assumed to be a root template or a single, complete template object
* @param {function} [config.query] A function assumed to be a root template
* @param {object} [config.data] A JSON object
* @param {string} [config.ajaxData] URL of a JSON file to retrieve for evaluation
* @param {boolean} [config.errorOnEqualPriority=false]
* @param {boolean} [config.autostart=true] Whether to begin transform() immediately.
* @param {string} [config.mode=''] The mode in which to begin the transform.
* @param {function} [config.engine=JSONPathTransformer] Will be based the same config as passed to this instance. Defaults to a transforming function based on JSONPath and with its own set of priorities for processing templates.
* @returns {JTLT} A JTLT instance object
*/
function JTLT (config) {
    if (!(this instanceof JTLT)) {
        return new JTLT(config);
    }

    this.setDefaults(config);
    var that = this;
    if (this.config.ajaxData) {
        getJSON(this.config.ajaxData, function (json) {
            that.config.data = json;
            that._autoStart();
        });
        return this;
    }
    this._autoStart();
}
JTLT.prototype._autoStart = function () {
    if (this.config.autostart !== false || this.ready) {
        this.config.success(this.transform());
    }
};
JTLT.prototype.setDefaults = function (config) {
    var query = config.query || (typeof config.templates === 'function' ? config.templates : (typeof config.template === 'function' ? config.template : null));
    this.templates = query ? [{name: 'root', path: '$', template: query}] : (config.templates || [config.template]);
    this.config.errorOnEqualPriority = config.errorOnEqualPriority || false;
    this.config = config || {};
    this.config.engine = this.config.engine || JSONPathTransformer;
    return this;
};
/**
* @returns {any}
*/
JTLT.prototype.transform = function () {
    if (this.config.data === undef) {
        if (this.config.ajaxData) {
            this.ready = true;
            return;
        }
        throw "You must supply a 'data' or 'ajaxData' property";
    }
    if (typeof this.config.success !== 'function') {
        throw "You must supply a 'success' callback";
    }

    return this.config.engine({templates: this.templates, json: this.config.data});
};

if (typeof exports !== 'undefined') {
    exports.JTLT = JTLT;
    exports.JSONPathTransformer = JSONPathTransformer;
}
else {
    window.JTLT = JTLT;
    window.JSONPathTransformer = JSONPathTransformer;
}

}());<commit_msg>Clarifying comments; optimize and improve regex<commit_after>/*global JSONPath, getJSON, exports*/
/*jslint vars:true, todo:true*/
(function (undef) {'use strict';

var jsonpath = JSONPath; // Satisfy JSLint

/**
* @private
* @static
*/
function _triggerEqualPriorityError (config) {
    if (config.errorOnEqualPriority) {
        throw "You have configured JSONPathTransformer to throw errors on finding templates of equal priority and these have been found.";
    }
}

/**
* @private
* @static
*/
function _makeAbsolute (select) {
    // See todo in JSONPath to avoid need for '$' (but may still need to add ".")
    return select[0] !== '$' ? '$' + (select[0] === '[' ? '$' : '$.') + select : select;
}

function _getPriorityBySpecificity (path) {
    if (typeof path === 'string') {
        path = JSONPath.toPathArray(path);
    }
    // Todo: Let's also, unlike XSLT, give options for higher priority to absolute fixed paths over recursive descent and priority to longer paths and lower to wildcard terminal points

    var terminal = path.slice(-1);
    if (terminal.match(/^(?:\^|\*|@.*?\(\))$/)) { // *, ^, @string() (comparable to XSLT's *, @*, and node tests)
        return -0.5;
    }
    if (terminal.match(/^(?:\.+|\[.*?\])$/))) { // ., .., [], [()], [(?)] (comparable to XSLT's /, //, or [])
        return 0.5;
    }
    return 0; // single name (i.e., $..someName or someName if allowing such relative paths) (comparable to XSLT's identifying a particular element or attribute name)
}

// Todo: utilize
function JSONTransformerEvaluator (config, templates) {
    this._config = config;
    this._templates = templates;
}

JSONTransformerEvaluator.prototype.applyTemplates = function (select, mode) {
    // Todo: adapt this code to only find (and apply) templates per context
    var that = this;
    if (select && typeof select === 'object') {
        mode = select.mode;
        select = select.select;
    }
    if (!this.hasOwnProperty('_contextNode')) {
        this._contextNode = this.config.data;
        this._parent = this.config.parent || null;
        this._parentProperty = this.config.parentProperty || null;
        select = select || '$';
    }
    else {
        select = select || '*';
    }
    select = _makeAbsolute(select);
    var results;
    var modeMatchedTemplates = this.templates.filter(function (template) {
        return ((mode && mode === template.mode) && (!mode && !template.mode));
    });
    var found = jsonpath({path: select, json: this._contextNode, wrap: false, returnType: 'all', callback: function (preferredOutput) {
        /*
        // Utilize
        preferredOutput.value;
        that._contextNode;
        that._parent;
        that._parentProperty;
        */

        var pathMatchedTemplates = modeMatchedTemplates.filter(function (template) {
            return jsonpath({path: _makeAbsolute(template.path), json: that._contextNode, resultType: 'path', wrap: true}).includes(preferredOutput.path);
        });

        if (!pathMatchedTemplates) {
            // Todo: deal with any default templates (by default, should have all defined), including the object and array ones containing this.applyTemplates('*', mode); and this.getDefaultPriority(preferredOutput.path);

            return;
        }

        var matched = pathMatchedTemplates.sort(function (a, b) {

            // Todo: deal with priority, specificity, order

            var aPriority = typeof a.priority === 'number' ? a.priority : _getPriorityBySpecificity(a.path);
            var bPriority = typeof b.priority === 'number' ? b.priority : _getPriorityBySpecificity(a.path);

            if (aPriority === bPriority) {
                _triggerEqualPriorityError(this._config);
            }

            return (aPriority > bPriority) ? -1 : 1; // We want equal conditions to go in favor of the later (b)
        }).some(function (templateObj) {
            var path = templateObj.path;
            var json = that._contextNode;
            var values = jsonpath({json: json, path: path, resultType: 'value', wrap: false, callback: function (parent, property, value, path) {

            }});
            if (values) {
                templateObj.template(values, path, json);
            }
            return true;
        });

        if (!matched) { // Should not get here with default template rules in place
            throw "No template rules matched";
        }

        results.add(matched.shift());
    }});
    if (!found) {

    }
    return results;
};
JSONTransformerEvaluator.prototype.callTemplate = function (name, withParam) {
    var value;
    if (name && typeof name === 'object') {
        withParam = name.withParam;
        name = name.name;
    }
    var template = this.templates.find(function (template) {
        value = withParam.value; // Todo: Support withParam.select (with reference to current context)
        return template.name === name;
    });
    if (!template) {
        throw "Template, " + name + ", cannot be called as it was not found.";
    }
    return template(value); // Todo: provide context
};
JSONTransformerEvaluator.prototype.forEach = function () {

};
JSONTransformerEvaluator.prototype.valueOf = function () {

};

/**
* @param {boolean} config.errorOnEqualPriority
*/
function JSONPathTransformer (config) {
    if (!(this instanceof JSONPathTransformer)) {
        return new JSONPathTransformer(config);
    }
    var that = this;
    var map = {};
    this.config = config;
    this.rootTemplates = [];
    this.templates.forEach(function (template, i, templates) {
        if (template.name && map[template.name]) {
            throw "Templates must all have different names.";
        }
        map[template.name] = true;
        if (template.path === '$') {
            that.rootTemplates.concat(templates.splice(i, 1));
        }
    });
    map = null;
    this.transform();
}

JSONPathTransformer.prototype.defaultRootTemplate = function () {
    return this.applyTemplates();
};

JSONPathTransformer.prototype.transform = function () {
    var jte = new JSONTransformerEvaluator(this.config, this.templates);
    var len = this.rootTemplates.length;
    if (!len) {
        return this.defaultRootTemplate.call(jte);
    }
    if (len === 1) {
        _triggerEqualPriorityError(this.config);
    }
    var templateObj = this.rootTemplates.pop();
    var path = templateObj.path;
    var json = this.config.data;
    return templateObj.template.call(jte, json, path, json);
};

JSONPathTransformer.DefaultTemplateRules = [
    // Todo: Apply default template rules
    /*
    / | *                                : apply-templates          (this.applyTemplates())
    / | * mode                           : apply-templates mode     (this.config.mode)
    text() | @*                          : value-of select="."      (this.valueOf({select: '.'}) or this.valueOf('.'))
    processing-instruction() | comment() : (nothing); optionally allow JSON equivalents or Jamilih
    (an XSLT processor is not to copy any part of the namespace node to the output); optionally allow JSON equivalents or Jamilih
    */
];

/**
* For templates/queries, one may choose among config.query, config.template, or config.templates, but one must be present and of valid type. For the source json, one must use either a valid config.ajaxData or config.data parameter.
* @param {function} config.success A callback supplied with a single argument that is the result of this instance's transform() method.
* @param {array} [config.templates] An array of template objects
* @param {object|function} [config.template] A function assumed to be a root template or a single, complete template object
* @param {function} [config.query] A function assumed to be a root template
* @param {object} [config.data] A JSON object
* @param {string} [config.ajaxData] URL of a JSON file to retrieve for evaluation
* @param {boolean} [config.errorOnEqualPriority=false]
* @param {boolean} [config.autostart=true] Whether to begin transform() immediately.
* @param {string} [config.mode=''] The mode in which to begin the transform.
* @param {function} [config.engine=JSONPathTransformer] Will be based the same config as passed to this instance. Defaults to a transforming function based on JSONPath and with its own set of priorities for processing templates.
* @returns {JTLT} A JTLT instance object
*/
function JTLT (config) {
    if (!(this instanceof JTLT)) {
        return new JTLT(config);
    }

    this.setDefaults(config);
    var that = this;
    if (this.config.ajaxData) {
        getJSON(this.config.ajaxData, function (json) {
            that.config.data = json;
            that._autoStart();
        });
        return this;
    }
    this._autoStart();
}
JTLT.prototype._autoStart = function () {
    if (this.config.autostart !== false || this.ready) {
        this.config.success(this.transform());
    }
};
JTLT.prototype.setDefaults = function (config) {
    var query = config.query || (typeof config.templates === 'function' ? config.templates : (typeof config.template === 'function' ? config.template : null));
    this.templates = query ? [{name: 'root', path: '$', template: query}] : (config.templates || [config.template]);
    this.config.errorOnEqualPriority = config.errorOnEqualPriority || false;
    this.config = config || {};
    this.config.engine = this.config.engine || JSONPathTransformer;
    return this;
};
/**
* @returns {any}
*/
JTLT.prototype.transform = function () {
    if (this.config.data === undef) {
        if (this.config.ajaxData) {
            this.ready = true;
            return;
        }
        throw "You must supply a 'data' or 'ajaxData' property";
    }
    if (typeof this.config.success !== 'function') {
        throw "You must supply a 'success' callback";
    }

    return this.config.engine({templates: this.templates, json: this.config.data});
};

if (typeof exports !== 'undefined') {
    exports.JTLT = JTLT;
    exports.JSONPathTransformer = JSONPathTransformer;
}
else {
    window.JTLT = JTLT;
    window.JSONPathTransformer = JSONPathTransformer;
}

}());<|endoftext|>


<commit_before>var bitwise = require('./bitwise.js')

// Use Hilbert curve point generation to map n-dimensional data to 1D space and vice-versa.

function Point(x, y, z) { // :: Int -> Int -> Int -> Point
    this.rotations = {
        x: 0,
        y: 0,
        z: 0
    }

    this.init = function (x, y, z) {
        this.x = Math.round(x) || 0
        this.y = Math.round(y) || 0
        if (z != null) {
            this.z = Math.round(z) || 0
            this.d = 3
            this.n = 4 * this.z + 2 * this.y  + this.x
        } else {
            this.z = null
            this.d = 2
        }
    }
    if (x instanceof Array) {
        this.init(x[0], x[1], x[2])
    } else {
        this.init(x, y, z)
    }
}

Point.prototype.rotate2d = function (n, xbit, ybit) { // : Int -> Int -> Int -> Point
    return new Point(rotate2d(n, this.x, this.y, xbit, ybit))
}

Point.prototype.toArray = function () { // :: -> [Int, Int]
        if (this.d == 3) { return [this.x, this.y, this.z] }
        return [this.x, this.y]
}

// Old 2D conversions.

// Accepts the height or width of a square/graph, and the coordinates to
// convert.
function convert2dPointToDistance (p, height) { // :: Int -> Int -> Int -> Int
    var xbit, ybit, level, d = 0
    var forHeight = p.x > p.y ? p.x : p.y

    // needs some tests to make sure height is compatible
    // What keeps the user from putting 54 down as the height
    while (forHeight >= height) {
            height *=2
    }
    // For each Hilbert level, we want to add an amount to
    // `d` based on which region we are in
    for (level = height / 2; level > 0; level = Math.floor(level / 2)) {
        // Determine what region we're in
        xbit = (p.x & level) > 0
        ybit = (p.y & level) > 0
        // increase distance based on region
        d += level * level * ((3 * xbit) ^ ybit)
        // rotate so that we'll be in sync with the next
        // region.
        p = p.rotate2d(level, xbit, ybit)
    }

    return d
}

// Accepts height or width of a square/graph and distance
function convertDistanceTo2dPoint (distance, height) { // :: Int -> Int -> [Int, Int]
    distance = Math.floor(distance)
    var xbit, ybit, level, p = new Point(0, 0)

    if (height <= Math.sqrt(distance))  {
        height = 2
        while (height <= Math.sqrt(distance)) {
            height *=2
        }
    }

    for (level = 1; level < height; level *= 2) {
        xbit = 1 & (distance / 2)
        ybit = 1 & (distance ^ xbit)

        p = p.rotate2d(level, xbit, ybit)
        p.x += level * xbit
        p.y += level * ybit
        distance = Math.floor(distance / 4)
    }

    return p.toArray()
}

// Rotate the coordinate plane and (x,y)
function rotate2d (n, x, y, xbit, ybit) { // :: Int -> Int -> Int -> Int -> Int -> [Int, Int]
    if (ybit == 0  ) {
        if (xbit == 1) {
            x = n - 1 - x
            y = n - 1 - y
        }

        var temp = x
        x = y
        y = temp
    }

    return [x, y]
}

// Helpers for new Hilbert index.

function grayCode (sequence) { // :: Int -> Int
    return sequence ^ (sequence >> 1)
}

// returns a non-negative int 'inverse' such that graycode(inverse) = g
function grayInverse (g) { // : Int -> Int
    var m = precision(g), inverse = g, j = 1
    while (j < m) {
        inverse = inverse ^ (g >> j)
        j++
    }
    return inverse
}

function grayTransform (entry, direction, x, dim) { // :: Int -> Int -> Int -> Int
    return bitwise.rotateRight((x ^ entry), dim, 0, direction + 1)
}

function grayInverseTransform (entry, direction, x, dim) { // :: Int -> Int -> Int -> Int
    return grayTransform(bitwise.rotateRight(entry, dim, 0, direction + 1), dim - direction - 1, x, dim)
}

// Returns the number of bits required to store an integer
function precision (n) { // :: Int > Int
    var ret = 0
    while (n > 0) {
        ret++
        n = n >> 1
    }
    return ret
}

function entrySequence (i) { // :: Int -> Int
    if (i) {
        return grayCode(2 * Math.floor((i-1) / 2))
    }
    return 0
}

function directionSequence(i, dim) { // :: Int -> Int -> Int
    if (i == 0) return 0
    if (i % 2 == 0) return trailingSetBits(i - 1) % dim
    return trailingSetBits(i) % dim
}

function trailingSetBits (i) { // :: Int -> Int
    var ones = ~i & (i + 1)
    return Math.log(ones) / Math.log(2)
}

// curve precision
function precise(index, dim) { // :: Int -> Int -> Int
    var n = Math.pow(2, dim)
    var bits = 32

    while(bits % dim != 0) --bits

    for (var i=1; i < bits; i++) {
        if (index < Math.pow(n, i)) return i+1
    }
}

function nthRoot(num, nArg, precArg) { // : Int -> Int -> Int -> Int
  var n = nArg || 2;
  var prec = precArg || 12;

  var x = 1; // Initial guess.
  for (var i=0; i<prec; i++) {
    x = 1/n * ((n-1)*x + (num / Math.pow(x, n-1)));
  }

  return x;
}

//N-dimensional conversions.

function hilbertIndex(point, options) { // :: [Int, Int, ..] -> {} -> Int
    options = options || {}
    var index = 0, code,
        entry = options.entry || 0,
        direction = options.direction || 0,
        i = options.precision || precision(Math.max.apply(null, point)) - 1,
        dim = point.length

    while (i >= 0) {

        var bits = 0
        var mask = 1 << dim - 1

        for (var k = 0; k < point.length; k++) {
            if (point[dim - (k+1)] & (1 << i)) {
                bits |= mask
            }
            mask >>>= 1
        }

        bits = grayTransform(entry, direction, bits, dim)
        code = grayInverse(bits)

        entry = entry ^ bitwise.rotateLeft(entrySequence(code), dim, 0, direction + 1)
        direction = (direction + directionSequence(code, dim) + 1) % dim
        index = (index << dim) | code

        i--
    }

    return index
}

function hilbertIndexInverse(dim, index, options) { // :: Int -> Int -> [Int, Int, ..]
    options = options || {}
    var entry = options.entry || 0,
        direction = options.direction || 0,
        m = precise(index, dim),
        p = Array.apply(null, new Array(dim)).map(Number.prototype.valueOf, 0)

    for (var i = m - 1; i >= 0; i--) {
        var mask = 1 << (i * dim), bits = 0, code

        for (var k = dim - 1; k >= 0; k--) {
            if (index & (mask << k)) {
                bits |= (1 << k)
            }
        }

        code = grayInverseTransform(entry, direction, grayCode(bits), dim)
        for (var k = 0; k < dim; k++) {
            if (code & (1 << k)) {
                p[k] |= (1 << i)
            }
        }

        entry = entry ^ bitwise.rotateLeft(entrySequence(bits), dim, 0, direction + 1)
        direction = (direction + directionSequence(bits, dim) + 1) % dim
    }
    return p
}

exports.hilbert = hilbertIndex
exports.hilbertInverse = hilbertIndexInverse
<commit_msg>Renaming things.<commit_after>var bitwise = require('./bitwise.js')

// Use Hilbert curve point generation to map n-dimensional data to 1D space and vice-versa.

function Point(x, y, z) { // :: Int -> Int -> Int -> Point
    this.rotations = {
        x: 0,
        y: 0,
        z: 0
    }

    this.init = function (x, y, z) {
        this.x = Math.round(x) || 0
        this.y = Math.round(y) || 0
        if (z != null) {
            this.z = Math.round(z) || 0
            this.d = 3
            this.n = 4 * this.z + 2 * this.y  + this.x
        } else {
            this.z = null
            this.d = 2
        }
    }
    if (x instanceof Array) {
        this.init(x[0], x[1], x[2])
    } else {
        this.init(x, y, z)
    }
}

Point.prototype.rotate2d = function (n, xbit, ybit) { // : Int -> Int -> Int -> Point
    return new Point(rotate2d(n, this.x, this.y, xbit, ybit))
}

Point.prototype.toArray = function () { // :: -> [Int, Int]
        if (this.d == 3) { return [this.x, this.y, this.z] }
        return [this.x, this.y]
}

// Old 2D conversions.

// Accepts the height or width of a square/graph, and the coordinates to
// convert.
function convert2dPointToDistance (p, height) { // :: Int -> Int -> Int -> Int
    var xbit, ybit, level, d = 0
    var forHeight = p.x > p.y ? p.x : p.y

    // needs some tests to make sure height is compatible
    // What keeps the user from putting 54 down as the height
    while (forHeight >= height) {
            height *=2
    }
    // For each Hilbert level, we want to add an amount to
    // `d` based on which region we are in
    for (level = height / 2; level > 0; level = Math.floor(level / 2)) {
        // Determine what region we're in
        xbit = (p.x & level) > 0
        ybit = (p.y & level) > 0
        // increase distance based on region
        d += level * level * ((3 * xbit) ^ ybit)
        // rotate so that we'll be in sync with the next
        // region.
        p = p.rotate2d(level, xbit, ybit)
    }

    return d
}

// Accepts height or width of a square/graph and distance
function convertDistanceTo2dPoint (distance, height) { // :: Int -> Int -> [Int, Int]
    distance = Math.floor(distance)
    var xbit, ybit, level, p = new Point(0, 0)

    if (height <= Math.sqrt(distance))  {
        height = 2
        while (height <= Math.sqrt(distance)) {
            height *=2
        }
    }

    for (level = 1; level < height; level *= 2) {
        xbit = 1 & (distance / 2)
        ybit = 1 & (distance ^ xbit)

        p = p.rotate2d(level, xbit, ybit)
        p.x += level * xbit
        p.y += level * ybit
        distance = Math.floor(distance / 4)
    }

    return p.toArray()
}

// Rotate the coordinate plane and (x,y)
function rotate2d (n, x, y, xbit, ybit) { // :: Int -> Int -> Int -> Int -> Int -> [Int, Int]
    if (ybit == 0  ) {
        if (xbit == 1) {
            x = n - 1 - x
            y = n - 1 - y
        }

        var temp = x
        x = y
        y = temp
    }

    return [x, y]
}

// Helpers for new Hilbert index.

function grayCode (sequence) { // :: Int -> Int
    return sequence ^ (sequence >> 1)
}

// returns a non-negative int 'inverse' such that graycode(inverse) = g
function grayInverse (g) { // : Int -> Int
    var m = precision(g), inverse = g, j = 1
    while (j < m) {
        inverse = inverse ^ (g >> j)
        j++
    }
    return inverse
}

function grayTransform (entry, direction, x, dim) { // :: Int -> Int -> Int -> Int
    return bitwise.rotateRight((x ^ entry), dim, 0, direction + 1)
}

function grayInverseTransform (entry, direction, x, dim) { // :: Int -> Int -> Int -> Int
    return grayTransform(bitwise.rotateRight(entry, dim, 0, direction + 1), dim - direction - 1, x, dim)
}

// Returns the number of bits required to store an integer
function bitPrecision (n) { // :: Int > Int
    var ret = 0
    while (n > 0) {
        ret++
        n = n >> 1
    }
    return ret
}

function entrySequence (i) { // :: Int -> Int
    if (i) {
        return grayCode(2 * Math.floor((i-1) / 2))
    }
    return 0
}

function directionSequence(i, dim) { // :: Int -> Int -> Int
    if (i == 0) return 0
    if (i % 2 == 0) return trailingSetBits(i - 1) % dim
    return trailingSetBits(i) % dim
}

function trailingSetBits (i) { // :: Int -> Int
    var ones = ~i & (i + 1)
    return Math.log(ones) / Math.log(2)
}

// curve precision
function curvePrecision(index, dim) { // :: Int -> Int -> Int
    var n = Math.pow(2, dim)
    var bits = 32

    while(bits % dim != 0) --bits

    for (var i=1; i < bits; i++) {
        if (index < Math.pow(n, i)) return i+1
    }
}

function nthRoot(num, nArg, precArg) { // : Int -> Int -> Int -> Int
  var n = nArg || 2;
  var prec = precArg || 12;

  var x = 1; // Initial guess.
  for (var i=0; i<prec; i++) {
    x = 1/n * ((n-1)*x + (num / Math.pow(x, n-1)));
  }

  return x;
}

//N-dimensional conversions.

function hilbertIndex(point, options) { // :: [Int, Int, ..] -> {} -> Int
    options = options || {}
    var index = 0, code,
        entry = options.entry || 0,
        direction = options.direction || 0,
        i = options.bitPrecision || bitPrecision(Math.max.apply(null, point)) - 1,
        dim = point.length

    while (i >= 0) {

        var bits = 0
        var mask = 1 << dim - 1

        for (var k = 0; k < point.length; k++) {
            if (point[dim - (k+1)] & (1 << i)) {
                bits |= mask
            }
            mask >>>= 1
        }

        bits = grayTransform(entry, direction, bits, dim)
        code = grayInverse(bits)

        entry = entry ^ bitwise.rotateLeft(entrySequence(code), dim, 0, direction + 1)
        direction = (direction + directionSequence(code, dim) + 1) % dim
        index = (index << dim) | code

        i--
    }

    return index
}

function hilbertIndexInverse(dim, index, options) { // :: Int -> Int -> [Int, Int, ..]
    options = options || {}
    var entry = options.entry || 0,
        direction = options.direction || 0,
        m = curvePrecision(index, dim),
        p = Array.apply(null, new Array(dim)).map(Number.prototype.valueOf, 0)

    for (var i = m - 1; i >= 0; i--) {
        var mask = 1 << (i * dim), bits = 0, code

        for (var k = dim - 1; k >= 0; k--) {
            if (index & (mask << k)) {
                bits |= (1 << k)
            }
        }

        code = grayInverseTransform(entry, direction, grayCode(bits), dim)
        for (var k = 0; k < dim; k++) {
            if (code & (1 << k)) {
                p[k] |= (1 << i)
            }
        }

        entry = entry ^ bitwise.rotateLeft(entrySequence(bits), dim, 0, direction + 1)
        direction = (direction + directionSequence(bits, dim) + 1) % dim
    }
    return p
}

exports.hilbert = hilbertIndex
exports.hilbertInverse = hilbertIndexInverse
<|endoftext|>


<commit_before>/*eslint no-console: "off"*/

const mqttWildcard = require("mqtt-wildcard")
const _ = require("lodash")
const AWS = require("aws-sdk")
const request = require("request-promise-native")
const uuid = require("uuid/v4")
const Date = require("sugar-date").Date

const {AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY, AWS_IOT_ENDPOINT_HOST, AWS_REGION, CHRIS_TELEGRAM_ID, HANNAH_TELEGRAM_ID, GROUP_TELEGRAM_ID} = process.env
const {rulesAdd, eventHandler, pickleGherkin} = require("./rules")
const fs = require("fs")
const gherkin = fs.readdirSync("features").map(file => fs.readFileSync(`features/${file}`).toString()).join("\n").replace(/Feature:/ig, "#Feature:").substr(1)

const AWSMqtt = require("aws-mqtt-client").default

const awsMqttClient = new AWSMqtt({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  endpointAddress: AWS_IOT_ENDPOINT_HOST,
  region: AWS_REGION,
  logger: console
})

const iotdata = new AWS.IotData({
  endpoint: AWS_IOT_ENDPOINT_HOST,
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: AWS_REGION,
  logger: console

})

const rekognition = new AWS.Rekognition({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: "eu-west-1"
})

const s3 = new AWS.S3({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: AWS_REGION,
})

const awsTopics = [
  "owntracks/+/+/event",
  "$aws/things/alarm_status/shadow/update/documents",
  "$aws/things/zwave_f2e55e6c_10/shadow/update/documents",
  `notify/out/${CHRIS_TELEGRAM_ID}`,
  `notify/out/${HANNAH_TELEGRAM_ID}`
]

awsMqttClient.on("connect", () => awsMqttClient.subscribe(awsTopics,
  {qos: 1},
  (err, granted) => console.log("aws", err, granted)
))

const get_alarm_state = () => iotdata.getThingShadow({thingName: "alarm_status"}).promise()
  .then(thing => JSON.parse(thing.payload).state.reported.state)

const get_alarm_ready_status = () => iotdata.getThingShadow({thingName: "alarm_status"}).promise()
  .then(thing => JSON.parse(thing.payload).state.reported.ready_status)

const set_alarm_state = state => iotdata.updateThingShadow({
  thingName: "alarm_status",
  payload: JSON.stringify({state: {desired: {state: state}}})
}, (err, data) => console.log(err, data))

awsMqttClient.on("message", function (topic, message) {
  message = message_parser(message)

  // react to chatbot commands
  if ((t = mqttWildcard(topic, "notify/out/+")) && t !== null) {
    // send acknowledgement back to user

    message = message.toLowerCase()
    console.log(`Telegram user ${t[0]} just sent:"${message}"`)

    if (message === messages.unlock_door.toLowerCase())
      iotdata.updateThingShadow({
        thingName: "zwave_f2e55e6c_4",
        payload: JSON.stringify({state: {desired: {user: {Locked: 0}}}})
      }, (err, data) => console.log(err, data))

    if (message === messages.arm_alarm_home.toLowerCase()) {
      reply_with_alarm_status(t[0].toString())
      set_alarm_state("arm_home")
    }

    if (message === messages.arm_alarm_away.toLowerCase()) {
      reply_with_alarm_status(t[0].toString())
      set_alarm_state("arm_away")
    }

    if (message === messages.disarm_alarm.toLowerCase())
      set_alarm_state("disarm")

    if (message.startsWith("say")) {
      let split_message = /say\s(\w+)(.*)/gi.exec(message)
      say_helper(split_message[1], split_message[2])
    }

    if (message === messages.start.toLowerCase())
      notify_helper(t[0], `You can do these things`, messages)

    if (message === messages.cam_driveway.toLowerCase())
      send_camera_to("camera_external_driveway", t[0])

    if (message === messages.cam_garden.toLowerCase())
      send_camera_to("camera_external_garden", t[0])

    if (message === messages.get_alarm_status.toLowerCase())
      get_alarm_state()
        .then(state => notify_helper(t[0], state))

    // zwave
    if (message === messages.zwave.toLowerCase())
      notify_helper(t[0], `You can do these zwave things`, zwave_messages)

    if (message === zwave_messages.zwave_secureadd.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {secureAddNode: random_number()})

    if (message === zwave_messages.zwave_add.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {addNode: random_number()})

    if (message === zwave_messages.zwave_cancel.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {cancelControllerCommand: random_number()})

    if (message === zwave_messages.zwave_remove.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {removeNode: random_number()})

    if (message === zwave_messages.zwave_heal.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {healNetwork: random_number()})

    if (message === zwave_messages.zwave_reset.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {softReset: random_number()})

    if (message === zwave_messages.zwave_follow.toLowerCase()) {
      awsMqttClient.subscribe("zwave/log", {qos: 1},
        (err, granted) => console.log("aws", err, granted)
      )
      setTimeout(awsMqttClient.unsubscribe, 5 * 60 * 1000, "zwave/log")
    }
  }

})

const random_number = () => Math.floor((Math.random() * 100000) + 1)

const send_camera_to = (camera, who) => {
  let inst_uuid = uuid()
  let imageBody
  return iotdata.getThingShadow({thingName: camera}).promise()
    .then(thing => JSON.parse(thing.payload).state.reported.jpg)

    .then(camera_url => request({uri: camera_url, encoding: null}))
    .then(body => imageBody = body)
    .then(() => s3.putObject({
      Body: imageBody,
      Key: `${inst_uuid}.jpg`,
      ContentType: "image/jpeg",
      Bucket: "me.cns.p.cams"
    }).promise())
    .then(() => s3.getSignedUrl("getObject", {Bucket: "me.cns.p.cams", Key: `${inst_uuid}.jpg`}))
    .then(signedurl => notify_helper(who, null, null, true, signedurl))
    .then(() => rekognition.detectLabels({
      Image: {
        Bytes: imageBody,
      },
      MaxLabels: 20,
      MinConfidence: 70
    }).promise())
    .then(labels => labels.Labels.map(label => label.Name))
    .then(labels => notify_helper(who, `Possible contents: ${labels.join(", ")}`, null, true))

}

const reply_with_alarm_status = who => get_alarm_ready_status().then(ready_status => notify_helper(who, `Alarm is currently${ready_status ? " " : " not "}ready to arm`, null, true))

const messages = {
  start: "/start",
  unlock_door: "Unlock the door",
  disarm_alarm: "Disarm alarm",
  arm_alarm_home: "Arm alarm home",
  arm_alarm_away: "Arm alarm away",
  get_alarm_status: "Get alarm status",
  cam_driveway: "Get driveway camera",
  cam_garden: "Get garden camera",
  cam_porch: "Get porch camera",
  zwave: "Z-wave management"
}

const zwave_messages = {
  start: "/start",
  zwave_secureadd: "Securely add device",
  zwave_add: "Insecurely add device",
  zwave_cancel: "Cancel controller command",
  zwave_remove: "Remove device",
  zwave_heal: "Heal network",
  zwave_reset: "Soft reset controller",
  zwave_follow: "Follow events for 5 minutes"
}

const TL_MAP = {
  chris: CHRIS_TELEGRAM_ID,
  hannah: HANNAH_TELEGRAM_ID,
  everyone: GROUP_TELEGRAM_ID
}

const message_parser = message => {
  try {
    return JSON.parse(message.toString())
  }
  catch (e) {
    return message.toString()
  }
}

const zwave_helper = (thing, state) => iotdata.updateThingShadow({
    thingName: thing,
    payload: JSON.stringify({state: {desired: state}})
  }
).promise()

const notify_helper = (who, message, actions = null, disableNotification = false, image = null) =>
  awsMqttClient.publish(`notify/in/${who}`, JSON.stringify({
    disableNotification: disableNotification,
    message: message,
    image: image,
    buttons: actions ? _.map(actions, action => {
      return {title: action, value: action}
    }) : null
  }))

const say_helper = (where, what) => awsMqttClient.publish(`sonos/say/${where}`, JSON.stringify([what, getSayVolume()]), {qos: 0})

const getSayVolume = () => _.inRange(new Date().getHours(), 6, 18) ? 40 : 15

awsMqttClient.on("connect", () => console.log("aws connected"))
awsMqttClient.on("error", (error) => console.error("aws", error))
awsMqttClient.on("close", () => console.error("aws connection close"))
awsMqttClient.on("offline", () => console.log("aws offline"))

rulesAdd("the {string} button is {string}", (thing, action, event) => {
  console.log(event)
  if (thing === "doorbell" &&
    event.topic === "$aws/things/zwave_f2e55e6c_10/shadow/update/documents" &&
    event.message.current.state.reported.basic.Basic === 0 &&
    event.message.current.state.reported.basic.Basic !== event.message.previous.state.reported.basic.Basic)
    return true
  return false
})

rulesAdd("the alarm state changes to {string}", (state, event) =>
  event.topic === "$aws/things/alarm_status/shadow/update/documents" &&
  event.message.previous.state.reported.state !== event.message.current.state.reported.state &&
  event.message.current.state.reported.state.toLowerCase() === state.toLowerCase()
)

rulesAdd("the alarm is not {string}", async state => {
  let current_state = await get_alarm_state()
  return current_state !== state
})

rulesAdd("the alarm is {string}", async state => {
  let current_state = await get_alarm_state()
  return current_state === state
})

rulesAdd("the {string} speaker says {string}", (speaker, message) =>
  say_helper(speaker, message)
)

rulesAdd("a screengrab of the {string} is sent to {string}", (camera, who) => {
  if (camera === "Driveway camera")
    camera = "camera_external_driveway"

  if (camera === "Porch camera")
    camera = "porch_external_driveway"

  return send_camera_to(camera, TL_MAP[who.toLowerCase()])
})

rulesAdd("{string} {string} Home", (device, transition, event) => {
  const t = mqttWildcard(event.topic, "owntracks/+/+/event")
  return t !== null &&
    t[1] === device &&
    event.message._type === "transition" &&
    event.message.event === transition &&
    event.message.desc === "Home"
})

const calculate_time = (number, measure) => {
  switch (measure) {
    case "milliseconds":
    case "millisecond":
      return number
    case "seconds":
    case "second":
      return calculate_time(number * 1000, "milliseconds")
    case "minutes":
    case "minute":
      return calculate_time(number * 60, "seconds")
    case "hours":
    case "hour":
      return calculate_time(number * 60, "minutes")
    default:
      throw "unknown time measure"
  }
}

const clock_tic = setInterval(() => eventHandler({topic: "clock tic"}), calculate_time(15, "minutes"))

const thing_lookup = {
  "front door lock": "zwave_f2e55e6c_4",
  "Hallway heating": "zwave_f2e55e6c_11",
  "Kitchen heating": "zwave_f2e55e6c_12",
  "Dining Room heating": "zwave_f2e55e6c_13",
  "Master bedroom radiator": "zwave_f2e55e6c_14"
}

rulesAdd("a clock tic", event => event.topic === "clock tic")

rulesAdd("the {string} {word} {string} should be {string}", (device, genre, setting, value) => zwave_helper(thing_lookup[thing], {[genre]: {[setting]: value}}))

rulesAdd("the time is between {string} and {string}", (start, end) => new Date().isBetween(start, end))

rulesAdd("the underfloor {string} should be {int}°C", (room, temp) => zwave_helper(thing_lookup[room], {user: {Heating: temp}}))

rulesAdd("the {string} should be {int}°C", (room, temp) => zwave_helper(thing_lookup[room], {user: {"Comfort setpoint": temp}}))

rulesAdd("a delay of {int} {word}", async (number, measure) => new Promise(resolve => setTimeout(resolve, calculate_time((number, measure.toLowerCase())))))

rulesAdd("the alarm state should be {string}", state => set_alarm_state(state.toLowerCase()))

rulesAdd("a message reading {string} is sent to {string}", (message, who) => notify_helper(TL_MAP[who.toLowerCase()], message))

rulesAdd("a zwave log message is received", event => event.topic === "zwave/log")

rulesAdd("the event is forwarded to {string}", (who, event) => notify_helper(TL_MAP[who.toLowerCase()], `zwave ${event.message.homeid} ${JSON.stringify(event.message.log)}`))

rulesAdd("a message reading {string} is sent to {string} with a button to {string}", (message, who, button) =>
  notify_helper(TL_MAP[who.toLowerCase()], message, [button])
)

awsMqttClient.on("message", (topic, message) =>
  eventHandler({topic: topic, message: message_parser(message)}))

// pickling needs to be done after adding all the rules
console.log(gherkin)
pickleGherkin(gherkin)
<commit_msg>fix typo on external porch camera name<commit_after>/*eslint no-console: "off"*/

const mqttWildcard = require("mqtt-wildcard")
const _ = require("lodash")
const AWS = require("aws-sdk")
const request = require("request-promise-native")
const uuid = require("uuid/v4")
const Date = require("sugar-date").Date

const {AWS_ACCESS_KEY, AWS_SECRET_ACCESS_KEY, AWS_IOT_ENDPOINT_HOST, AWS_REGION, CHRIS_TELEGRAM_ID, HANNAH_TELEGRAM_ID, GROUP_TELEGRAM_ID} = process.env
const {rulesAdd, eventHandler, pickleGherkin} = require("./rules")
const fs = require("fs")
const gherkin = fs.readdirSync("features").map(file => fs.readFileSync(`features/${file}`).toString()).join("\n").replace(/Feature:/ig, "#Feature:").substr(1)

const AWSMqtt = require("aws-mqtt-client").default

const awsMqttClient = new AWSMqtt({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  endpointAddress: AWS_IOT_ENDPOINT_HOST,
  region: AWS_REGION,
  logger: console
})

const iotdata = new AWS.IotData({
  endpoint: AWS_IOT_ENDPOINT_HOST,
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: AWS_REGION,
  logger: console

})

const rekognition = new AWS.Rekognition({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: "eu-west-1"
})

const s3 = new AWS.S3({
  accessKeyId: AWS_ACCESS_KEY,
  secretAccessKey: AWS_SECRET_ACCESS_KEY,
  region: AWS_REGION,
})

const awsTopics = [
  "owntracks/+/+/event",
  "$aws/things/alarm_status/shadow/update/documents",
  "$aws/things/zwave_f2e55e6c_10/shadow/update/documents",
  `notify/out/${CHRIS_TELEGRAM_ID}`,
  `notify/out/${HANNAH_TELEGRAM_ID}`
]

awsMqttClient.on("connect", () => awsMqttClient.subscribe(awsTopics,
  {qos: 1},
  (err, granted) => console.log("aws", err, granted)
))

const get_alarm_state = () => iotdata.getThingShadow({thingName: "alarm_status"}).promise()
  .then(thing => JSON.parse(thing.payload).state.reported.state)

const get_alarm_ready_status = () => iotdata.getThingShadow({thingName: "alarm_status"}).promise()
  .then(thing => JSON.parse(thing.payload).state.reported.ready_status)

const set_alarm_state = state => iotdata.updateThingShadow({
  thingName: "alarm_status",
  payload: JSON.stringify({state: {desired: {state: state}}})
}, (err, data) => console.log(err, data))

awsMqttClient.on("message", function (topic, message) {
  message = message_parser(message)

  // react to chatbot commands
  if ((t = mqttWildcard(topic, "notify/out/+")) && t !== null) {
    // send acknowledgement back to user

    message = message.toLowerCase()
    console.log(`Telegram user ${t[0]} just sent:"${message}"`)

    if (message === messages.unlock_door.toLowerCase())
      iotdata.updateThingShadow({
        thingName: "zwave_f2e55e6c_4",
        payload: JSON.stringify({state: {desired: {user: {Locked: 0}}}})
      }, (err, data) => console.log(err, data))

    if (message === messages.arm_alarm_home.toLowerCase()) {
      reply_with_alarm_status(t[0].toString())
      set_alarm_state("arm_home")
    }

    if (message === messages.arm_alarm_away.toLowerCase()) {
      reply_with_alarm_status(t[0].toString())
      set_alarm_state("arm_away")
    }

    if (message === messages.disarm_alarm.toLowerCase())
      set_alarm_state("disarm")

    if (message.startsWith("say")) {
      let split_message = /say\s(\w+)(.*)/gi.exec(message)
      say_helper(split_message[1], split_message[2])
    }

    if (message === messages.start.toLowerCase())
      notify_helper(t[0], `You can do these things`, messages)

    if (message === messages.cam_driveway.toLowerCase())
      send_camera_to("camera_external_driveway", t[0])

    if (message === messages.cam_garden.toLowerCase())
      send_camera_to("camera_external_garden", t[0])

    if (message === messages.get_alarm_status.toLowerCase())
      get_alarm_state()
        .then(state => notify_helper(t[0], state))

    // zwave
    if (message === messages.zwave.toLowerCase())
      notify_helper(t[0], `You can do these zwave things`, zwave_messages)

    if (message === zwave_messages.zwave_secureadd.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {secureAddNode: random_number()})

    if (message === zwave_messages.zwave_add.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {addNode: random_number()})

    if (message === zwave_messages.zwave_cancel.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {cancelControllerCommand: random_number()})

    if (message === zwave_messages.zwave_remove.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {removeNode: random_number()})

    if (message === zwave_messages.zwave_heal.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {healNetwork: random_number()})

    if (message === zwave_messages.zwave_reset.toLowerCase())
      zwave_helper("zwave_f2e55e6c", {softReset: random_number()})

    if (message === zwave_messages.zwave_follow.toLowerCase()) {
      awsMqttClient.subscribe("zwave/log", {qos: 1},
        (err, granted) => console.log("aws", err, granted)
      )
      setTimeout(awsMqttClient.unsubscribe, 5 * 60 * 1000, "zwave/log")
    }
  }

})

const random_number = () => Math.floor((Math.random() * 100000) + 1)

const send_camera_to = (camera, who) => {
  let inst_uuid = uuid()
  let imageBody
  return iotdata.getThingShadow({thingName: camera}).promise()
    .then(thing => JSON.parse(thing.payload).state.reported.jpg)

    .then(camera_url => request({uri: camera_url, encoding: null}))
    .then(body => imageBody = body)
    .then(() => s3.putObject({
      Body: imageBody,
      Key: `${inst_uuid}.jpg`,
      ContentType: "image/jpeg",
      Bucket: "me.cns.p.cams"
    }).promise())
    .then(() => s3.getSignedUrl("getObject", {Bucket: "me.cns.p.cams", Key: `${inst_uuid}.jpg`}))
    .then(signedurl => notify_helper(who, null, null, true, signedurl))
    .then(() => rekognition.detectLabels({
      Image: {
        Bytes: imageBody,
      },
      MaxLabels: 20,
      MinConfidence: 70
    }).promise())
    .then(labels => labels.Labels.map(label => label.Name))
    .then(labels => notify_helper(who, `Possible contents: ${labels.join(", ")}`, null, true))

}

const reply_with_alarm_status = who => get_alarm_ready_status().then(ready_status => notify_helper(who, `Alarm is currently${ready_status ? " " : " not "}ready to arm`, null, true))

const messages = {
  start: "/start",
  unlock_door: "Unlock the door",
  disarm_alarm: "Disarm alarm",
  arm_alarm_home: "Arm alarm home",
  arm_alarm_away: "Arm alarm away",
  get_alarm_status: "Get alarm status",
  cam_driveway: "Get driveway camera",
  cam_garden: "Get garden camera",
  cam_porch: "Get porch camera",
  zwave: "Z-wave management"
}

const zwave_messages = {
  start: "/start",
  zwave_secureadd: "Securely add device",
  zwave_add: "Insecurely add device",
  zwave_cancel: "Cancel controller command",
  zwave_remove: "Remove device",
  zwave_heal: "Heal network",
  zwave_reset: "Soft reset controller",
  zwave_follow: "Follow events for 5 minutes"
}

const TL_MAP = {
  chris: CHRIS_TELEGRAM_ID,
  hannah: HANNAH_TELEGRAM_ID,
  everyone: GROUP_TELEGRAM_ID
}

const message_parser = message => {
  try {
    return JSON.parse(message.toString())
  }
  catch (e) {
    return message.toString()
  }
}

const zwave_helper = (thing, state) => iotdata.updateThingShadow({
    thingName: thing,
    payload: JSON.stringify({state: {desired: state}})
  }
).promise()

const notify_helper = (who, message, actions = null, disableNotification = false, image = null) =>
  awsMqttClient.publish(`notify/in/${who}`, JSON.stringify({
    disableNotification: disableNotification,
    message: message,
    image: image,
    buttons: actions ? _.map(actions, action => {
      return {title: action, value: action}
    }) : null
  }))

const say_helper = (where, what) => awsMqttClient.publish(`sonos/say/${where}`, JSON.stringify([what, getSayVolume()]), {qos: 0})

const getSayVolume = () => _.inRange(new Date().getHours(), 6, 18) ? 40 : 15

awsMqttClient.on("connect", () => console.log("aws connected"))
awsMqttClient.on("error", (error) => console.error("aws", error))
awsMqttClient.on("close", () => console.error("aws connection close"))
awsMqttClient.on("offline", () => console.log("aws offline"))

rulesAdd("the {string} button is {string}", (thing, action, event) => {
  console.log(event)
  if (thing === "doorbell" &&
    event.topic === "$aws/things/zwave_f2e55e6c_10/shadow/update/documents" &&
    event.message.current.state.reported.basic.Basic === 0 &&
    event.message.current.state.reported.basic.Basic !== event.message.previous.state.reported.basic.Basic)
    return true
  return false
})

rulesAdd("the alarm state changes to {string}", (state, event) =>
  event.topic === "$aws/things/alarm_status/shadow/update/documents" &&
  event.message.previous.state.reported.state !== event.message.current.state.reported.state &&
  event.message.current.state.reported.state.toLowerCase() === state.toLowerCase()
)

rulesAdd("the alarm is not {string}", async state => {
  let current_state = await get_alarm_state()
  return current_state !== state
})

rulesAdd("the alarm is {string}", async state => {
  let current_state = await get_alarm_state()
  return current_state === state
})

rulesAdd("the {string} speaker says {string}", (speaker, message) =>
  say_helper(speaker, message)
)

rulesAdd("a screengrab of the {string} is sent to {string}", (camera, who) => {
  if (camera === "Driveway camera")
    camera = "camera_external_driveway"

  if (camera === "Porch camera")
    camera = "camera_external_porch"

  return send_camera_to(camera, TL_MAP[who.toLowerCase()])
})

rulesAdd("{string} {string} Home", (device, transition, event) => {
  const t = mqttWildcard(event.topic, "owntracks/+/+/event")
  return t !== null &&
    t[1] === device &&
    event.message._type === "transition" &&
    event.message.event === transition &&
    event.message.desc === "Home"
})

const calculate_time = (number, measure) => {
  switch (measure) {
    case "milliseconds":
    case "millisecond":
      return number
    case "seconds":
    case "second":
      return calculate_time(number * 1000, "milliseconds")
    case "minutes":
    case "minute":
      return calculate_time(number * 60, "seconds")
    case "hours":
    case "hour":
      return calculate_time(number * 60, "minutes")
    default:
      throw "unknown time measure"
  }
}

const clock_tic = setInterval(() => eventHandler({topic: "clock tic"}), calculate_time(15, "minutes"))

const thing_lookup = {
  "front door lock": "zwave_f2e55e6c_4",
  "Hallway heating": "zwave_f2e55e6c_11",
  "Kitchen heating": "zwave_f2e55e6c_12",
  "Dining Room heating": "zwave_f2e55e6c_13",
  "Master bedroom radiator": "zwave_f2e55e6c_14"
}

rulesAdd("a clock tic", event => event.topic === "clock tic")

rulesAdd("the {string} {word} {string} should be {string}", (device, genre, setting, value) => zwave_helper(thing_lookup[thing], {[genre]: {[setting]: value}}))

rulesAdd("the time is between {string} and {string}", (start, end) => new Date().isBetween(start, end))

rulesAdd("the underfloor {string} should be {int}°C", (room, temp) => zwave_helper(thing_lookup[room], {user: {Heating: temp}}))

rulesAdd("the {string} should be {int}°C", (room, temp) => zwave_helper(thing_lookup[room], {user: {"Comfort setpoint": temp}}))

rulesAdd("a delay of {int} {word}", async (number, measure) => new Promise(resolve => setTimeout(resolve, calculate_time((number, measure.toLowerCase())))))

rulesAdd("the alarm state should be {string}", state => set_alarm_state(state.toLowerCase()))

rulesAdd("a message reading {string} is sent to {string}", (message, who) => notify_helper(TL_MAP[who.toLowerCase()], message))

rulesAdd("a zwave log message is received", event => event.topic === "zwave/log")

rulesAdd("the event is forwarded to {string}", (who, event) => notify_helper(TL_MAP[who.toLowerCase()], `zwave ${event.message.homeid} ${JSON.stringify(event.message.log)}`))

rulesAdd("a message reading {string} is sent to {string} with a button to {string}", (message, who, button) =>
  notify_helper(TL_MAP[who.toLowerCase()], message, [button])
)

awsMqttClient.on("message", (topic, message) =>
  eventHandler({topic: topic, message: message_parser(message)}))

// pickling needs to be done after adding all the rules
console.log(gherkin)
pickleGherkin(gherkin)
<|endoftext|>


<commit_before>'use strict';

var CONFIG_FILE = '/.cartridgerc';
var MATCH_REGEX = /(\[\/\/\]: <> \(Modules start\)\s)([^[]*)(\[\/\/\]: <> \(Modules end\)\s)/g;

var del      = require('del');
var path     = require('path');
var chalk    = require('chalk');
var template = require('lodash/template');
var Promise  = require('bluebird');
var fs       = Promise.promisifyAll(require('fs-extra'));

var paths = {
	project:   path.resolve('../../'),
	config:    path.resolve('../../', '_config'),
	readme:    path.resolve('../../', 'readme.md'),
	cartridge: path.resolve('../../_cartridge')
};

// Checks if the project has been set up with Cartridge
function hasCartridgeInstalled() {
	try {
		fs.accessSync(paths.project + CONFIG_FILE, fs.R_OK | fs.W_OK);
	} catch(err) {
		return false;
	}

	return true;
}

function insertModulesInToReadme(readmeContents) {
	return readmeContents.replace(MATCH_REGEX, this.renderedModules);
}

function updateReadme(renderedModuleTemplate) {

	return fs.readFileAsync(paths.readme, 'utf8')
		.bind({
			renderedModules: renderedModuleTemplate
		})
		.then(insertModulesInToReadme)
		.then(function(fileContent){
			return fs.writeFileAsync(paths.readme, fileContent)
		})
		.catch(function(err) {
			console.log('updateReadme error');
			console.error(err);
			process.exit(1);
		});
}

function compileTemplate(rawTemplate) {
	var compiledTemplate = template(rawTemplate);

	return compiledTemplate(this)
}

function updateReadmeModules(rcData) {
	var filePath = path.join(paths.cartridge, 'modules.tpl');

	return fs.readFileAsync(filePath, 'utf8')
		.bind(rcData)
		.then(compileTemplate)
		.then(updateReadme)
		.catch(function(err){
			console.log('updateReadmeModules error');
			console.error(err);
			console.trace();
			process.exit(1);
		});
}

function ensureProjectConfigPaths(projectConfig) {
	if(!projectConfig.hasOwnProperty('paths')) {
		projectConfig.paths = {
			src:   {},
			dest: {}
		};
	}

	if(!projectConfig.paths.hasOwnProperty('src')) {
		projectConfig.paths.src = {};
	}

	if(!projectConfig.paths.hasOwnProperty('dest')) {
		projectConfig.paths.dest = {};
	}

	return projectConfig;
}

function getModuleId(modules, moduleName) {
	var i, moduleIndex;

	// Yes so check that this one doesn't already exist
	for(i = 0; i < modules.length; i++) {

		if(modules[i].name == moduleName) {
			moduleIndex = i;
			break;
		}
	}

	if(moduleIndex === undefined) {
		moduleIndex = modules.length;
	}

	return moduleIndex;
}

function addModule(rcContent) {
	// This is bound to the module data
	var i, moduleIndex;

	// Check if any modules have been added to the rc yet
	if(!rcContent.hasOwnProperty('modules')) {
		// No so create the array
		rcContent.modules = [];
		moduleIndex = 0;
	} else {
		moduleIndex = getModuleId(rcContent.modules, this.name);
	}

	// Set the data
	rcContent.modules[moduleIndex] = this;

	return rcContent;
}

function writeJsonFile(fileContent) {
	return fs.writeJsonAsync(this, fileContent)
		.then(function(){
			return fileContent;
		});
}

module.exports = function(packageConfig) {
	var cartridgeApi = {};

	cartridgeApi.ensureCartridgeExists = function ensureCartridgeExists() {
		if(!hasCartridgeInstalled()) {
			console.error(chalk.red('Cartridge is not set up in this directory. Please set it up first before installing this module'));
			process.exit(1);
		}
	};

	// Adds the specified module to the .cartridgerc file
	cartridgeApi.addToRc = function addToRc() {
		var filePath = path.join(paths.project,  CONFIG_FILE);

		var moduleConfig = {
			name:    packageConfig.name,
			version: packageConfig.version,
			site:    packageConfig.homepage,
			task:    packageConfig.name + '/' + packageConfig.main
		};

		cartridgeApi.logMessage('Adding ' + packageConfig.name + ' to .cartridgerc');

		return fs.readJsonAsync(filePath)
			.bind(moduleConfig)
			.then(addModule)
			.bind(filePath)
			.then(writeJsonFile)
			.then(updateReadmeModules)
			.then(function() {
				cartridgeApi.logMessage('Finished: adding ' + packageConfig.name + ' to .cartridgerc');
				return Promise.resolve();
			})
			.catch(function(err){
				console.log('addToRc error');
				console.error(err);
				process.exit(1);
			});
	};

	// Removes the specified module from the .cartridgerc file
	cartridgeApi.removeFromRc = function removeFromRc(moduleName) {
		// TODO: implement
	};

	// Modify the project configuration (project.json) with a transform function
	cartridgeApi.modifyProjectConfig = function modifyProjectConfig(transform) {
		var filePath = path.join(paths.config,  '/project.json');

		cartridgeApi.logMessage('Updating project config for ' + packageConfig.name);

		return fs.readJsonAsync(paths.config + '/project.json')
			.then(ensureProjectConfigPaths)
			.then(transform)
			.bind(filePath)
			.then(writeJsonFile)
			.then(function(){
				cartridgeApi.logMessage('Finished: modifying project config for ' + packageConfig.name);
				return Promise.resolve();
			})
			.catch(function(err){
				console.log('modifyProjectConfig error');
				console.error(err);
				process.exit(1);
			});
	};

	// Add configuration files to the project _config directory for this module
	cartridgeApi.addModuleConfig = function addModuleConfig(configPath, callback) {
		var toPath = path.join(paths.config, path.basename(configPath));
		return fs.copyAsync(configPath, toPath)
			.then(function(){
				cartridgeApi.logMessage('Finished: adding ' + packageConfig.name + ' config files');
				return Promise.resolve();
			});
	};

	// Remove configuration files from the project _config directory for this module
	cartridgeApi.removeModuleConfig = function removeModuleConfig() {
		// TODO: implement
	};

	cartridgeApi.setPackageConfig = function setPackageDetails(packageDetails) {
		packageConfig = info;
	};

	cartridgeApi.finishInstall = function finishInstall(packageDetails) {
		cartridgeApi.logMessage('Finished post install of ' + packageConfig.name);
		process.exit(0);
	};

	cartridgeApi.logMessage = function logMessage(message) {
		console.log(message);
	};

	return cartridgeApi;
};
<commit_msg>Remove the superceded setPackageDetails method<commit_after>'use strict';

var CONFIG_FILE = '/.cartridgerc';
var MATCH_REGEX = /(\[\/\/\]: <> \(Modules start\)\s)([^[]*)(\[\/\/\]: <> \(Modules end\)\s)/g;

var del      = require('del');
var path     = require('path');
var chalk    = require('chalk');
var template = require('lodash/template');
var Promise  = require('bluebird');
var fs       = Promise.promisifyAll(require('fs-extra'));

var paths = {
	project:   path.resolve('../../'),
	config:    path.resolve('../../', '_config'),
	readme:    path.resolve('../../', 'readme.md'),
	cartridge: path.resolve('../../_cartridge')
};

// Checks if the project has been set up with Cartridge
function hasCartridgeInstalled() {
	try {
		fs.accessSync(paths.project + CONFIG_FILE, fs.R_OK | fs.W_OK);
	} catch(err) {
		return false;
	}

	return true;
}

function insertModulesInToReadme(readmeContents) {
	return readmeContents.replace(MATCH_REGEX, this.renderedModules);
}

function updateReadme(renderedModuleTemplate) {

	return fs.readFileAsync(paths.readme, 'utf8')
		.bind({
			renderedModules: renderedModuleTemplate
		})
		.then(insertModulesInToReadme)
		.then(function(fileContent){
			return fs.writeFileAsync(paths.readme, fileContent)
		})
		.catch(function(err) {
			console.log('updateReadme error');
			console.error(err);
			process.exit(1);
		});
}

function compileTemplate(rawTemplate) {
	var compiledTemplate = template(rawTemplate);

	return compiledTemplate(this)
}

function updateReadmeModules(rcData) {
	var filePath = path.join(paths.cartridge, 'modules.tpl');

	return fs.readFileAsync(filePath, 'utf8')
		.bind(rcData)
		.then(compileTemplate)
		.then(updateReadme)
		.catch(function(err){
			console.log('updateReadmeModules error');
			console.error(err);
			console.trace();
			process.exit(1);
		});
}

function ensureProjectConfigPaths(projectConfig) {
	if(!projectConfig.hasOwnProperty('paths')) {
		projectConfig.paths = {
			src:   {},
			dest: {}
		};
	}

	if(!projectConfig.paths.hasOwnProperty('src')) {
		projectConfig.paths.src = {};
	}

	if(!projectConfig.paths.hasOwnProperty('dest')) {
		projectConfig.paths.dest = {};
	}

	return projectConfig;
}

function getModuleId(modules, moduleName) {
	var i, moduleIndex;

	// Yes so check that this one doesn't already exist
	for(i = 0; i < modules.length; i++) {

		if(modules[i].name == moduleName) {
			moduleIndex = i;
			break;
		}
	}

	if(moduleIndex === undefined) {
		moduleIndex = modules.length;
	}

	return moduleIndex;
}

function addModule(rcContent) {
	// This is bound to the module data
	var i, moduleIndex;

	// Check if any modules have been added to the rc yet
	if(!rcContent.hasOwnProperty('modules')) {
		// No so create the array
		rcContent.modules = [];
		moduleIndex = 0;
	} else {
		moduleIndex = getModuleId(rcContent.modules, this.name);
	}

	// Set the data
	rcContent.modules[moduleIndex] = this;

	return rcContent;
}

function writeJsonFile(fileContent) {
	return fs.writeJsonAsync(this, fileContent)
		.then(function(){
			return fileContent;
		});
}

module.exports = function(packageConfig) {
	var cartridgeApi = {};

	cartridgeApi.ensureCartridgeExists = function ensureCartridgeExists() {
		if(!hasCartridgeInstalled()) {
			console.error(chalk.red('Cartridge is not set up in this directory. Please set it up first before installing this module'));
			process.exit(1);
		}
	};

	// Adds the specified module to the .cartridgerc file
	cartridgeApi.addToRc = function addToRc() {
		var filePath = path.join(paths.project,  CONFIG_FILE);

		var moduleConfig = {
			name:    packageConfig.name,
			version: packageConfig.version,
			site:    packageConfig.homepage,
			task:    packageConfig.name + '/' + packageConfig.main
		};

		cartridgeApi.logMessage('Adding ' + packageConfig.name + ' to .cartridgerc');

		return fs.readJsonAsync(filePath)
			.bind(moduleConfig)
			.then(addModule)
			.bind(filePath)
			.then(writeJsonFile)
			.then(updateReadmeModules)
			.then(function() {
				cartridgeApi.logMessage('Finished: adding ' + packageConfig.name + ' to .cartridgerc');
				return Promise.resolve();
			})
			.catch(function(err){
				console.log('addToRc error');
				console.error(err);
				process.exit(1);
			});
	};

	// Removes the specified module from the .cartridgerc file
	cartridgeApi.removeFromRc = function removeFromRc(moduleName) {
		// TODO: implement
	};

	// Modify the project configuration (project.json) with a transform function
	cartridgeApi.modifyProjectConfig = function modifyProjectConfig(transform) {
		var filePath = path.join(paths.config,  '/project.json');

		cartridgeApi.logMessage('Updating project config for ' + packageConfig.name);

		return fs.readJsonAsync(paths.config + '/project.json')
			.then(ensureProjectConfigPaths)
			.then(transform)
			.bind(filePath)
			.then(writeJsonFile)
			.then(function(){
				cartridgeApi.logMessage('Finished: modifying project config for ' + packageConfig.name);
				return Promise.resolve();
			})
			.catch(function(err){
				console.log('modifyProjectConfig error');
				console.error(err);
				process.exit(1);
			});
	};

	// Add configuration files to the project _config directory for this module
	cartridgeApi.addModuleConfig = function addModuleConfig(configPath, callback) {
		var toPath = path.join(paths.config, path.basename(configPath));
		return fs.copyAsync(configPath, toPath)
			.then(function(){
				cartridgeApi.logMessage('Finished: adding ' + packageConfig.name + ' config files');
				return Promise.resolve();
			});
	};

	// Remove configuration files from the project _config directory for this module
	cartridgeApi.removeModuleConfig = function removeModuleConfig() {
		// TODO: implement
	};

	cartridgeApi.finishInstall = function finishInstall(packageDetails) {
		cartridgeApi.logMessage('Finished post install of ' + packageConfig.name);
		process.exit(0);
	};

	cartridgeApi.logMessage = function logMessage(message) {
		console.log(message);
	};

	return cartridgeApi;
};
<|endoftext|>


<commit_before>var ADDR_RE = /^\[?([^\]]+)\]?:(\d+)$/ // ipv4/ipv6/hostname + port

var cache = {}

module.exports = function addrToIPPort (addr) {
  if (!cache[addr]) {
    var m = ADDR_RE.exec(addr)
    if (!m) throw new Error('invalid addr')
    cache[addr] = [ m[1], Number(m[2]) ]
  }
  return cache[addr]
}

module.exports.reset = function () {
  cache = {}
}
<commit_msg>reset cache every hour<commit_after>var ADDR_RE = /^\[?([^\]]+)\]?:(\d+)$/ // ipv4/ipv6/hostname + port

var cache = {}

module.exports = function addrToIPPort (addr) {
  if (!cache[addr]) {
    var m = ADDR_RE.exec(addr)
    if (!m) throw new Error('invalid addr')
    cache[addr] = [ m[1], Number(m[2]) ]
  }
  return cache[addr]
}

module.exports.reset = function reset () {
  cache = {}
}

// reset cache every hour so it will not grow to consume all memory
// in long-running processes (like in server dameons)
var interval = setInterval(module.exports.reset, 60 * 60 * 1000)
if (interval.unref) interval.unref()
<|endoftext|>


<commit_before>'use strict';

var SkyRemote = require('sky-remote');
var SkyQCheck = require('sky-q');
var Accessory, Service, Characteristic;

module.exports = function(homebridge) {

	Accessory = homebridge.platformAccessory;
	Service = homebridge.hap.Service;
	Characteristic = homebridge.hap.Characteristic;
	homebridge.registerAccessory("homebridge-sky-q-play", "SkyQPlay", SkyQAccessory);
};

function SkyQAccessory(log, config, api) {

	this.log = log;
	this.config = config;
	this.name = config.name || 'Sky Q';
	this.stateful = false;

	if (this.config.cmd) {

		this.cmd = this.config.cmd.split(',');
	} else {

		this.cmd = 'power';
	}
	if (this.cmd == 'power') {
		this.stateful = true;
	}

	var remoteControl = new SkyRemote(config.ipAddress);
	var boxCheck = new SkyQCheck({ip:config.ipAddress})
	this.skyQ = remoteControl;
	this.box = boxCheck;
}

SkyQAccessory.prototype = {

	setPowerState: function(powerOn, callback) {

		var log = this.log;
		var name = this.name;
		var stateful = this.stateful;

		log('Sending "' + this.cmd + '" command to ' + name + '...');

		this.skyQ.press(this.cmd, function(error) {

			if (error) {

				log('Failed to send cmd ' + name + '. ' + error);
			}

			if (stateful == true) {

				callback();
			} else {

				callback(new Error(1)); //Only way to keep the switch from turning on/off
			}
		});
	},

	identify: function(callback) {

		this.log("Identify...");

		callback();
	},

	getState: function(callback) {
		if (this.stateful) {

			this.box.getPowerState().then(isOn=>{
	  		if (isOn) {

			    this.log(this.name + " is on :-)")
			  } else {

			    this.log(this.name + " is in standby :-(")
			  }
			  callback(null, isOn);

			}).catch(err=>{

			  this.log("Unable to determine power state")
			  this.log("Perhaps looking at this error will help you figure out why" + err)
			  callback(err || new Error('Error getting state of ' + this.name));
			})
		} else {

		  callback(null, 0);
		}
	},

	getServices: function() {

		var switchService = new Service.Switch(this.name);

		var characteristic = switchService.getCharacteristic(Characteristic.On).on('set', this.setPowerState.bind(this));

		characteristic.on('get', this.getState.bind(this));

		return [switchService];
	}
};
<commit_msg>Fixed for 2+ button presses!<commit_after>'use strict';

var SkyRemote = require('sky-remote');
var SkyQCheck = require('sky-q');
var Accessory, Service, Characteristic;

module.exports = function(homebridge) {

	Accessory = homebridge.platformAccessory;
	Service = homebridge.hap.Service;
	Characteristic = homebridge.hap.Characteristic;
	homebridge.registerAccessory("homebridge-sky-q-play", "SkyQPlay", SkyQAccessory);
};

function SkyQAccessory(log, config, api) {

	this.log = log;
	this.config = config;
	this.name = config.name || 'Sky Q';
	this.stateful = false;

	if (this.config.cmd === 'power' || !this.config.cmd) {

		this.stateful = true;
	}

	var remoteControl = new SkyRemote(config.ipAddress);
	var boxCheck = new SkyQCheck({ip:config.ipAddress})
	this.skyQ = remoteControl;
	this.box = boxCheck;
}

SkyQAccessory.prototype = {

	setPowerState: function(powerOn, callback) {

		var log = this.log;
		var name = this.name;
		var stateful = this.stateful;

		if (this.config.cmd) {

			this.cmd = this.config.cmd.split(',');
		} else {

			this.cmd = 'power';
		}

		log('Sending "' + this.cmd + '" command to ' + name + '...');

		this.skyQ.press(this.cmd, function(error) {

			if (error) {

				log('Failed to send cmd ' + name + '. ' + error);
			}

			if (stateful === true) {

				callback();
			} else {

				callback(new Error(1)); //Only way to keep the switch from turning on/off
			}
		});
	},

	identify: function(callback) {

		this.log("Identify...");

		callback();
	},

	getState: function(callback) {
		if (this.stateful) {

			this.box.getPowerState().then(isOn=>{
	  		if (isOn) {

			    this.log(this.name + " is on :-)")
			  } else {

			    this.log(this.name + " is in standby :-(")
			  }
			  callback(null, isOn);

			}).catch(err=>{

			  this.log("Unable to determine power state")
			  this.log("Perhaps looking at this error will help you figure out why" + err)
			  callback(err || new Error('Error getting state of ' + this.name));
			})
		} else {

		  callback(null, 0);
		}
	},

	getServices: function() {

		var switchService = new Service.Switch(this.name);

		var characteristic = switchService.getCharacteristic(Characteristic.On).on('set', this.setPowerState.bind(this));

		characteristic.on('get', this.getState.bind(this));

		return [switchService];
	}
};
<|endoftext|>


<commit_before>'use strict';
var gutil 	= require('gulp-util'),
	shelljs	= require('shelljs/global'),
	through = require('through2'),

	stream = through.obj(function(file, enc, callback) {
		this.push(file);
		callback();
	}),

	build_arguments = function(opts) {
		var args = []; // initialize command array

		var keys = Object.keys( opts );
		for( var i = 0, length = keys.length; i < length; i++ ) {
			if(opts[ keys[ i ] ]) { //handle false case if passed in explicitly
				var parameter = ((keys[ i ].length == 1)?'-':'--'); // use - for d, w, n, etc.
				parameter += keys[ i ]; // add parameter name
				if ( typeof opts[ keys[ i ] ] != 'boolean') {
					parameter += ' ' + opts[ keys[ i ] ];
				}
				args.push( parameter ); // add parameter to command array
			}
		}
		return args.join(' ');
	},
	log_indent = '|   ',
	log_exec = function(output) {
		var output_lines = output.replace(/^\s+|\s+$/,'').split(/[\n\r]+/),// Trims the output and returns it split into an array of lines
			log_line;
		if (output) {
			for (log_line = 0; log_line < output_lines.length; ++log_line) {
				gutil.log(log_indent,output_lines[log_line]);
			}
		}
	};

if (!which('php')) {
	stream.emit('error', new gutil.PluginError('gulp-composer', "PHP is a composer requirement and therefore it is required to use gulp-composer."));
}
module.exports = function (cmd, opts) {
	var bin, commandToRun, cwd;

	if (typeof cmd === 'undefined' || typeof cmd === 'object') {
		// Looks like this was called with either no parameters
		// or the options were passed through the first parameter
		// In this case, we'll ignore all subsequent params
		opts = cmd || {};
		cmd = 'install';
	} else {
		// Make sure opts is set
		opts = opts || {};
	}

	// cwd legacy support
	cwd = opts['working-dir'] || opts.d || opts.cwd || process.cwd();

	// using --working-dir=xxxx instead of -d makes logging clearer
	// and allows us to remove an unneeded `gutil.log` line
	opts['working-dir'] = cwd;

	delete opts.cwd; // avoid sending cwd parameter
	delete opts.d;   // avoid sending duplicate -d parameter

	// To default to `true` for historical purposes, uncomment the following block
	//if (typeof opts.quiet == 'undefined') {
	//	 opts.quiet = true;
	//}

	// Ensure opts.quiet is a bool
	opts.quiet = !!opts.quiet;

	if (!opts.quiet) {
		// Turn colors on by default
		if (!opts['no-ansi'] && typeof opts.ansi === 'undefined') {
			opts.ansi = true;
		}
	}

	bin = opts.bin || (function(){
			var self_install,
				self_install_dir  = tempdir(),
				self_install_file = 'composer.phar',
				self_install_path = self_install_dir + '/' + self_install_file,
				self_install_options = build_arguments({
					'install-dir': self_install_dir,
					'filename':    self_install_file,
					'quiet':       opts.quiet,
					'ansi':        opts.ansi,
					'no-ansi':    !opts.ansi
				}),
				self_install_alert = function () {
					gutil.log();
					gutil.log(gutil.colors.yellow.inverse(" Significantly improve performance by installing composer on this machine. "));
					gutil.log(gutil.colors.yellow.inverse(" Installation: ") + gutil.colors.yellow.underline.inverse("https://getcomposer.org/doc/00-intro.md") + gutil.colors.yellow.inverse("                     "));
					gutil.log();
				};

			// Use composer.phar if it exists locally...
			if (test('-e',cwd + '/composer.phar')) {
				gutil.log(gutil.colors.green("Defaulting to locally installed " + cwd + "/composer.phar..."));
				return cwd + '/composer.phar';
			}

			// Otherwise use composer if it's installed globally...
			if (which('composer')) {
				gutil.log(gutil.colors.green("Defaulting to globally installed composer..."));
				return 'composer';
			}
			gutil.log(gutil.colors.yellow("Composer is not available globally."));

			if (test('-e', self_install_path)) {
				gutil.log(gutil.colors.yellow("Loading composer from system temp directory..."));
				self_install_alert();
				return self_install_path;
			}

			gutil.log(gutil.colors.yellow("Composer is not available locally, either."));

			// It doesn't exist, so we'll attempt to download it locally and delete it afterward
			if (self_install_options) {
				self_install_options = ' -- ' + self_install_options;
			}

			gutil.log(gutil.colors.magenta.inverse(" Attempting to download composer to system temp directory...               "));
			self_install = exec('php -r "readfile(\'https://getcomposer.org/installer\');" | php' + self_install_options, { async: false, silent: true });

			if (self_install.code != 0) {
				stream.emit('error', new gutil.PluginError('gulp-composer', self_install.output));
			}
			log_exec(self_install.output);

			if (ls(self_install_path)[0]) {
				gutil.log(gutil.colors.magenta.inverse(" Successfully downloaded!                                                  "));
				self_install_alert();
				return self_install_path;
			}

			gutil.log(gutil.colors.red.inverse("Failed to download. All options have been exhausted."));
			gutil.log(gutil.colors.red.inverse("Installation instructions: ") + gutil.colors.blue.underline.inverse("https://getcomposer.org/doc/00-intro.md"));

			return '';
		})();
	delete opts.bin; // avoid sending bin parameter

	if (!bin) {
		stream.emit('error', new gutil.PluginError('gulp-composer', "Composer executable does not exist. Please install before continuing."));
	}

	// build the command array
	commandToRun = [bin, cmd, build_arguments(opts)].join(' ');
	gutil.log(gutil.colors.red(commandToRun));
	gutil.log();
	gutil.log(gutil.colors.cyan.inverse(" Executing composer...                                                     "));

	exec(commandToRun, {silent:true}, function(code, output) {
		if (code != 0) {
			stream.emit('error', new gutil.PluginError('gulp-composer', output));
		}

		log_exec(output);

		gutil.log(gutil.colors.cyan.inverse(" Composer completed.                                                       "));

		stream.end();
		stream.emit("end");
	});

	return stream;
};
<commit_msg>Add option to disable self-install and fix a logging bug<commit_after>'use strict';
var gutil 	= require('gulp-util'),
	shelljs	= require('shelljs/global'),
	through = require('through2'),

	stream = through.obj(function(file, enc, callback) {
		this.push(file);
		callback();
	}),

	build_arguments = function(opts) {
		var args = []; // initialize command array

		var keys = Object.keys( opts );
		for( var i = 0, length = keys.length; i < length; i++ ) {
			if(opts[ keys[ i ] ]) { //handle false case if passed in explicitly
				var parameter = ((keys[ i ].length == 1)?'-':'--'); // use - for d, w, n, etc.
				parameter += keys[ i ]; // add parameter name
				if ( typeof opts[ keys[ i ] ] != 'boolean') {
					parameter += ' ' + opts[ keys[ i ] ];
				}
				args.push( parameter ); // add parameter to command array
			}
		}
		return args.join(' ');
	},
	log_indent = '|   ',
	log_exec = function(output) {
		var output_lines = output.replace(/^\s+|\s+$/,'').split(/[\n\r]+/),// Trims the output and returns it split into an array of lines
			log_line;
		if (output) {
			for (log_line = 0; log_line < output_lines.length; ++log_line) {
				gutil.log(log_indent,output_lines[log_line]);
			}
		}
	};

if (!which('php')) {
	stream.emit('error', new gutil.PluginError('gulp-composer', "PHP is a composer requirement and therefore it is required to use gulp-composer."));
}
module.exports = function (cmd, opts) {
	var bin, commandToRun, cwd, self_install = true;

	if (typeof cmd === 'undefined' || typeof cmd === 'object') {
		// Looks like this was called with either no parameters
		// or the options were passed through the first parameter
		// In this case, we'll ignore all subsequent params
		opts = cmd || {};
		cmd = 'install';
	} else {
		// Make sure opts is set
		opts = opts || {};
	}

	// optional self-install support
	if (typeof opts['self-install'] !== 'undefined') {
		self_install = !!opts['self-install'];
		delete opts['self-install']; // avoid sending parameter to composer
	}

	// cwd legacy support
	cwd = opts['working-dir'] || opts.d || opts.cwd || process.cwd();

	// using --working-dir=xxxx instead of -d makes logging clearer
	// and allows us to remove an unneeded `gutil.log` line
	opts['working-dir'] = cwd;

	delete opts.cwd; // avoid sending parameter to composer
	delete opts.d;   // avoid sending duplicate -d parameter

	// To default to `true` for historical purposes, uncomment the following block
	//if (typeof opts.quiet == 'undefined') {
	//	 opts.quiet = true;
	//}

	// Ensure opts.quiet is a bool
	opts.quiet = !!opts.quiet;

	if (!opts.quiet) {
		// Turn colors on by default
		if (!opts['no-ansi'] && typeof opts.ansi === 'undefined') {
			opts.ansi = true;
		}
	}

	bin = opts.bin || (function(){
			var self_install_cmd,
				self_install_dir  = tempdir(),
				self_install_file = 'composer.phar',
				self_install_path = self_install_dir + '/' + self_install_file,
				self_install_options = build_arguments({
					'install-dir': self_install_dir,
					'filename':    self_install_file,
					'quiet':       opts.quiet,
					'ansi':        opts.ansi,
					'no-ansi':    !opts.ansi
				}),
				self_install_alert = function () {
					gutil.log();
						gutil.log(gutil.colors.yellow.inverse(" Significantly improve performance by installing composer on this machine. "));
					gutil.log(gutil.colors.yellow.inverse(" Installation: ") + gutil.colors.yellow.underline.inverse("https://getcomposer.org/doc/00-intro.md") + gutil.colors.yellow.inverse("                     "));
					gutil.log();
				};

			// Use composer.phar if it exists locally...
			if (test('-e',cwd + '/composer.phar')) {
				gutil.log(gutil.colors.green("Defaulting to locally installed " + cwd + "/composer.phar..."));
				return cwd + '/composer.phar';
			}
			gutil.log(gutil.colors.yellow("Composer is not available locally."));

			// Otherwise use composer if it's installed globally...
			if (which('composer')) {
				gutil.log(gutil.colors.green("Defaulting to globally installed composer..."));
				return 'composer';
			}
			gutil.log(gutil.colors.yellow("Composer is not available globally."));

			if (!self_install) {
				gutil.log(gutil.colors.red.inverse("Failed to load composer and self-install has been disabled."));
				gutil.log(gutil.colors.red.inverse("Installation instructions: ") + gutil.colors.blue.underline.inverse("https://getcomposer.org/doc/00-intro.md"));
				return '';
			}

			if (test('-e', self_install_path)) {
				gutil.log(gutil.colors.yellow("Loading composer from system temp directory..."));
				self_install_alert();
				return self_install_path;
			}

			gutil.log(gutil.colors.yellow("Composer is not available from the system temp, either."));

			// It doesn't exist, so we'll attempt to download it locally and delete it afterward
			if (self_install_options) {
				self_install_options = ' -- ' + self_install_options;
			}

			gutil.log(gutil.colors.magenta.inverse(" Attempting to download composer to system temp directory...               "));
			self_install_cmd = exec('php -r "readfile(\'https://getcomposer.org/installer\');" | php' + self_install_options, { async: false, silent: true });

			if (self_install_cmd.code != 0) {
				stream.emit('error', new gutil.PluginError('gulp-composer', self_install_cmd.output));
			}
			log_exec(self_install_cmd.output);

			if (ls(self_install_path)[0]) {
				gutil.log(gutil.colors.magenta.inverse(" Successfully downloaded!                                                  "));
				self_install_alert();
				return self_install_path;
			}

			gutil.log(gutil.colors.red.inverse("Failed to download. All options have been exhausted."));
			gutil.log(gutil.colors.red.inverse("Installation instructions: ") + gutil.colors.blue.underline.inverse("https://getcomposer.org/doc/00-intro.md"));

			return '';
		})();
	delete opts.bin; // avoid sending bin parameter

	if (!bin) {
		stream.emit('error', new gutil.PluginError('gulp-composer', "Composer executable does not exist. " + ( self_install ? "Self-install was unsuccessful. Please install before continuing." : "Please install or enable the self-install option before continuing.") ));
	}

	// build the command array
	commandToRun = [bin, cmd, build_arguments(opts)].join(' ');
	gutil.log(gutil.colors.red(commandToRun));
	gutil.log();
	gutil.log(gutil.colors.cyan.inverse(" Executing composer...                                                     "));

	exec(commandToRun, {silent:true}, function(code, output) {
		if (code != 0) {
			stream.emit('error', new gutil.PluginError('gulp-composer', output));
		}

		log_exec(output);

		gutil.log(gutil.colors.cyan.inverse(" Composer completed.                                                       "));

		stream.end();
		stream.emit("end");
	});

	return stream;
};
<|endoftext|>


<commit_before>'use strict';
var fs = require('fs');
var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');
var spawn = require('win-spawn');
var tempWrite = require('temp-write');
var dargs = require('dargs');
var shelljs = require('shelljs');
var chalk = require('chalk');

module.exports = function (options) {
	options = options || {};
	var passedArgs = dargs(options, ['bundleExec']);
	var bundleExec = options.bundleExec;
	var versionTest = bundleExec === true ? 'bundle exec jekyll -v' : 'jekyll -v';

	try {
		shelljs.exec(versionTest, {silent: true});
	}
	catch (err) {
		throw new gutil.PluginError('gulp-ruby-jekyll', 'You need to have Ruby and Jekyll installed and in your PATH for this task to work.');
	}

	// NOTE: This looks at single files -- we need to look at the dir as a whole.
	// NOTE: Should source matter / be applied to the input glob? No, let's just
	// say that stream is source, output is dest

	return through.obj(function (file, enc, cb) {
		var self = this;
		// var fileDirname = file.base;

		if (file.isNull()) {
			this.push(file);
			return cb();
		}

		if (file.isStream()) {
			this.emit('error', new gutil.PluginError('gulp-ruby-jekyll', 'Streaming not supported'));
			return cb();
		}

		// NOTE: inputTempFile is a single file based idea.
		tempWrite(file.contents, path.basename(file.path), function (err, inputTempFile) {
			if (err) {
				self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				return cb();
			}

			// NOTE: set up real args. Let people pass anything except --dest && --source
			var args = [
				// 'sass',
				// inputTempFile,
				// outputTempFile,
				// '--load-path', fileDirname
			].concat(passedArgs);

			if (bundleExec) {
				args.unshift('bundle', 'exec');
			}

			// if we're compiling SCSS or CSS files
			// if (path.extname(file.path) === '.css') {
			// 	args.push('--scss');
			// }

			var cmd = args.shift();
			var cp = spawn(cmd, args);

			// NOTE: Will need this.
			// if (process.argv.indexOf('--verbose') !== -1) {
			// 	gutil.log('gulp-ruby-jekyll:', 'Running command:', cmd, chalk.blue(args.join(' ')));
			// }

			cp.on('error', function (err) {
				// TODO: remove when gulp has fixed error handling
				gutil.log('[gulp-ruby-jekyll] ' + err);

				self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				return cb();
			});

			// NOTE: Setting up stderr output
			var errors = '';
			cp.stderr.setEncoding('utf8');
			cp.stderr.on('data', function (data) {
				// ignore deprecation and empty warnings
				// if (/DEPRECATION WARNING/.test(data) || data.trim() === '') {
				// 	return;
				// }

				errors += data;
			});

			cp.on('close', function (code) {
				if (errors) {
					// TODO: remove when gulp has fixed error handling
					// NOTE: Probably need to rewrite these two
					// gutil.log('[gulp-ruby-jekyll]', '\n' + errors.replace(inputTempFile, file.path).replace('Use --trace for backtrace.\n', ''));

					// self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', '\n' + errors.replace(inputTempFile, file.path).replace('Use --trace for backtrace.\n', '')));
					return cb();
				}

				if (code > 0) {
					self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', 'Exited with error code ' + code));
					return cb();
				}

				// NOTE: Pull in those new files
				// NOTE: Once again, doing single files here, got to move to dirs
				// fs.readFile(outputTempFile, function (err, data) {
				// 	if (err) {
				// 		self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				// 		return cb();
				// 	}

				// 	self.push(new gutil.File({
				// 		base: fileDirname,
				// 		contents: data
				// 	}));

				// NOTE: final callback
				// 	cb();
				// });
			});
		});
	});
};
<commit_msg>versionCommand<commit_after>'use strict';
var fs = require('fs');
var path = require('path');
var gutil = require('gulp-util');
var through = require('through2');
var spawn = require('win-spawn');
var tempWrite = require('temp-write');
var dargs = require('dargs');
var shelljs = require('shelljs');
var chalk = require('chalk');

module.exports = function (options) {
	options = options || {};
	var passedArgs = dargs(options, ['bundleExec']);
	var bundleExec = options.bundleExec;
	var versionCommand = bundleExec === true ? 'bundle exec jekyll -v' : 'jekyll -v';

	try {
		shelljs.exec(versionCommand, {silent: true});
	}
	catch (err) {
		throw new gutil.PluginError('gulp-ruby-jekyll', 'You need to have Ruby and Jekyll installed and in your PATH for this task to work.');
	}

	// NOTE: This looks at single files -- we need to look at the dir as a whole.
	// NOTE: Should source matter / be applied to the input glob? No, let's just
	// say that stream is source, output is dest

	return through.obj(function (file, enc, cb) {
		var self = this;
		// var fileDirname = file.base;

		if (file.isNull()) {
			this.push(file);
			return cb();
		}

		if (file.isStream()) {
			this.emit('error', new gutil.PluginError('gulp-ruby-jekyll', 'Streaming not supported'));
			return cb();
		}

		// NOTE: inputTempFile is a single file based idea.
		tempWrite(file.contents, path.basename(file.path), function (err, inputTempFile) {
			if (err) {
				self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				return cb();
			}

			// NOTE: set up real args. Let people pass anything except --dest && --source
			var args = [
				// 'sass',
				// inputTempFile,
				// outputTempFile,
				// '--load-path', fileDirname
			].concat(passedArgs);

			if (bundleExec) {
				args.unshift('bundle', 'exec');
			}

			// if we're compiling SCSS or CSS files
			// if (path.extname(file.path) === '.css') {
			// 	args.push('--scss');
			// }

			var cmd = args.shift();
			var cp = spawn(cmd, args);

			// NOTE: Will need this.
			// if (process.argv.indexOf('--verbose') !== -1) {
			// 	gutil.log('gulp-ruby-jekyll:', 'Running command:', cmd, chalk.blue(args.join(' ')));
			// }

			cp.on('error', function (err) {
				// TODO: remove when gulp has fixed error handling
				gutil.log('[gulp-ruby-jekyll] ' + err);

				self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				return cb();
			});

			// NOTE: Setting up stderr output
			var errors = '';
			cp.stderr.setEncoding('utf8');
			cp.stderr.on('data', function (data) {
				// ignore deprecation and empty warnings
				// if (/DEPRECATION WARNING/.test(data) || data.trim() === '') {
				// 	return;
				// }

				errors += data;
			});

			cp.on('close', function (code) {
				if (errors) {
					// TODO: remove when gulp has fixed error handling
					// NOTE: Probably need to rewrite these two
					// gutil.log('[gulp-ruby-jekyll]', '\n' + errors.replace(inputTempFile, file.path).replace('Use --trace for backtrace.\n', ''));

					// self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', '\n' + errors.replace(inputTempFile, file.path).replace('Use --trace for backtrace.\n', '')));
					return cb();
				}

				if (code > 0) {
					self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', 'Exited with error code ' + code));
					return cb();
				}

				// NOTE: Pull in those new files
				// NOTE: Once again, doing single files here, got to move to dirs
				// fs.readFile(outputTempFile, function (err, data) {
				// 	if (err) {
				// 		self.emit('error', new gutil.PluginError('gulp-ruby-jekyll', err));
				// 		return cb();
				// 	}

				// 	self.push(new gutil.File({
				// 		base: fileDirname,
				// 		contents: data
				// 	}));

				// NOTE: final callback
				// 	cb();
				// });
			});
		});
	});
};
<|endoftext|>