/*jslint node: true, nomen: true, stupid: true */
"use strict";

/**
 * @module 'hapi-locale'
 * @description
 * Configurable plugin for determine request language in hapi.js applications.
 */
const Boom = require('boom'),
    fs = require('fs'),
    path = require('path'),
    lodash = require('lodash'),
    headerParser = require('accept-language-parser'),
    Joi = require('joi'),
    pkg = require("../package");

var rootDir = path.join(__dirname, '../../..');
var locales = [];

/**
 * @typedef {Object}                    PluginOptions                   - Plugin configuration options.
 * @property {Array.<string>}           [locales=[]]                    - List of locales to use in application.
 * @property {string|null}              [default=1st Locale]            - Default locale to use if no locale is given.
 * @property {string|null}              [configFile=package.json]       - Configuration file to get available locales.
 * @property {string|null}              [configKey=locales]             - Key to look in configuration file to get available locales. May be nested key such as 'a.b.c'.
 * @property {Object}                   [scan]                          - Scanning options to get available locales
 * @property {string}                   [scan.path=locale]              - Path or paths to scan locale files to get available locales.
 * @property {string}                   [scan.fileTypes=json]           - File types to scan. ie. "json" for en_US.json, tr_TR.json
 * @property {boolean}                  [scan.directories=true]         - whether to scan directory names to get available locales.
 * @property {Array.<string>}           [scan.exclude=[templates]]      - Directory or file names to exclude from scan results.
 * @property {string|null}              [param=lang]                    - Name of the path parameter to determine language. ie. /{lang}/account
 * @property {string|null}              [query=lang]                    - Name of the query parameter to determine language. ie. /account?lang=tr_TR
 * @property {string|null}              [cookie=lang]                   - Name of the cookie to determine language.
 * @property {string|null}              [cookieKey=lang]                - Name of the key to look inside cookie to determine language. May be nested key such as 'a.b.c'.
 * @property {string|null}              [header=accept-language]        - Name of the header parameter to determine language.
 * @property {Array.<string>}           [order=['params', 'cookie', 'query', 'headers']] - Order in which language determination process follows. First successful method returns requested language.
 * @property {boolean}                  [throw404=true]                 - Whether to throw 404 not found if locale is not found. Does not apply path parameters, it always throws 404.
 * @property {string|null}              [getter=i18n.getLocale]         - Getter method in request object to get current locale. May be nested object such as 'a.b.c'
 * @property {string|null}              [setter=i18n.setLocale]         - Setter method in request object to set current locale. May be nested object such as 'a.b.c'
 * @property {string|null}              [attribute=i18n.locale]         - Key in request object which will be used to store locale name. May be nested path such as 'a.b.c'.
 * @property {boolean}                  [createAccessors=true]          - Enables creating getter and setter methods in request object.
 * @property {string}                   [onEvent=onPreAuth]             - Event on which locale determination process is fired.
 */

/**
 * @type {PluginOptions}
 * @private
 */
var defaultOptions = {
    locales: [],
    configFile: path.join(rootDir, 'package.json'),
    configKey: 'locales',
    scan: {
        path: path.join(rootDir, 'locales'),
        fileType: 'json',
        directories: true,
        exclude: ['templates', 'template.json']
    },
    param: 'lang',
    query: 'lang',
    cookie: 'lang',
    cookieKey: 'lang',
    header: 'accept-language',
    order: ['params', 'cookie', 'query', 'headers'],
    throw404: true,
    getter: 'i18n.getLocale',
    setter: 'i18n.setLocale',
    attribute: 'i18n.locale',
    createAccessors: true,
    onEvent: 'onPreAuth'
};

var orderParameters = {
    // Process in options.order array and JS method which will be called for that process.
    params: 'parseParam',
    query: 'parseQuery',
    headers: 'parseHeader',
    cookie: 'parseCookie'
};

var optionsSchema = {
    locales: Joi.array().items(Joi.string()).default(defaultOptions.locales),
    default: Joi.string().allow(null).default(defaultOptions.default),
    configFile: Joi.string().allow(null).default(defaultOptions.configFile),
    configKey: Joi.string().allow(null).default(defaultOptions.configKey),
    scan: Joi.object({
        path: Joi.string().default(defaultOptions.scan.path),
        fileType: Joi.string().default(defaultOptions.scan.fileType),
        directories: Joi.boolean().default(defaultOptions.scan.directories),
        exclude: Joi.array().items(Joi.string()).allow(null).default(defaultOptions.scan.exclude)
    }).allow(null).default(defaultOptions.scan),
    param: Joi.string().allow(null).default(defaultOptions.param),
    query: Joi.string().allow(null).default(defaultOptions.query),
    cookie: Joi.string().allow(null).default(defaultOptions.cookie),
    cookieKey: Joi.string().allow(null).default(defaultOptions.cookieKey),
    header: Joi.string().allow(null).default(defaultOptions.header),
    order: Joi.array().items(Joi.any().valid(Object.keys(orderParameters))).default(defaultOptions.order),
    throw404: Joi.boolean().default(defaultOptions.throw404),
    getter: Joi.string().allow(null).default(defaultOptions.getter),
    setter: Joi.string().allow(null).default(defaultOptions.setter),
    attribute: Joi.string().allow(null).default(defaultOptions.attribute),
    createAccessors: Joi.string().allow(null).default(defaultOptions.createAccessors),
    onEvent: Joi.string().default(defaultOptions.onEvent)
};

/**
 * Class to implement inner working of plugin.
 * @param {PluginOptions} options     - Plugin configuration options.
 * @constructor
 * @private
 */
var Internal = function (options) {
    "use strict";
    if ((options.setter && options.setter.indexOf('.') > -1) || (options.getter && options.getter.indexOf('.') > -1)) {
        throw new Error('Getter (' + options.getter + ') and setter (' + options.setter + ') methods cannot be nested, so they cannot contain dot(.)');
    }

    this.options = Joi.attempt(options, optionsSchema);
    this.locales = this.getAvailableLocales();
    this.default = this.options.default || this.locales[0];
    //this.callback   = this.getCallback(this.options.callback);
};

/**
 * Returns requested languages as an array by looking url part.
 * @param {Object}          request     - Hapi request object.
 * @returns {string|undefined}          - Requested locale or undefined.
 * @private
 */
Internal.prototype.parseParam = function parseParam(request) {
    "use strict";
    if (!request.params.hasOwnProperty(this.options.param)) return;
    var name = this.options.param,
        locales = lodash.get(request.params, name),
        match = this.bestMatch(locales);

    if (!match && this.options.throw404) {
        throw new Error('Requested locale/language ' + locales + ' cannot be found.');
    }

    return match;
};

/**
 * Returns requested languages as an array by looking query parameter.
 * @param {Object}          request     - Hapi request object.
 * @returns {string|undefined}          - Requested locale or undefined.
 * @private
 */
Internal.prototype.parseQuery = function parseQuery(request) {
    "use strict";
    var name = this.options.query,
        locales = lodash.get(request.query, name);

    return this.bestMatch(locales);
};

/**
 * Returns requested language from cookie if found in available languages.
 * @param {Object}          request     - Hapi request object.
 * @returns {string|undefined}          - Requested locale or undefined.
 * @private
 */
Internal.prototype.parseCookie = function parseCookie(request) {
    "use strict";
    var name = this.options.cookie,
        key = this.options.cookieKey,
        locales = lodash.get(request.state[name], key);

    return this.bestMatch(locales);
};

/**
 * Returns requested language from header if found in available languages.
 * @param {Object}          request     - Hapi request object
 * @returns {string|undefined}          - Requested locale or undefined.
 * @private
 */
Internal.prototype.parseHeader = function parseHeader(request) {
    var name = this.options.header,
        raw = headerParser.parse(request.headers[name]),
        locales = raw.map(function (value) {
            return value.region ? value.code + '_' + value.region : value.code;
        });

    return this.bestMatch(locales);
};

/**
 * Returns best match for requested locale among available locales. First matched locale will be returned.
 * @param {string|Array.<string>} requested     - Requested locale or list of requested locales.
 * @returns {string|undefined}                  - Matched locale or null if not any match found.
 * @private
 */
Internal.prototype.bestMatch = function bestMatch(requested) {
    if (!requested) return;
    if (!Array.isArray(requested)) requested = [requested];

    for (let one of requested) {
        if (this.locales.indexOf(one) > -1) return one;
    }
};

/**
 * Checks synchroniously if given file or directory exists. Returns true or false.
 * @param {string}  path        - Path of the file or directory.
 * @param {boolean} shouldBeDir - Whether given path should be directory.
 * @returns {boolean}
 * @private
 */
function fileExists(path, shouldBeDir) {
    "use strict";
    try {
        var lstat = fs.lstatSync(path);
        if (shouldBeDir && lstat.isDirectory()) {
            return true;
        }
        if (!shouldBeDir && lstat.isFile()) {
            return true;
        }
    } catch (err) {
        return false;
    }
    return false;
}

/**
 * Scans path in options.scan.path and returns list of available locale files.
 * @returns {Array.<string>}
 * @throws {Error} - Throws error if locales directory is not found.
 * @private
 */
Internal.prototype.scan = function scan() {
    // Check if scan path is available
    if (this.options.scan && !fileExists(this.options.scan.path, true)) {
        throw new Error('Locales directory "' + this.options.scan.path + '"cannot be found.');
    }

    let dir = this.options.scan.path,
        files = fs.readdirSync(dir),
        locales = [];

    for (let file of files) {
        let fullPath = path.join(dir, file);

        // Skip if it is in exclude list or it is directory and scan.directories is false
        if (this.options.scan.exclude.indexOf(file) > -1 || (fs.statSync(fullPath).isDirectory() && !this.options.scan.directories)) {
            continue;
        }

        locales.push(path.basename(file, path.extname(file)));  // Strip extension such as .json
    }

    return lodash.uniq(locales);
};

/**
 * Determines which locales are available. It tries to determine available locales in given order:
 * 1. Returns if locales are present in options.locales.
 * 2. If not found, looks for given config file and searches opted key in config file.
 * 3. If not found, scans path given in options.scan.path for files and directories excluding files in options.scan.exclude.
 * @returns {Array|null}    - List of available locales
 * @throws {Error}          - Throws error if necessary files are not found or no locales are available.
 * @private
 */
Internal.prototype.getAvailableLocales = function getAvailableLocales() {
    "use strict";
    let locales = [];

    if (Array.isArray(this.options.locales) && this.options.locales.length > 0) {
        locales = this.options.locales;
        if (!Array.isArray(locales)) locales = [];
    }

    // Config file
    if (locales.length === 0 && this.options.configFile) {

        // Check if config file is available
        if (!fileExists(this.options.configFile, false)) {
            throw new Error('Configuration file "' + this.options.configFile + '" cannot be found');
        }

        locales = lodash.get(require(this.options.configFile), this.options.configKey);       // key chain string to reference: 'options.locale' => options.locale
        if (!Array.isArray(locales)) locales = [];
    }

    // Locale files
    if (locales.length === 0 && this.options.scan.path) {
        locales = this.scan();
    }

    if (locales.length === 0) {
        throw new Error('Cannot found any locale.')
    }

    return lodash.uniq(locales);
};

/**
 * @param {Object}              request - hapi.js request object
 * @returns {string|undefined}          - Locale
 * @private
 */
Internal.prototype.determineLocale = function determineLocale(request) {
    let requestedLocale;

    for (let method of this.options.order) {
        requestedLocale = this[orderParameters[method]](request);       // this.parseParam | this.parseCookie ... etc.
        if (requestedLocale) break;
    }

    return requestedLocale || this.default;
};

/**
 *
 * @param {Object}              request - hapi.js request object
 * @param {Function}            reply   - hapi.js reply object
 * @returns {*}
 */
Internal.prototype.processRequest = function processRequest(request, h) {
    "use strict";
    try {
        var locale = this.determineLocale(request);
    } catch (err) {
        throw Boom.notFound(err);
    }

    let getter = this.options.getter,
        setter = this.options.setter,
        attribute = this.options.attribute;

    // Create accessors if necessary
    if (this.options.createAccessors) {
        if (!lodash.get(request, getter)) {
            lodash.set(request, getter, function () {
                return lodash.get(request, attribute);
            });
        }
        if (!lodash.get(request, setter)) {
            lodash.set(request, setter, function (locale) {
                return lodash.set(request, attribute, locale);
            });
        }
    }

    // Call setter.
    lodash.get(request, setter)(locale);

    return h.continue;
};

exports.plugin = {
    name: pkg.name,
    version: pkg.version,
    pkg: pkg,
    once: true,
    multiple: false,
    /**
     * Hapi plugin function which adds i18n support to request and response objects.
     * @param {Object}          server      - Hapi server object
     * @param {PluginOptions}   options     - Plugin configuration options.
     */
    register: async function (server, options) {
        try {
            var internal = new Internal(options);
        } catch (err) {
            throw new Boom(err);
        }

        /**
         * @module exposed
         * @description
         * Exposed functions and attributes are listed under exposed name.
         * To access those attributes `request.server.plugins['hapi-locale']` can be used.
         * @example
         * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.
         */

        /**
         * Returns all available locales as an array.
         * @name getLocales
         * @function
         * @returns {Array.<string>}    - Array of locales.
         * @example
         * var locales = request.server.plugins['hapi-locale'].getLocales(); // ['tr_TR', 'en_US'] etc.
         */
        server.expose('getLocales', function getLocales() {
            return internal.locales;
        });

        /**
         * Returns default locale.
         * @name getDefaultLocale
         * @function
         * @returns {string}    - Default locale
         */
        server.expose('getDefaultLocale', function getDefaultLocale() {
            return internal.default;
        });

        /**
         * Returns requested language.
         * @name getLocale
         * @function
         * @param {Object}      request - Hapi.js request object
         * @returns {string}    Locale
         */
        server.expose('getLocale', function getLocale(request) {
            try {
                return lodash.get(request, internal.options.getter)();
            } catch (err) {
                return null;
            }
        });

        server.ext(internal.options.onEvent, internal.processRequest, {bind: internal});
    }
};


var mdeps = require('module-deps')
  , cycle = require('./cycle')
  , inspect = require('util').inspect
  , topiary = require('topiary');

// resolve non-core dependencies
var coreModules = ['assert', 'buffer', 'child_process', 'cluster',
  'console', 'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net',
  'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
  'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib'];

var isNotBuiltin = function (id) {
  return coreModules.indexOf(id) < 0;
};
var isNotCoverage = function (id) {
  return !(/\/\w*\-cov\//).test(id);
};

var isScannedForDeps = function (id) {
  return isNotBuiltin(id) && isNotCoverage(id);
};

var notCoveredInArray = function (ary, name) {
  return ary.every(function (d) {
    return d.name !== name;
  });
};

exports.analyze = function (file, name, cb, opts) {
  opts = opts || {};

  var showInTree = function (o) {
    return (opts.showBuiltins || isNotBuiltin(o.name)) && isNotCoverage(o.name);
  };
  var isActuallyScanned = function (id) {
    return isScannedForDeps(id) && (opts.ignored || []).indexOf(id) < 0;
  };

  var failed = false;
  var lookup = {};
  var md = mdeps({
    filter: isActuallyScanned,
    ignoreMissing: opts.ignoreMissing
  });
  md.on('error', function (err) {
    if (!failed) {
      failed = true;
      cb(err);
    }
  });
  md.on('data', function (o) {
    // o is object with `id` and `deps` = { relReq : id }
    lookup[o.id] = Object.keys(o.deps).map(function (key) {
      return { name: key, path: o.deps[key] };
    });
  });
  md.on('end', function () {
    if (failed) {
      return;
    }
    var cycleData = cycle.trimWhileCycles(lookup);
    if (opts.showCycles) {
      return cb(null, inspect(cycleData.cycles));
    }

    var labeler = function (o) {
      if (o.path && cycleData.removed[o.path]) {
        return o.name + " ↪ " + cycleData.removed[o.path];
      }
      return o.name;
    };

    // build a dependency tree from the flat mdeps list by recursing
    var traverse = function (currDeps, loc) {
      loc.deps = loc.deps || [];
      var covered = []; // only cover unique paths once to avoid stack overflow
      currDeps.forEach(function (obj) {
        if (covered.indexOf(obj.path) < 0) {
          covered.push(obj.path); // cover unique paths only once per level

          var key = obj.name
            , isRelative = (['\\', '/', '.'].indexOf(key[0]) >= 0)
            , notCovered = notCoveredInArray(loc.deps, key)
            , isRecorded = (!isRelative || opts.showLocal) && notCovered
            , res = isRecorded ? { name: key } : loc;

          if (isRecorded) {
            // NB: !isRecorded => only inspect the file for recorded deps
            res.path = obj.path;
            loc.deps.push(res);
          }

          // recurse (!isRecorded => keep adding to previous location)
          traverse(lookup[obj.path] || [], res);
        }
      });
    };
    var tree = { name: name, path: file };
    traverse(lookup[file], tree);

    cb(null, topiary(tree, 'deps', {
      filter: showInTree,
      sort: true,
      name: labeler
    }));
  });
  md.end(file);
};

exports.cycle = cycle;


var ssc = require('strongly-connected-components');

var createAdjacencyArrays = function (ms) {
  var indexed = []; // index -> unique id map
  Object.keys(ms).forEach(function (key, i) {
    indexed[i] = key; // what's the unique id of index i
  });
  var adjacency = []; // index -> [adjacent indexes]
  Object.keys(ms).forEach(function (key, i) {
    adjacency[i] = ms[key].filter(function (o) {
      return o.path; // only care about cyclicals in code we can scan (not builtins)
    }).map(function (o) {
      // now leave only a map o
      var idx = indexed.indexOf(o.path);
      if (idx < 0) {
        throw new Error("logic fail");
      }
      return idx;
    });
  });

  return { indexes: indexed, adjacency: adjacency };
};

exports.detect = function (ms) {
  var arys = createAdjacencyArrays(ms);
  var comps = ssc(arys.adjacency).components;
  arys.cycles = comps.filter(function (c) {
    return c.length > 1; // length 1 components have nothing referring back to it :]
  }).map(function (c) {
    return c.map(function (i) {
      return arys.indexes[i]; // convert back to unique ids
    });
  });
  return arys;
};

// given a modules array with cycles - trim dependencies in each cycle
var trimList = function (ms, cycles) {
  var removed = {};

  cycles.forEach(function (c) {
    var last = c[c.length-1]; // last id in cycle
    //console.log('will try to trim from', last, ms[last]);

    // need to find a dependency in the cycle
    var depsInCycle = ms[last].filter(function (deps) {
      return deps.path && c.indexOf(deps.path) >= 0;
    });
    if (!depsInCycle.length) {
      throw new Error("logic fail2"); // last thing in a cycle should have deps
    }
    var depToRemove = depsInCycle[0].path;
    //console.log('deps in cycle', depsInCycle);

    for (var i = 0; i < ms[last].length; i += 1) {
      var dep = ms[last][i];
      if (dep.path && dep.path === depToRemove) {
        //console.log('removing', depToRemove);
        removed[last] = dep.name;
        ms[last].splice(i, 1);
      }
    }
    //console.log('after remove', ms[last]);
  });

  return removed;
};

// modifies input array
exports.trimWhileCycles = function (ms) {
  var output = exports.detect(ms);
  var cycleData = {
    cycles: output.cycles.slice(), // original cycles found
    removed: {}
  };

  var appendToRemoved = function (rm) {
    Object.keys(rm).forEach(function (key) {
      cycleData.removed[key] = rm[key];
    });
  };

  output = exports.detect(ms);
  while (output.cycles.length) {
    var removed = trimList(ms, output.cycles);
    output = exports.detect(ms); // recompute cycles
    appendToRemoved(removed);
  }

  return cycleData;
};


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @typedef SchemaMessage
 * @type Object
 */

const constants = require('./constants')
const wrapper = require('./fetch')

/**
 * @module @lobsangnet/lobsang-formatter-matrix/schema
 */

/**
 * Library to get a schema for the given topic.
 *
 * @private
 * @param {string} topic - The topic for which a schema should be fetched.
 * @returns {Promise<object|Error>}
 *   Promise which resolves in a schema object.
 *   Or an Error if the topic is not supported or fetch failed.
 * @todo Replace object after https://github.com/Kegsay/flow-jsdoc/issues/22
 */
function schema (topic) {
  if (topic === constants.LOBSANG_CONTENT_TOPIC) {
    return wrapper.fetch(constants.LOBSANG_CONTENT_SCHEMA_URL)
  }
  return Promise.reject(new Error('Topic is not supported'))
}

module.exports = schema


var assert = require('assert');
var _ = require('lodash');
var debug = require('debug')('base:series');

/** Types supported for reporting */
var types = {
  String: function(v) { return /string/.test(typeof(v)); },
  Number: function(v) { return /number/.test(typeof(v)); },
  Any:    function(v) { return /string|number/.test(typeof(v)); }
};

exports.types = types;

/**
 * Create a times series that you can report to.
 *
 * options:
 * {
 *   // Name of the series in influxdb
 *   name:               'ResponseTimes',
 *
 *   // Required columns
 *   columns: {
 *     method:           stats.types.String,
 *     duration:         stats.types.Number,
 *     custom:           stats.types.Any
 *   },
 *
 *   // Type of additional columns if they are allowed (defaults `null`)
 *   additionalColumns:  stats.types.String
 * }
 */
var Series = function(options) {
  // Validate options
  assert(options,         "options are required");
  assert(options.name,    "Series must be named");
  options = _.defaults({}, options, {
    columns:              {},
    additionalColumns:    null
  });

  // Store options
  this._options = options;
};

exports.Series = Series;

/** Returns list of columns */
Series.prototype.columns = function() {
  return _.keys(this._options.columns);
};

/**
 * Create a reporter function that will validate points and submit them to the
 * drain, while always setting columns specified in tags. Note that values
 * specified in `tags` maybe overwritten by individual points.
 *
 * Example tags:
 * ```js
 * MySeries.reporter(drain, {
 *   component: 'queue',
 *   process: 'web'
 * })
 * ```
 */
Series.prototype.reporter = function(drain, tags) {
  var options = this._options;

  // Validate tags
  _.forIn(tags, function(value, key) {
    var validate = options.columns[key] || options.additionalColumns;
    if (!validate) {
      debug("additionalColumn %s not allowed in series: %s",
            key, options.name);
      throw new Error("additionalColumn " + key + " is not allowed!");
    }
    if (!validate(value)) {
      debug("Failed validate %s for key %s in series: %s",
            value, key, options.name);
      throw new Error("Failed validate key " + key + " in series: " +
                      options.name);
    }
  });

  // Return a reporter
  return function(point) {
    // Validate point columns
    _.forIn(point, function(value, key) {
      var validate = options.columns[key] || options.additionalColumns;
      if (!validate) {
        debug("additionalColumn %s not allowed in series: %s",
              key, options.name);
        throw new Error("additionalColumn " + key + " is not allowed!");
      }
      if (!validate(value)) {
        debug("Failed validate %s for key %s in series: %s",
              value, key, options.name);
        throw new Error("Failed validate key " + key + " in series: " +
                        options.name);
      }
    });

    // If validated we'll add the point to the drain
    drain.addPoint(options.name, _.defaults({}, point, tags));
  };
};

/** Collection of statistics series used in taskcluster-base */

/** Usage reports format for stats.monitorProcessUsage */
exports.UsageReports = new Series({
  name:               'UsageReports',
  columns: {
    component:        types.String,
    process:          types.String,
    cpu:              types.Number,
    memory:           types.Number
  }
});

/** Structure for API response times series */
exports.ResponseTimes = new Series({
  name:               'ResponseTimes',
  columns: {
    duration:         types.Number,
    statusCode:       types.Number,
    requestMethod:    types.String,
    method:           types.String,
    component:        types.String
  },
  // Additional columns are req.params prefixed with "param", these should all
  // be strings
  additionalColumns:  types.String
});

/** Exchange reports for statistics */
exports.ExchangeReports = new Series({
  name:               'ExchangeReports',
  columns: {
    component:        types.String, // Component name (e.g. 'queue')
    process:          types.String, // Process name (e.g. 'server')
    duration:         types.Number, // Time it took to send the message
    routingKeys:      types.Number, // 1 + number CCed routing keys
    payloadSize:      types.Number, // Size of message bytes
    exchange:         types.String, // true || false
    error:            types.String  // true || false
  }
});

/** Statistics from Azure table operations */
exports.AzureTableOperations = new Series({
  name:             'AzureTableOperations',
  columns: {
    component:        types.String,
    process:          types.String,
    duration:         types.Number,
    table:            types.String,
    method:           types.String,
    error:            types.String
  }
});

/** Statistics from TaskCluster Client stats callback */
exports.APIClientCalls = new Series({
  name:               'APIClientCalls ',
  columns: {
    duration:         types.Number, // Duration of call in ms including retries
    retries:          types.Number, // Number of retries
    method:           types.String, // Name of method (signature)
    success:          types.Number, // 1 or 0
    resolution:       types.String, // http-<status> or err.code
    target:           types.String, // Queue, Index (class name if available)
    baseUrl:          types.String  // BaseUrl
  },
  additionalColumns:  types.String  // workerType, workerId, component, process
});

/** Handler reports format for stats.createHandlerTimer */
exports.HandlerReports = new Series({
  name:               'HandlerReports',
  columns: {
    component:        types.String,
    duration:         types.Number,
    exchange:         types.String,
    redelivered:      types.String,   // true || false
    error:            types.String    // true || false
  }
});


import Symbol from 'es6-symbol'
import getCompiler from 'cerebral-url-scheme-compiler/get'
import toDisplayName from './helpers/toDisplayName'
const truthy = Symbol('truthy')
const falsy = Symbol('falsy')
const otherwise = Symbol('otherwise')

function when (path, conditions = { 'true': truthy, 'false': otherwise }) {
  const getValue = getCompiler(path)

  // prepare the output conditions
  let otherwisePath = null
  const outputConditions = {}
  if (Array.isArray(conditions)) {
    conditions.forEach((condition) => {
      outputConditions[condition] = condition
    })
  } else {
    for (let path in conditions) {
      outputConditions[path] = conditions[path]
      otherwisePath = otherwisePath || (conditions[path] === otherwise && path)
    }
  }
  if (!otherwisePath) {
    outputConditions['otherwise'] = otherwise
    otherwisePath = 'otherwise'
  }

  // test the getter returned value
  const whenTest = (args, value) => {
    // treat objects with no keys as falsy
    if (value && typeof value === 'object' && Object.keys(value).length === 0) {
      value = false
    }

    let outputPath

    for (let path in outputConditions) {
      let test = outputConditions[path]
      if (test !== otherwise &&
        ((test === value) || (test === truthy && value) || (test === falsy && !value))) {
        outputPath = path
        break
      }
    }

    args.output[outputPath || otherwisePath]()
  }

  // define the action
  let action = function whenRead (args) {
    let value = getValue(args)
    if (value && typeof value.then === 'function') {
      value.then((val) => whenTest(args, val)).catch((error) => {
        console.error(`${action.displayName} caught an error whilst getting a value to test`, error)
      })
    } else {
      whenTest(args, value)
    }
  }

  action.outputs = Object.keys(outputConditions)

  action.displayName = `operators.when(${toDisplayName(path, getValue)})`

  return action
}

when.truthy = truthy
when.falsy = falsy
when.otherwise = otherwise

export default when


"use strict";
/**
 * Tools and utils needed everywhere
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const deep_equal_1 = __importDefault(require("deep-equal"));
/**
 * [[include: guid.md]]
 * @returns {string} GUIDv4 string
 */
function GUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
        let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}
exports.GUID = GUID;
/**
 * @ignore
 * @private
 */
function _testConstructor(constructorName, value) {
    return !isUndefined(value) && !isNull(value) && value.constructor && value.constructor.name === constructorName;
}
/**
 * Is value an *AsyncFunction*
 */
function isAsyncFunction(value) {
    if (!value)
        return false;
    const afcText = value.toString().toLocaleLowerCase().replace(/\n/g, '').replace(/ /g, '');
    return _testConstructor('AsyncFunction', value)
        || ((_testConstructor('Function', value) && (afcText.slice(afcText.indexOf('{')).indexOf('returnnewpromise(function($return,$error)') === 1))); //fast-async monkey-support
}
exports.isAsyncFunction = isAsyncFunction;
/**
 * Is value a *Function*
 */
function isFunction(value) {
    return _testConstructor('Function', value) || isAsyncFunction(value);
}
exports.isFunction = isFunction;
/**
 * Is value an *undefined*
 */
function isUndefined(value) {
    return typeof value === 'undefined';
}
exports.isUndefined = isUndefined;
/**
 * Is value an *Array*
 */
function isArray(value) {
    return _testConstructor('Array', value);
}
exports.isArray = isArray;
/**
 * Is value an *object*
 */
function isObject(value) {
    return ({}.toString.call(value)) === '[object Object]';
}
exports.isObject = isObject;
/**
 * Is value a Native *Object*
 */
function isNativeObject(value) {
    return _testConstructor('Object', value);
}
exports.isNativeObject = isNativeObject;
/**
 * Is value a *string*
 */
function isString(value) {
    return _testConstructor('String', value);
}
exports.isString = isString;
/**
 * Is value a *null*
 */
function isNull(value) {
    return Object.prototype.toString.call(value) === '[object Null]';
}
exports.isNull = isNull;
/**
 * Is value a *boolean*
 */
function isBoolean(value) {
    return Object.prototype.toString.call(value) === '[object Boolean]';
}
exports.isBoolean = isBoolean;
/**
 * Is value a *number*
 */
function isNumber(value) {
    return Number.isFinite(value);
}
exports.isNumber = isNumber;
/**
 * Is value an Integer *number*
 */
function isInteger(value) {
    return Number.isSafeInteger(value);
}
exports.isInteger = isInteger;
/**
 * [[include: is-positive-integer.md]]
 */
function isPositiveInteger(value) {
    return isInteger(value) && value >= 0;
}
exports.isPositiveInteger = isPositiveInteger;
/**
 * Is value iterable (*object* or *Array* with not zero length)
 */
function isIterable(value) {
    return (isObject(value) ? !!Object.keys(value).length : false) || (isArray(value) ? !!value.length : false);
}
exports.isIterable = isIterable;
/**
 * Is value a JSON and can be decoded as *object*
 */
function isJSON(value) {
    if (!isString(value))
        return false;
    try {
        const obj = JSON.parse(value);
        return !!obj && typeof obj === 'object';
    }
    catch (e) {
    }
    return false;
}
exports.isJSON = isJSON;
/**
 * Is value a valid GUIDv4 *string*
 */
function isGUIDv4(value) {
    return isString(value) && /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(value);
}
exports.isGUIDv4 = isGUIDv4;
/**
 * Is value a valid GUID *string*
 */
function isGUID(value) {
    return isString(value) && /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i.test(value);
}
exports.isGUID = isGUID;
/**
 * [[include: to-TAnyArray.md]]
 */
function toArray(value) {
    return isArray(value) ? value : [value];
}
exports.toArray = toArray;
/**
 * [[include: TAnyArray-to-object.md]]
 * @param value
 * @param toKeys
 */
function arrayToObject(value, toKeys) {
    return (iterate(value, (row, idx, iter) => {
        if (toKeys) {
            if (isInteger(row) || isString(row))
                iter.key(idx + 1);
            return row;
        }
        else {
            iter.key(row);
            return idx + 1;
        }
    }, {}));
}
exports.arrayToObject = arrayToObject;
/**
 * [[include: object-to-TAnyArray.md]]
 */
function objectToArray(value, toKeys) {
    return iterate(value, (val, key) => toKeys ? key : val, []);
}
exports.objectToArray = objectToArray;
/**
 * Sync void empty function (No Operation)
 */
function nop() {
}
exports.nop = nop;
/**
 * Async void empty function (No Operation Async)
 */
async function nop$() {
}
exports.nop$ = nop$;
/**
 * [[include: set-defaults.md]]
 */
function setDefaults(obj, name, value) {
    if (isUndefined(obj[name])) {
        obj[name] = value;
    }
    return obj;
}
exports.setDefaults = setDefaults;
/**
 * @deprecated
 * Escapes all special RegExp characters
 */
function escapeRegExp(value) {
    return value.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
exports.escapeRegExp = escapeRegExp;
/**
 * Waits for ms via async/await
 * @param ms time to wait or 100
 */
async function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms || 100));
}
exports.delay = delay;
exports.sleep = delay;
/**
 * @deprecated
 * @ignore
 */
function log(...msg) {
    console.log(...msg);
}
exports.log = log;
/**
 * [[include: iterate.md]]
 *
 * @param value
 * @param callback
 * @param accumulate
 * @param assign
 */
function iterate(value, callback, accumulate, assign) {
    let breakFlag = false;
    function newIteration(index) {
        let instance = {
            'break': () => breakFlag = true,
            accKeyName: index,
            key: (name) => instance.accKeyName = name
        };
        return instance;
    }
    let iterateInstanceAsync = async (callback, val, index) => {
        let iteration = newIteration(index);
        pushRet(await callback(val, index, iteration), iteration);
    };
    let iterateInstance = (callback, val, index) => {
        let iteration = newIteration(index);
        pushRet(callback(val, index, iteration), iteration);
    };
    let ret = isObject(accumulate) ? accumulate : isArray(accumulate) ? accumulate : accumulate === true ? false : value;
    let pushRet = (val, iteration) => {
        if (isUndefined(val))
            return;
        if (isObject(accumulate)) {
            ret[iteration.accKeyName] = assign
                ? Object.assign(ret[iteration.accKeyName] || {}, val)
                : val;
        }
        if (isArray(accumulate))
            ret.push(val);
        if (accumulate === true)
            ret = ret || val;
    };
    return isAsyncFunction(callback)
        ? new Promise(async (resolve) => {
            if (isArray(value)) {
                for (let index = 0; index < value.length; ++index) {
                    if (breakFlag)
                        break;
                    await iterateInstanceAsync(callback, value[index], index);
                }
                resolve(ret);
            }
            if (isObject(value)) {
                await iterate(Object.keys(value), async (index, _, iteration) => {
                    if (breakFlag)
                        iteration.break();
                    await iterateInstanceAsync(callback, value[index], index);
                });
                resolve(ret);
            }
            if (isInteger(value)) {
                for (let index = 0; index < value; ++index) {
                    if (breakFlag)
                        break;
                    await iterateInstanceAsync(callback, index, index);
                }
                resolve(ret);
            }
            resolve(false);
        })
        : (() => {
            if (isArray(value)) {
                for (let index = 0; index < value.length; ++index) {
                    if (breakFlag)
                        break;
                    iterateInstance(callback, value[index], index);
                }
                return ret;
            }
            if (isObject(value)) {
                iterate(Object.keys(value), (index, _, iteration) => {
                    if (breakFlag)
                        iteration.break();
                    iterateInstance(callback, value[index], index);
                });
                return ret;
            }
            if (isInteger(value)) {
                for (let index = 0; index < value; ++index) {
                    if (breakFlag)
                        break;
                    iterateInstance(callback, index, index);
                }
                return ret;
            }
            return false;
        })();
}
exports.iterate = iterate;
/**
 * [[include: iterate-keys.md]]
 * @param value
 * @param callback
 * @param accumulate
 */
function iterateKeys(value, callback, accumulate) {
    return isAsyncFunction(callback)
        ? (async () => await iterate(value, async (row, key, iteration) => await callback(key, row, iteration), accumulate))()
        : iterate(value, (row, key, iteration) => callback(key, row, iteration), accumulate);
}
exports.iterateKeys = iterateKeys;
/**
 * [[include: iterate-parallel.md]]
 * @param value
 * @param callback
 */
async function iterateParallel(value, callback) {
    return Promise.all(iterate(value, (val, key, iter) => (async () => await callback(val, key, iter))(), []));
}
exports.iterateParallel = iterateParallel;
/**
 * Finds and deletes first encounter of value in target
 * @param target
 * @param value
 * @returns true if an element has been deleted, otherwise - false
 */
function findAndDelete(target, value) {
    if (!isIterable(target))
        return false;
    if (isArray(target)) {
        for (let i = 0; i < target.length; i++) {
            if (deep_equal_1.default(target[i], value)) {
                target.splice(i, 1);
                return true;
            }
        }
    }
    else if (isObject(target)) {
        const keys = Object.keys(target);
        for (let i = 0; i < keys.length; i++) {
            if (deep_equal_1.default(target[keys[i]], value)) {
                delete target[keys[i]];
                return true;
            }
        }
    }
    return false;
}
exports.findAndDelete = findAndDelete;
/**
 * Finds and deletes all encounters of value in target
 * @param target
 * @param value
 * @returns true if anything has been deleted, otherwise - false
 */
function findAndDeleteAll(target, value) {
    let flag = false;
    while (findAndDelete(target, value)) {
        flag = true;
    }
    return flag;
}
exports.findAndDeleteAll = findAndDeleteAll;
//# sourceMappingURL=index.js.map


/*
Copyright 2017 apHarmony

This file is part of jsHarmony.

jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this package.  If not, see <http://www.gnu.org/licenses/>.
*/

var jsHarmonyAgreement = function(jsh){
  this.jsh = jsh;
}

jsHarmonyAgreement.prototype.Init = function(){
  var _this = this;
  var jsh = _this.jsh;
  var $ = jsh.$;
  var _ = jsh._;
  var moment = jsh.moment;
  var XValidate = jsh.XValidate;
  var XFormat = jsh.XFormat;
  var XForm = jsh.XForm;

  $(document).ready(function () {
    jsh.$root('.a_date').val(moment().format('MM/DD/YYYY'));

    //Sample Data
    /*jsh.$root('.a_name').val('Slim Stanowski');
    jsh.$root('.a_dob').val('9/9/1990');
    jsh.$root('.a_accept').prop('checked', true);*/

    //Set up Step 1
    _.each(XFormStep1.prototype.Fields, function (field, fieldid) {
      field.name = fieldid;
      if (!('validators' in field)) return;
      XFormStep1.prototype.xvalidate.AddControlValidator('.' + field.name, '_obj.' + field.name, field.caption, 'I', field.validators);
    });
  });

  /**************************
   * STEP 1
   * *************************/
  this.step1_init = function() { }

  this.step1_submit = function() {
    var xf = new XFormStep1();
    xf.GetValues();
    xf.xvalidate = XFormStep1.prototype.xvalidate;
    var xp = new XForm();
    xp.Data = xf;
    var valid = xp.Validate('I');
    delete xf.xvalidate;
    if (!valid) return;

    SignAgreement(xf);
  }

  function _v_IsChecked() {
    var _val = jsh.$root('.a_accept').prop('checked');
    if (!_val) return "E-signature must be checked to continue.";
    return "";
  }

  function XFormStep1() { }
  XFormStep1.prototype.Fields = {
    "a_accept": { "caption": "E-signature", "actions": "I", "validators": [XValidate._v_Required(), _v_IsChecked] },
    "a_date": { "caption": "Today's Date", "actions": "I", "validators": [XValidate._v_Required(), XValidate._v_MaxLength(10), XValidate._v_IsDate()] },
    "a_name": { "caption": "Signed Name", "actions": "I", "validators": [XValidate._v_Required(), XValidate._v_MaxLength(72)] },
    "a_dob": { "caption": "Date of Birth", "actions": "I", "validators": [XValidate._v_Required(), XValidate._v_MaxLength(10), XValidate._v_IsDate(), XValidate._v_MaxAge(150), XValidate._v_MinAge(18)] }
  }
  XFormStep1.prototype.GetValues = function () {
    var _this = this;
    _.each(this.Fields, function (field) {
      _this[field.name] = _this.GetValue(field);
    });
  };
  XFormStep1.prototype.GetValue = function (field) {
    var val = jsh.$root('.' + field.name).val();
    if ('format' in field) {
      var format = field.format;
      if (_.isString(format)) val = XFormat[format + '_decode'](val);
      else {
        var fargs = [];
        for (var i = 1; i < format.length; i++) fargs.push(format[i]);
        fargs.push(val);
        val = XFormat[format[0] + '_decode'].apply(this, fargs);
      }
    }
    return val;
  }
  XFormStep1.prototype.xvalidate = new XValidate();

  //AppSrv Logic
  function SignAgreement(data) {
    var d = {
      'a_name': data.a_name,
      'a_dob': data.a_dob
    };
    XForm.prototype.XExecutePost('../agreement/_sign', d, function (rslt) {
      if ('_success' in rslt) {
        window.location.href = jsh._BASEURL + 'agreement/welcome/';
      }
    });
  }
}


/*globals freedom, console, self*/
/*jslint sloppy:true */

/**
 * Bind handlers on startup
 */
var theBuddylist;  // TODO: remove
function start(instance) {
  console.log('start called!');
  var chatClient = instance(),
    // If messages are going to a specific user, store that here.
    activeBuddylistEntry,
    buddylist,
    input;

  document.getElementById('msg-input').focus();

  function clearLog() {
    var log = document.getElementById('messagelist');
    log.innerHTML = "";
  }

  function appendLog(elt) {
    var log = document.getElementById('messagelist'),
      br;
    //Trim old messages
    while (log.childNodes.length > 36) {
      log.removeChild(log.firstChild);
    }
    log.appendChild(elt);
    br = document.createElement('br');
    log.appendChild(br);
    br.scrollIntoView();
  }

  function makeDisplayString(buddylistEntry) {
    return buddylistEntry.name && buddylistEntry.name !== buddylistEntry.clientId ?
        buddylistEntry.name + ' (' + buddylistEntry.clientId + ')' :
        buddylistEntry.clientId;
  }

  function redrawBuddylist() {
    var onClick = function (buddylistEntry, child) {
      console.log("Messages will be sent to: " + buddylistEntry.clientId);
      activeBuddylistEntry = buddylistEntry;
      redrawBuddylist();
      document.getElementById('msg-input').focus();
    },
      buddylistDiv = document.getElementById('buddylist'),
      clientId,
      child;

    // Remove all elements in there now
    buddylistDiv.innerHTML = "<b>Buddylist</b>";

    // Create a new element for each buddy
    for (clientId in buddylist) {
      if (buddylist.hasOwnProperty(clientId)) {
        child = document.createElement('div');
        if (activeBuddylistEntry === buddylist[clientId]) {
          child.innerHTML = "[" + makeDisplayString(buddylist[clientId]) + "]";
        } else {
          child.innerHTML = makeDisplayString(buddylist[clientId]);
        }
        // If the user clicks on a buddy, change our current destination for messages
        child.addEventListener('click', onClick.bind(this, buddylist[clientId], child), true);
        buddylistDiv.appendChild(child);
      }
    }

  }

  // on changes to the buddylist, redraw entire buddylist
  chatClient.on('recv-buddylist', function (val) {
    buddylist = val;
    theBuddylist = buddylist;
    console.log('got buddylist', buddylist);
    redrawBuddylist();
  });

  // On new messages, append it to our message log
  chatClient.on('recv-message', function (data) {
    // Show the name instead of the clientId, if it's available.
    var clientId = data.from.clientId,
      displayName = buddylist[clientId].name || clientId,
      message = displayName + ": " + data.message;
    appendLog(document.createTextNode(message));
  });

  // On new messages, append it to our message log
  chatClient.on('recv-err', function (data) {
    document.getElementById('uid').textContent = "Error: " + data.message;
  });

  // Display our own clientId when we get it
  chatClient.on('recv-uid', function (data) {
    document.getElementById('uid').textContent = "Logged in as: " + data;
  });

  // Display the current status of our connection to the Social provider
  chatClient.on('recv-status', function (msg) {
    if (msg && msg === 'online') {
      document.getElementById('msg-input').disabled = false;
    } else {
      document.getElementById('msg-input').disabled = true;
    }
    clearLog();
    var elt = document.createElement('b');
    elt.appendChild(document.createTextNode('Status: ' + msg));
    appendLog(elt);
  });

  // Listen for the enter key and send messages on return
  input = document.getElementById('msg-input');
  input.onkeydown = function (evt) {
    if (evt.keyCode === 13) {
      var text = input.value;
      input.value = "";
      appendLog(document.createTextNode("You: " + text));
      chatClient.send(activeBuddylistEntry.clientId, text);
    }
  };

  // Just call boot when login is clicked
  console.log('connecting login!');
  var loginButton = document.getElementById('uid');
  console.log('loginButton: ' + loginButton);
  loginButton.addEventListener('click', function() {
    console.log('login clicked');
    chatClient.login();
  });
}

/**
 * In Firefox, window.freedom is not defined
 * Instead, communicate with listener.js, which will forward the messages
 * to the root freedom.js module
 **/

window.onload = function (port) {
  if (typeof freedom !== 'undefined') {
    freedom('demo.json').then(start);
  } else if (typeof port !== 'undefined') { // Firefox
    port.emit('test', 'Initializing self.port');
    start(function() {
      return {
        send: function(to, msg) {
          port.emit('send', {to: to, msg: msg});
        },
        login: function() {
          port.emit('login');
        },
        logout: function() {
          port.emit('logout');
        },
        on: port.on.bind(port)
      };
    });
  } else {
    console.error("Error initializing: cannot detect environment");
  }
}.bind({}, self.port);


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const wrapper = require('./sync')

/**
 * @module @lobsangnet/lobsang-connect-matrix/connect
 */

/**
 * Connects to a MatrixClient and resolves once connection was established.
 *
 * @private
 * @param {MatrixClient} client - The client used for connection.
 * @returns {Promise<MatrixClient|Error>}
 *   Promise which resolves to a connected MatrixClient.
 *   Or an Error if connection could not be established.
 */
function connect (client) {
  client.startClient()

  return new Promise((resolve, reject) => {
    client.once('sync', (state) => {
      if (wrapper.isSyncState(state)) {
        return resolve(client)
      } else {
        return reject(new Error('Client could not sync'))
      }
    })
  })
}

module.exports = connect


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @module @lobsangnet/lobsang-processor-hostname
 */

/**
 * Library to process the hostname from an URL.
 *
 * @public
 * @param {string} link - The URL to parse.
 * @returns {Promise<string|Error>}
 *   Promise which resolves to the hostname if it could be parsed.
 *   Or an Error if the validation failed.
 */
function lobsangProcessorHostname (link) {
  let parts
  try {
    parts = parseUrl(link)
  } catch (error) {
    return Promise.reject(error)
  }

  if (parts.hostname === '') {
    return Promise.reject(new Error('Not an URL'))
  }

  return Promise.resolve(parts.hostname)
}

/**
 * Helper method to parse a link using WHATWG URL
 *
 * @private
 * @param {string} link - The URL to parse.
 * @returns {any} - An urlObject containing different aspects of an URL.
 * @throws {TypeError} - If the parsing failed.
 * @todo Replace any after https://github.com/Kegsay/flow-jsdoc/issues/22
 */
function parseUrl (link) {
  return new URL(link)
}

module.exports = lobsangProcessorHostname


let paddys = require('paddys'); if (paddys && paddys.__esModule) paddys = paddys.default;
/**
 * Add a frame around strings.
 * @param {string} string The string to frame.
 * @param {!_frameOfMind.Config} [options] Options for the program.
 * @param {number} [options.padding=1] The left and right padding. Default `1`.
 */
               function frameOfMind(string, options = {}) {
  const { padding = 1 } = options
  const l = string.split('\n')
  const w = l.reduce((acc, { length }) => length > acc ? length : acc, 0)
  const ww = w + padding * 2
  const bt = `┌${'─'.repeat(ww)}┐`
  const bb = `└${'─'.repeat(ww)}┘`
  const pp = ' '.repeat(padding)
  const p = paddys(string)
    .split('\n')
    .map(line => `│${pp}${line}${pp}│`)
    .join('\n')
  return `${bt}\n${p}\n${bb}`
}

/* documentary types/index.xml */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {_frameOfMind.Config} Config Options for the program.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {Object} _frameOfMind.Config Options for the program.
 * @prop {number} [padding=1] The left and right padding. Default `1`.
 */

module.exports = frameOfMind


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const CONSTANTS = require('./constants')

/**
 * @module @lobsangnet/lobsang-connect-matrix/sync
 */

/**
 * Helper method to check the state against those indicating some sync state.
 *
 * @private
 * @param {String} state - The current state of MatrixClient
 * @returns {Boolean}
 *   Indicating whether the client is already synced (or syncing)
 *   Or in another state.
 */
function isSyncState (state) {
  if (state === CONSTANTS.MATRIX_STATE_PREPARED) {
    return true
  }

  if (state === CONSTANTS.MATRIX_STATE_SYNCING) {
    return true
  }

  return false
}

module.exports.isSyncState = isSyncState


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const format = require('./format')
const schema = require('./schema')
const validate = require('./validate')

/**
 * @module @lobsangnet/lobsang-formatter-matrix
 */

/**
 * Library to wrap a message into an object which complies to the given schema.
 *
 * @public
 * @param {string} message - The message to format.
 * @param {string} topic - The topic in which the message should be published.
 * @returns {Promise<any|Error>}
 *   Promise which resolves to a SchemaMessage.
 *   Or an Error if something went wrong.
 * @todo Replace any after https://github.com/Kegsay/flow-jsdoc/issues/22
 */
function lobsangFormatterSchema (message, topic) {
  if (typeof message !== 'string') {
    return Promise.reject(new Error('Message is not a string'))
  }

  return schema(topic)
    .then((schemaObject) => {
      const validator = validate(schemaObject)
      const schemaMessage = format(message)

      if (validator(schemaMessage)) {
        return Promise.resolve(schemaMessage)
      }
      return Promise.reject(validator.errors)
    })
}

module.exports = lobsangFormatterSchema


var fs = require('fs')

var Module = require('module')

var CarbonClient = require('@carbon-io/carbon-client-node')

var errors = require('./errors')

/***************************************************************************************************
 * @property PRIVATE_ENV
 * @memberof bond
 * @ignore
 */
var PRIVATE_ENV = "__privateEnv"

/***************************************************************************************************
 * @method _resolve
 * @memberof bond
 * @ignore
 */
function _resolve(path, options, mod) {
  if (path) {
    var i = path.indexOf(':')
    if (i === -1) {
      return resolveModule(path, options, mod)
    } else {
      var namespace = path.substring(0, i)
      var p = path.substring(i + 1)
      if (namespace === "env") {
        return resolveEnv(p, options, mod)
      } else if (namespace === "http" || namespace === "https") {
        return resolveHttp(path, options, mod)
      } else if (namespace === "file") {
        return resolveFile(p, options, mod)
      } else {
        throw new Error("Unable to resolve path: '" + path + "'. Unknown namespace: " + namespace)
      }
    }
  }
}

/***************************************************************************************************
 * @method resolveEnv
 * @memberof bond
 * @ignore
 */
function resolveEnv(path, options, mod) {
  var result = undefined
  if (path) {
    result = (process[PRIVATE_ENV] && process[PRIVATE_ENV][path]) || process.env[path]
  }

  return result
}

/***************************************************************************************************
 * @method resolveHttp
 * @memberof bond
 * @ignore
 */
function resolveHttp(path, options, mod) {
  return new CarbonClient(path, options)
}

/***************************************************************************************************
 * @method resolveModule
 * @memberof bond
 * @ignore
 */
function resolveModule(path, options, mod) { // XXX this is ineffecient. must cache
  var result = null
  var filepath = resolveFilename(path, mod)
  try {
    result = mod.require(filepath)
  } catch (e) {
    var error = undefined
    if (e instanceof errors.ResolveModuleSyntaxError) {
      error = e
    } else if (e instanceof SyntaxError) {
      error = new errors.ResolveModuleSyntaxError(e, filepath)
    } else {
      error = new Error("Error loading module: " + filepath + " " + e.stack)
    }
    throw error
  }
  return result
}

/***************************************************************************************************
 * @method resolveFilename
 * @memberof bond
 * @ignore
 */
function resolveFilename(path, mod) {
  var result = null;

  if (path) {
    try {
      return Module._resolveFilename(path, mod) // TODO: is this what we want?
    } catch (e) { // XXX really slows this down
      if (path && path.length > 1 && path[0] != '/') {
        pathlist = path.split('/')
        if (pathlist.length > 1) {
          if (pathlist.indexOf("lib") == -1) {
            pathlist.splice(1, 0, "lib")
            var newpath = pathlist.join('/')
            result = Module._resolveFilename(newpath, mod)
            return result
          }
        }
      }

      throw(e)
    }
  }

  return result
}

/***************************************************************************************************
 * @method resolveFile
 * @memberof bond
 * @ignore
 */
function resolveFile(path, options, mod) {
  if(path) {
    var f = Object()
    Object.defineProperties(f, {
      readStream: {
        enumerable: true,
        configurable: false,
        writeable: false,
        get: function() {
          return fs.createReadStream(path)
        }
      },
      writeStream: {
        enumerable: true,
        configurable: false,
        writeable: false,
        get: function() {
          return fs.createWriteStream(path, {flags: "a"})
        }
      },
      content: {
        enumerable: true,
        configurable: false,
        writeable: false,
        get: function() {
          return fs.readFile.sync(path).toString()
        }
      }
    })
    f.toString = function() {
      return this.content
    }
    /* open(flags[, mode][, callback])
     */
    f.open = function() {
      if(!arguments || arguments.length < 1) {
        throw(Error("flags argument is required"))
      }
      var flags = arguments[0]
      var mode = null
      var cb = null
      if(arguments.length > 2) {
        mode = arguments[1]
        cb = arguments[2]
      } else if(arguments.length > 1) {
        if(typeof arguments[1] === "number") {
          mode = arguments[1]
        } else {
          cb = arguments[1]
        }
      }
      if(cb) {
        fs.open(path, flags, mode, cb)
      } else {
        return fs.openSync(path, flags, mode)
      }
    }
    return f
  } else {
    throw(Error("File not found: "+path))
  }
}

// XXX: intentionally leaving the "file" namespace out of resolve's path parameter description since
//      it behaves differently than module resolution for relative paths

/***************************************************************************************************
 * @method resolve
 * @name resolve
 * @memberof bond
 * @description Resolves a resource. A resource can be a module, environment variable, HTTP
 *              resource, or file.
 * @param {string} path --
 *              The resource "path". By default, this is assumed to be a module and will be resolved
 *              relative to the module passed to {@link bond._o}. Environment variables should start
 *              with the scheme ``env`` (e.g., ``env:VAR``), HTTP resources should use the ``http``
 *              or ``https`` scheme (e.g., ``http://foo.com/bar``).
 * @param {Object} options --
 *              Options to pass to the underlying resolver. This is currently only supported by the
 *              HTTP resolver (``CarbonClient``)
 * @returns {*} -- The resource
 */

/***************************************************************************************************
 * @method _o
 * @memberof bond
 * @description A factory function that returns an instantiation function ``_o``
 * @param {Module} mod -- The module whose context the resulting function should execute in. This is
 *                        used to help resolve modules that are relative to ``mod``.
 * @returns {bond.resolve}
 */
function _o(mod) {
  if (!(mod instanceof Module)) {
    throw(Error("Must supply a module to _o: " + mod))
  }
  return function(path, options) {
    return _resolve(path, options, mod)
  }
}

/***************************************************************************************************
 * exports
 */
module.exports = _o
module.exports._o = _o // XXX might want to decide

Object.defineProperty(module.exports, '$Test', {
  enumerable: false,
  configurable: false,
  writeable: false,
  get: function() {
    var bondTests = require('../test/index.js')
    //bondTests.tests.push(require('@carbon-io/carbon-client-node').$Test)
    var skipTest = new (require('@carbon-io/test-tube')).util.SkipTest()
    skipTest.description = "Skipping broken carbon client node tests"
    bondTests.tests.push(skipTest)
    return bondTests
  }
})


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const Ajv = require('ajv')

/**
 * @module @lobsangnet/lobsang-formatter-matrix/validate
 */

/**
 * Derives a validator from a schema.
 *
 * @private
 * @param {object} schema - The schema object to validate data against.
 * @returns {Promise<Function|Error>}
 *   Promise which resolves to a validateFunction to use.
 *   Or an Error if a validator function could not be derived.
 */
function validate (schema) {
  const ajv = new Ajv()
  try {
    const validator = ajv.compile(schema)
    return Promise.resolve(validator)
  } catch (error) {
    return Promise.reject(error)
  }
}

module.exports = validate


"use strict";

var debug         = require('debug')('base:stats');
var assert        = require('assert');
var _             = require('lodash');
var Promise       = require('promise');
var request       = require('superagent-promise');
var url           = require('url');
var urljoin       = require('url-join');
var series        = require('./series');
var events        = require('events');
var util          = require('util');

// Export types and series as defined in series module
// This is to make them available as `base.stats.Series`.
exports.types = series.types;
exports.Series = series.Series;

/**
 * Create an Influx Database Connection
 *
 * options:
 * {
 *   // Connection string for
 *   connectionString:  '<protocol>://<user>:<pwd>@<host>:<port>/db/<database>',
 *
 *   // Max submission delay
 *   maxDelay:          60 * 5, // 5 minutes
 *
 *   // Maximum number of pending points before writing
 *   maxPendingPoints:  250,
 *
 *   // Allow the connection string to use HTTP instead of HTTPS, this option
 *   // is just to prevent accidental deployments with HTTP instead of HTTPS
 *   // (there is no reason not to use HTTPS).
 *   allowHTTP:         false
 * }
 */
var Influx = function(options) {
  assert(options,                   "options are required");
  assert(options.connectionString,  "options.connectionString is missing");
  assert(url.parse(options.connectionString).protocol === 'https:' ||
         options.allowHTTP, "InfluxDB connectionString must use HTTPS!");
  options = _.defaults({}, options, {
    maxDelay:             60 * 5,
    maxPendingPoints:     250
  });
  this._options           = options;
  this._pendingPoints     = {};
  this._nbPendingPoints   = 0;
  this._flushTimeout = setTimeout(
    this.flush.bind(this, true),
    options.maxDelay * 1000
  );
};

/** Flush data to InfluxDB and optionally `restart` the timer */
Influx.prototype.flush = function(restart) {
  var that = this;

  // Clear timeout if asked to restart
  if (restart) {
    clearTimeout(this._flushTimeout);
    this._flushTimeout = null;
  }

  // Send points
  var done = Promise.resolve(null);
  if (this._nbPendingPoints > 0) {
    debug("Sending points to influxdb");

    // Prepare payload for transmission
    var payload = _.values(this._pendingPoints);
    payload.forEach(function(entry) {
      var nbCols = entry.columns.length;
      // Extend all points with some null properties, in case columns were added
      // after the initial creation
      entry.points.forEach(function(p) {
        p.length = nbCols;
      });
    });

    // Reset internals
    this._pendingPoints = {};
    this._nbPendingPoints = 0;

    // Send data
    done = request
      .post(urljoin(this._options.connectionString, 'series'))
      .query({
        time_precision: 'ms'
      })
      .send(payload)
      .end()
      .then(function(res) {
        // Handle errors
        if (!res.ok) {
          throw new Error("Request failed with HTTP code: " + res.status);
        }
      }).then(null, function(err) {
        debug("Failed to send to influxdb, err: %s, %j", err, err, err.stack);
      });
  }

  // Restart if requested
  if (restart) {
    done = done.then(function() {
      // Schedule the next flush
      this._flushTimeout = setTimeout(
        that.flush.bind(that, true),
        that._options.maxDelay * 1000
      );
    });
  }

  return done;
};

/** Flush a close InfluxDB connection */
Influx.prototype.close = function() {
  var that = this;
  clearTimeout(this._flushTimeout);
  this._flushTimeout = null;
  return that.flush(false);
};

/**
 * Add a new point to be saved in `series`.
 *
 * Example:
 *
 *     influx.addPoint('responseTime', {
 *       duration:   251
 *     });
 *
 */
Influx.prototype.addPoint = function(series, point) {
  // Get entry and create one if we don't have one
  var entry = this._pendingPoints[series];
  if (!entry) {
    entry = this._pendingPoints[series] = {
      name:     series,
      columns:  ['time'],
      points:   []
    };
  }

  // Transform point to list form
  var value = [new Date().getTime()];
  _.forIn(point, function(val, col) {
    // Find index for value
    var index = entry.columns.indexOf(col);
    // If it's not in columns, we add it
    if (index === -1) {
      entry.columns.push(col);
      var nbCols = entry.columns.length;
      index = nbCols - 1;
    }

    // Set the value
    value[index] = val;
  });

  // Add point to list of pending points
  entry.points.push(value);
  this._nbPendingPoints += 1;

  // Flush if we have too many points
  if (this._nbPendingPoints >= this._options.maxPendingPoints) {
    this.flush(true);
  }
};

/** Get the number of point currently waiting for submission*/
Influx.prototype.pendingPoints = function() {
  return this._nbPendingPoints;
};

/** Run a query on the influx db */
Influx.prototype.query = function(query) {
  return request
    .get(urljoin(this._options.connectionString, 'series'))
    .query({
      time_precision: 'ms',
      q: query,
    })
    .end()
    .then(function(res) {
      // Handle errors
      if (res.ok) {
        return res.body;
      } else {
        throw new Error("Request failed with HTTP code: " + res.status);
      }
    }).then(null, function(err) {
      debug("Failed to query influxdb, err: %s, %j", err, err, err.stack);
    });
};

// Export Influx
exports.Influx = Influx;

/**
 * Alternative statistics drain that acts like Influx,
 * But drops all points when flushed, useful for tests, when you need to create
 * a mock API or a mock exchange.
 */
var NullDrain = function() {
  this._nbPendingPoints = 0;
};
util.inherits(NullDrain, events.EventEmitter);

/** Drop point counter */
NullDrain.prototype.flush = function() {
  this._nbPendingPoints = 0
  return Promise.resolve(undefined);
};

/** Increment point counter */
NullDrain.prototype.addPoint = function(series, point) {
  debug("NullDrain.addPoint(%s, %j)", series, point);
  this._nbPendingPoints += 1;
  this.emit('point', series, point);
};

/** Drop point counter */
NullDrain.prototype.close = function() {
  return this.flush();
};

/** Return number of points that would be pending */
NullDrain.prototype.pendingPoints = function() {
  return this._nbPendingPoints;
};

// Export NullDrain
exports.NullDrain = NullDrain;

/**
 * Create some express middleware that will send a point to `reporter` with
 * the following columns:
 *
 * columns:
 * {
 *   duration:                stats.types.Number,  // Response time in ms
 *   statusCode:              stats.types.Number,  // Response status code
 *   requesstMethod:          stats.types.String,  // Request method
 *   param<req.params...>:    stats.types.String   // Request URL parameters
 * }
 *
 * As we include `req.params` prefixed with `param` it's necessary for the
 * reporter to accept `additionalColumns` of type `String` or `Any`.
 *
 * You may define additional values to be submitted with the ping using the
 * `additionalValues` object. This is useful if you create one middleware
 * instance of each HTTP end-point and add a key like `name`.
 */
var createResponseTimer = function(reporter, additionalValues) {
  return function(req, res, next) {
    var sent = false;
    var start = process.hrtime();
    var send = function() {
      try {
        // Don't send twice
        if (sent) {
          return;
        }
        sent = true;

        // Get duration
        var d = process.hrtime(start);

        var params = _.transform(req.params, function(result, value, key) {
          // Prefix the parameter keys
          result['param' + key[0].toUpperCase() + key.slice(1)] = value + '';
        });

        // Construct point
        var point = _.defaults({
          // Convert to milliseconds
          duration:       d[0] * 1000 + (d[1] / 1000000),
          requestMethod:  req.method.toLowerCase(),
          statusCode:     res.statusCode
        }, additionalValues, params);

        // Send point to reporter
        reporter(point);
      }
      catch(err) {
        debug("Error while compiling response times: %s, %j",
              err, err, err.stack);
      }
    };

    res.once('finish', send);
    res.once('close', send);
    next();
  };
};

// Export createResponseTimer
exports.createResponseTimer = createResponseTimer;

/**
 * Create a handler timer for AMQP messages received through
 * taskcluster-client. Please note, that this relies on that messages format.
 *
 * options:
 * {
 *   drain:        new Influx(...),  // Place to send events
 *   component:    'queue'           // Identifier for taskcluster component
 * }
 */
var createHandlerTimer = function(handler, options) {
  assert(handler instanceof Function, "A handler must be provided");
  assert(options,                     "options required");
  assert(options.drain,               "options.drain is required");
  assert(options.component,           "options.component is required");

  // Create a reporter
  var reporter = series.HandlerReports.reporter(options.drain);

  // Wrap handler and let that be it
  return function(message) {
    // Create most of the point
    var point = {
      component:      options.component,
      duration:       undefined,
      exchange:       message.exchange || '',
      redelivered:    (message.redelivered ? 'true' : 'false'),
      error:          'false'
    };

    // Start timer
    var start = process.hrtime();

    // Handle the message
    return Promise.resolve(handler(message)).then(function() {
      // Get duration
      var d = process.hrtime(start);

      // Convert to milliseconds
      point.duration = d[0] * 1000 + (d[1] / 1000000);

      // Send point to reporter
      reporter(point);
    }, function(err) {
      // Get duration
      var d = process.hrtime(start);

      // Convert to milliseconds
      point.duration = d[0] * 1000 + (d[1] / 1000000);

      // Flag and error
      point.error = 'true';

      // Send point to reporter
      reporter(point);

      // Re-throw the error
      throw err;
    });
  };
};

// Export createHandlerTimer
exports.createHandlerTimer = createHandlerTimer;

/** Interval handle for process usage monitoring */
var _processUsageReportingInterval = null;

/**
 * Monitor CPU and memory for this instance.
 *
 * options: {
 *   drain:        new Influx(...), // Place to send the pings
 *   interval:     60,              // Report every 1 minute
 *   component:    'queue',         // Identifier for the taskcluster component
 *   process:      'server'         // Process name
 * }
 *
 * Note, this is global in the current process.
 */
var startProcessUsageReporting = function(options) {
  // Validate options
  assert(options,           "Options are required");
  assert(options.drain,     "A drain for the measurements must be provided!");
  assert(options.component, "A component must be specified");
  assert(options.process,   "A process name must be specified");

  // Provide default options
  options = _.defaults({}, options, {
    interval:         60
  });

  // Clear reporting if already started
  if (_processUsageReportingInterval) {
    debug("WARNING: startProcessUsageReporting() already started!");
    clearInterval(_processUsageReportingInterval);
    _processUsageReportingInterval = null;
  }

  // Lazy load the usage monitor module
  var usage = require('usage');

  // Create reporter
  var reporter = series.UsageReports.reporter(options.drain);

  // Set interval to report usage at interval
  _processUsageReportingInterval = setInterval(function() {
    // Lookup usage for the current process
    usage.lookup(process.pid, {keepHistory: true}, function(err, result) {
      // Check for error
      if (err) {
        debug("Failed to get usage statistics, err: %s, %j",
              err, err, err.stack);
        return;
      }

      // Report usage
      reporter({
        component:      options.component,
        process:        options.process,
        cpu:            result.cpu,
        memory:         result.memory
      });
    });
  }, options.interval * 1000);
};

/** Stop process usage reporting */
var stopProcessUsageReporting = function() {
  if (_processUsageReportingInterval) {
    clearInterval(_processUsageReportingInterval);
    _processUsageReportingInterval = null;
  }
}

// Export startProcessUsageReporting and stopProcessUsageReporting
exports.startProcessUsageReporting  = startProcessUsageReporting;
exports.stopProcessUsageReporting   = stopProcessUsageReporting;

/**
 * Create a stats handler for taskcluster-client clients which takes an option
 * `stats` as function that will be call after an API call.
 *
 * options:
 * {
 *   tags: {                         // Tags as key/value (both strings)
 *     component: 'queue',           // Component identifier
 *     process:   'web'              // Process identifier
 *     // Common tags that are good to use includes:
 *     //     component, process, provisionerId, workerType
 *   },
 *   drain:        new Influx(...),  // Place to send events
 * }
 */
var createAPIClientStatsHandler = function(options) {
  options = _.defaults({}, options || {}, {
    tags:   {},
    drain:  undefined
  });
  assert(options.drain,                     "options.drain is required");
  assert(typeof options.tags === 'object',  "options.tags is required");
  assert(_.intersection(
    _.keys(options.tags), series.APIClientCalls.columns()
  ).length === 0, "Can't used reserved tag names!");

  // Create a reporter
  return series.APIClientCalls.reporter(options.drain, options.tags);
};

// Export createAPIClientStatsHandler
exports.createAPIClientStatsHandler = createAPIClientStatsHandler;


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @module @lobsangnet/lobsang-connect-matrix/check
 */

/**
 * Checks the message on type etc.
 *
 * @private
 * @param {String} message - The message to send.
 * @returns {Promise<String|Error>}
 *   Promise which resolved to a cleaned message.
 *   Or an Error if rejected.
 */
function check (message) {
  if (!message) {
    return Promise.reject(new Error('Argument must not be falsy!'))
  }

  if (typeof message !== 'string') {
    return Promise.reject(new Error('Argument must be a string!'))
  }

  return Promise.resolve(message)
}

module.exports = check


'use strict';

/**
 * Dependencies
 */

var objectLength = require('object-length');
var dasherize = require('dashify');
var indent = require('indent-string');
var format = require('util').format;
var result = require('get-result');
var table = require('text-table');
var is = require('is_js');
var os = require('os');

var EOL = os.EOL;

/**
 * Expose generate-help
 */

module.exports = generateHelp;

/**
 * Generate help
 */

function generateHelp (params) {
  var output = '';

  if (params.usage) {
    var usage = result(params, 'usage');

    output += EOL;
    output += format('Usage: %s', usage);
    output += EOL;
  }

  if (params.desc) {
    var desc = result(params, 'desc');

    output += EOL;
    output += desc;
    output += EOL;
  }

  if (is.object(params.options) && objectLength(params.options) > 0) {
    var options = buildOptions(params.options);

    output += EOL;
    output += 'Options:';
    output += EOL;
    output += EOL;
    output += indent(options, ' ', 2);
    output += EOL;
  }

  if (is.array(params.commands) && params.commands.length > 0) {
    var commands = buildCommands(params.commands);

    output += EOL;
    output += 'Commands:';
    output += EOL;
    output += EOL;
    output += indent(commands, ' ', 2);
    output += EOL;
  }

  output += EOL;

  return indent(output, ' ', 2);
}

/**
 * Helpers
 */

/**
 * Output option list
 */

function buildOptions (options) {
  var result = [];

  var keys = Object.keys(options);

  keys.forEach(function (key) {
    var props = options[key];

    // convert short to long form
    if (is.string(props)) {
      props = {
        type: props
      };
    }

    // all names of an option
    // aliases come first
    // full name comes last
    var name = [
      format('--%s', dasherize(key))
    ];

    // accept both string and array
    var aliases = props.alias || props.aliases || [];

    if (is.not.array(aliases)) {
      aliases = [aliases];
    }

    // aliases are prefixed with "-"
    aliases.forEach(function (alias) {
      alias = format('-%s', alias);

      name.unshift(alias);
    });

    result.push([
      name.join(', '),
      props.desc || ''
    ]);
  });

  return table(result);
}

/**
 * Output command list
 */

function buildCommands (commands) {
  var result = [];

  commands.forEach(function (command) {
    result.push([
      command.name,
      command.desc || ''
    ]);
  });

  return table(result);
}


/*
 * grunt-growl
 * https://github.com/alextucker/grunt-growl
 *
 * Copyright (c) 2012 Alex Tucker
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {

  var growl = require('growl'),
      path = require('path'),
      messages = [],
      ignoreWatch = false;

  // Please see the grunt documentation for more information regarding task and
  // helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md

  // ==========================================================================
  // SHARED FUNCTIONS
  // ==========================================================================

  function growlMessage(config) {
    growl(config.message, config);
  }

  function flushMessages(status) {
    if( messages.length <= 0 ) {
      return;
    }

    growlMessage({
      message: messages.join('\n'),
      title: 'Grunt',
      image: __dirname + '/../img/' + status + '.png'
    });
    messages = [];
  }

  // ==========================================================================
  // TASKS
  // ==========================================================================

  grunt.registerMultiTask('growl', 'Configure system notifications from your gruntfile', function() {
    growlMessage(this.data);
  });

  // ==========================================================================
  // HELPERS
  // ==========================================================================

  exports.growl = function(config){
    growlMessage(config);
  };

  exports.growlmock = function(mock){
    growlMessage = mock;
  };

  // ==========================================================================
  // DEFAULT NOTIFICATIONS
  // ==========================================================================

  function initGrowlStatus() {
    grunt.util.hooker.hook(grunt.log, 'write', function(msg){
      if( grunt.log.uncolor(msg).match(/Waiting.../) ) { flushMessages('ok'); }
    });

    grunt.util.hooker.hook(grunt.log, 'header', function(msg){
      msg = grunt.log.uncolor(msg);

      if( ignoreWatch && msg.match(/"watch" task/) ) { return; }

      if( msg.match(/".+:.+"/) ) { return; }

      if( !ignoreWatch && msg.match(/"watch" task/) ) {
        msg += ' for ' + path.basename(process.cwd());
        ignoreWatch = true;
      }

      messages.push(msg);
    });

    grunt.util.hooker.hook(grunt.log, 'ok', function(msg){
      if( typeof msg === 'string' ) {
       messages.push(grunt.log.uncolor(msg));
      }
    });

    grunt.util.hooker.hook(grunt, 'warn', function(error){
      var warning = [];

      if( typeof error !== 'undefined' ) {
        warning.push(messages[0]);
        warning.push(messages[messages.length-1]);
        warning.push(String(error.message || error));
        messages = warning;
        flushMessages('error');
      }
    });

    grunt.util.hooker.hook(grunt.log, 'error', function(msg){
      if( typeof msg === 'string' ) {
       messages.push(grunt.log.uncolor(msg));
       flushMessages('error');
      }
    });
  }

  grunt.util.hooker.hook(grunt, 'initConfig', {
    once: true,
    post: function(){
      if( grunt.config('growlstatus') !== false ) {
        initGrowlStatus();
      }
    }
  });

};


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @typedef MatrixMessage
 * @type Object
 * @property {String} body - The content to transmit.
 * @property {String} msgtype - What kind of message to send.
 */
const CONSTANTS = require('./constants')

/**
 * @module @lobsangnet/lobsang-formatter-matrix
 */

/**
 * Library to wrap a message into an object which can be sent to matrix.
 *
 * @public
 * @param {string} message - The message to format.
 * @returns {Promise<any|Error>}
 *   Promise which resolves to a MatrixMessage.
 *   Or an Error if something went wrong.
 * @todo Replace any after https://github.com/Kegsay/flow-jsdoc/issues/22
 */
function lobsangFormatterMatrix (message) {
  if (typeof message !== 'string') {
    return Promise.reject(new Error('Message is not a string'))
  }

  const formattedMessage = {
    body: message,
    msgtype: CONSTANTS.MATRIX_NOTICE_TYPE
  }
  return Promise.resolve(formattedMessage)
}

module.exports = lobsangFormatterMatrix


'use strict';

const topLogPrefix = 'larvitrouter: index.js: ';
const LUtils       = require('larvitutils');
const Lfs          = require('larvitfs');

/**
 * Module main constructor
 *
 * @param {obj} options - {
 * 	'paths':           {
 * 		'controller': {
 * 			'path': 'controllers',
 * 			'exts': 'js'
 * 		},
 * 		'static': {
 * 			'path': 'public',
 * 			'exts': false
 * 		},
 * 		'template': {
 * 			'path': 'public/templates',
 * 			'exts': ['tmpl', 'tmp', 'ejs', 'pug']
 * 		}
 * 	},
 * 	'routes':   [],
 * 	'basePath': process.cwd(),
 * 	'log':      log object,
 * 	'lfs':      larvitfs instance
 * }
 */
function Router(options) {
	const logPrefix = topLogPrefix + 'Router() - ';
	const that      = this;

	let defaultRouteFound = false;

	that.options = options || {};

	if (! that.options.paths) {
		that.options.paths = {
			'controller': {
				'path': 'controllers',
				'exts': 'js'
			},
			'static': {
				'path': 'public',
				'exts': false
			},
			'template': {
				'path': 'public/templates',
				'exts': ['tmpl', 'tmp', 'ejs', 'pug']
			}
		};
	}

	if (! that.options.routes)   that.options.routes   = [];
	if (! that.options.basePath) that.options.basePath = process.cwd();

	if (! that.options.log) {
		const lUtils = new LUtils();

		that.options.log = new lUtils.Log();
	}

	for (const key of Object.keys(that.options.paths)) {
		if (! Array.isArray(that.options.paths[key].exts) && that.options.paths[key].exts !== false) {
			that.options.paths[key].exts = [that.options.paths[key].exts];
		}
	}

	if (! that.options.lfs) {
		that.options.lfs = new Lfs({'basePath': that.options.basePath, 'log': that.options.log});
	}

	for (let i = 0; that.options.routes[i] !== undefined; i ++) {
		if (that.options.routes[i].regex === '^/$') {
			defaultRouteFound = true;
			break;
		}
	}

	// We should always have a default route, so if none exists, create one
	if (defaultRouteFound === false) {
		that.options.routes.push({
			'regex':          '^/$',
			'controllerPath': 'default.js',
			'templatePath':   'default.tmpl'
		});
	}

	for (const key of Object.keys(that.options)) {
		that[key] = that.options[key];
	}

	that.log.debug(logPrefix + 'Instantiated with options: ' + JSON.stringify(that.options));
}

Router.prototype.resolve = function (urlStr, cb) {
	const logPrefix = topLogPrefix + 'Router.prototype.resolve() - ';
	const result    = {};
	const that      = this;

	let relUrlStr;

	if (typeof cb !== 'function') {
		cb = function () {};
	}

	if (typeof urlStr !== 'string') {
		const err = new Error('First parameter must be a string');

		that.log.warn(logPrefix + err.message);
		that.log.verbose(logPrefix + 'err.stack:\n' + err.stack);

		return cb(err);
	}

	that.log.debug(logPrefix + 'parsing URL ' + urlStr);

	relUrlStr = urlStr[0] === '/' ? urlStr.substring(1) : urlStr;

	// Go through all custom routes to see if we have a match
	for (let i = 0; that.routes[i] !== undefined; i ++) {
		that.log.silly(logPrefix + 'Trying to match custom route "' + that.routes[i].regex + '" with urlStr "' + urlStr + '"');

		if (RegExp(that.routes[i].regex).test(urlStr)) {
			that.log.debug(logPrefix + 'Matched custom route "' + that.routes[i].regex + '" to route: ' + JSON.stringify(that.routes[i]));

			for (const key of Object.keys(that.routes[i])) {
				if (key !== 'regex') {
					result[key]	= that.routes[i][key];
				}
			}

			break; // Break execution, no need to go through the rest
		}
	}

	// If no route is matched, try to autoresolve stuff
	if (Object.keys(result).length === 0) {
		for (const type of Object.keys(that.paths)) {
			const routeOpts = that.paths[type];

			if (! Array.isArray(routeOpts.exts) && that.lfs.getPathSync(routeOpts.path + '/' + relUrlStr)) {
				result[type + 'Path'] = relUrlStr;
			} else {
				for (let i = 0; routeOpts.exts[i] !== undefined; i ++) {
					const ext = routeOpts.exts[i];

					if (that.lfs.getPathSync(routeOpts.path + '/' + relUrlStr + '.' + ext)) {
						result[type + 'Path'] = relUrlStr + '.' + ext;
					}
				}
			}
		}
	}

	// Set full paths where missing
	for (const type of Object.keys(that.paths)) {
		const routeOpts = that.paths[type];

		if (result[type + 'Path'] && ! result[type + 'FullPath']) {
			result[type + 'FullPath'] = that.lfs.getPathSync(routeOpts.path + '/' + result[type + 'Path']);
			if (! result[type + 'FullPath']) {
				that.log.warn(logPrefix + 'Could not find full path for ' + type + 'Path: ' + result[type + 'Path']);
			}
		}
	}

	cb(null, result);
};

exports = module.exports = Router;


/**
 * Created by Andy Likuski on 2017.03.03
 * Copyright (c) 2017 Andy Likuski
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import * as R from 'ramda'
import {JSDOM} from 'jsdom';

global.navigator = {
  userAgent: 'node.js'
};

// jsdom, window, document, navigator setup
// http://airbnb.io/enzyme/docs/guides/jsdom.html
function copyProps(src, target) {
  const props = Object.getOwnPropertyNames(src)
    .filter(prop => typeof target[prop] === 'undefined')
    .reduce((result, prop) => R.merge(
      result,
      {
        [prop]: Object.getOwnPropertyDescriptor(src, prop)
      }),
      {});
  Object.defineProperties(target, props);
}

global.jsdom = new JSDOM('<!doctype html>');
const {window} = jsdom;
global.window = window;
global.document = window.document;
global.navigator = {
  userAgent: 'node.js'
};
copyProps(window, global);
window.URL = window.URL || {}
window.URL.createObjectURL = () => {}

Error.stackTraceLimit = Infinity;
// Have exceptions traces traverse async processes
if (process.env.NODE_ENV !== 'production') {
  require('longjohn');
}

class LocalStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = value.toString();
  }

  removeItem(key) {
    delete this.store[key];
  }
};

global.localStorage = new LocalStorageMock;

// In Node v7 unhandled promise rejections will terminate the process
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
  process.on('unhandledRejection', event => {
    console.error(event)
  })
  // Avoid memory leak by adding too many listeners
  process.env.LISTENING_TO_UNHANDLED_REJECTION = true
}


'use strict'
/*
Copyright 2018 André Jaenisch

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @typedef urlObject
 * @type object
 * @property {string} protocol
 * @property {number} port
 */

const portNumbers = require('port-numbers')

/**
 * @module @lobsangnet/lobsang-processor-port
 */

/**
 * Library to process the port from an URL.
 *
 * @public
 * @param {string} link - The URL to parse.
 * @returns {Promise<string|Error>}
 *   Promise which resolves to the port if it could be parsed.
 *   Or an Error if the validation failed.
 */
function lobsangProcessorPort (link) {
  let parts
  try {
    parts = parseUrl(link)
  } catch (error) {
    return Promise.reject(error)
  }

  if (parts.port === '') {
    return getDefaultPortByProtocol(parts.protocol)
  }

  // For some reasons, the Promise resolves always to a String (not Number).
  return Promise.resolve(String(parts.port))
}

/**
 * Helper method to parse a link using WHATWG URL
 *
 * @private
 * @param {string} link - The URL to parse.
 * @returns {any} - An urlObject containing different aspects of an URL.
 * @throws {TypeError} - If the parsing failed.
 * @todo Replace any after https://github.com/Kegsay/flow-jsdoc/issues/22
 */
function parseUrl (link) {
  return new URL(link)
}

/**
 * Helper method to look up port by protocol.
 *
 * @private
 * @param {string} rawProtocol - Protocol as returned by url, i.e. with :
 * @returns {Promise<string|Error>}
 *   Associated port with protocol.
 *   Or an Error if the validation failed (e.g. mailto:).
 */
function getDefaultPortByProtocol (rawProtocol) {
  // port-numbers expect no trailing colon
  const protocol = rawProtocol.endsWith(':')
    ? rawProtocol.slice(0, -1)
    : rawProtocol

  // e.g. mailto has no port associated
  // example return value:
  // { port: 80, protocol: 'tcp', description: 'World Wide Web HTTP' }
  const portByProtocol = portNumbers.getPort(protocol)

  return portByProtocol
    ? Promise.resolve(String(portByProtocol.port))
    : Promise.reject(new Error('Has no port'))
}

module.exports = lobsangProcessorPort