<commit_before>#!/usr/bin/env node

/*    Copyright 2016-2022 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program 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 Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// TODO Need to check argument, a null appid work ... shouldn't
'use strict';

let log2 = require('../net2/logger.js')(__filename, 'info');
let log = log2.info;
const Trace = require('../util/audit.js');
const rclient = require('../util/redis_manager.js').getRedisClient()

module.exports = class {
    // config is the controller json file + the controller section of fullConfig
    constructor(config, fullConfig, eptcloud, groups, gid, debug, offlineMode = false) {
        this.type = config.controller.type;
        this.id = eptcloud.eid + "." + config.controller.id;
        this.name = config.controller.name;

        this.fullConfig = fullConfig
        this.config = config;
        this.groups = groups;
        this.gid = gid;
        this.eptcloud = eptcloud;
        this.eid = eptcloud.eid;
        this.primarygid = gid;
        this.groupsdb = {};
        this.intercommDevices = {};
        this.defaultDevice = null;
        this.capabilitydb = {};

        this._txQueue = [];
        this._txQueueTxing = false;

        this.compress = true; // if this is on, if last msg send is same ... don't send

        this.offlineMode = offlineMode

        if (offlineMode) {
            log2.info("Skipping eptcloud feature in offline mode");
            return; // skip eptcloud stuff when in offline Mode
        }

        this.initEptCloud()
    }

    initEptCloud() {
        log2.info("Initializeing Controller", this.name, this.type, this.id);

        log2.info("Groups Counter : ", this.groups.length, this.gid);
        for (const group of this.groups) {
            log2.debug("Processing Groups : ", group);
            if (group.me == null) {
                continue;
            }
            log2.info("Group ID: ", group.gid, " Known as: ", group.me.displayName);
            this.groupsdb[group.gid] = group;
            if (this.fullConfig.listen == "all" || group.gid == this.gid) {
                log("Listening to group ", group.gid, group.name, "members count", group.symmetricKeys.length);
                if (group.symmetricKeys.length > 2) {
                    this.tx(group.gid, "Please use @" + group.me.displayName + " to talk to me");
                }
                this.eptcloud.pullMsgFromGroup(group.gid, 4, (e, r) => {
                    if (e) {
                      log2.error('Error getting message from group', e)
                      return
                    }

                    log2.debug(" - ----- Received :",r.length," msgs",r);
                    let myname = group.me.displayName;
                    if (r != null && r.length > 0) {
                        for (let i = 0; i < r.length; i++) {
                            try {
                                let message = r[i].message;
                                if (message && message.msg) {
                                    let lmsg = r[i].message.msg.toLowerCase();
                                    if (lmsg.indexOf('@' + myname) == -1 && group.symmetricKeys.length > 2) {
                                        continue;
                                    }
                                    this.msgHandler(group.gid, r[i]);
                                } else {
                                    log2.error("Invalid error message:", r[i]);
                                }
                            } catch(err) {
                                log2.error("Got error when parsing messages, err:", err);
                            }
                        }
                    }
                }, (e, msg) => {
                    if (this.boneMsgHandler) {
                        this.boneMsgHandler(msg);
                    }
                });
            }
        }
    }

    getDeviceName() {
        for (const group of this.groups) {
            if (group.gid === this.primarygid) {
                return group.name;
            }
        }

        return "Unknown"; // should not reach this line, every device should belong to a group
    }

  updatePrimaryDeviceName(name) {
        for (const group of this.groups) {
            if (group.gid === this.primarygid) {
              group.name = name;
              break;
            }
        }
  }

    msgHandler(gid, msg, callback) { }

    // this is advertise locally or may be remotely like steaming addres ...
    addCapability(gid, eid, type, object, publish) {
        this.capabilitydb[type] = object;
        if (publish) {
            this.publish(gid, eid);
        }
    }

    // msg == type == video
    txFile(gid, thumb, file, msg, type, beepmsg, height, width) {
        let name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendFileToGroup(gid, msg, file, thumb, type, name, beepmsg, height, width, "", function (e, r) {
            if (e) {
                log2.info("Error Sending file", e);
            } else {
                log("Message is sent successfully");
            }
        });
    }

    txData(gid, msg, obj, type, beepmsg, whisper, callback, rawmsg) {
        if (rawmsg && rawmsg.appInfo && rawmsg.appInfo.eid && rawmsg.mtype != "init") {
            const appInfo = rawmsg.appInfo;
            //subscribe trace if command from app
            const filterItems = ["ping", 'liveStats']
            const item = rawmsg.data && rawmsg.data.item;
            if (filterItems.indexOf(item) == -1) {
                let tracelog = {
                    target: rawmsg.target,
                    action: rawmsg.data,
                    appInfo: {
                        deviceName: appInfo.deviceName,
                        eid: appInfo.eid,
                        appID: appInfo.appID,
                        platform: appInfo.platform,
                        version: appInfo.version
                    },
                    success: obj.code == 200,
                    mtype: rawmsg.mtype
                }
                if (!tracelog.success) tracelog.error = obj.message
                Trace(tracelog)
            }
        }
        if (typeof callback === 'function') { //direct callback mode
            callback(null, obj);
            return;
        }

        if (typeof callback === 'object') {
            if (callback.compressMode) {
                obj.compressMode = true;
            }
        }

        var name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendDataToGroup(gid, msg, obj, type, name, beepmsg, whisper, (e, r) => {
            if (e) {
                log2.error("Error Sending data", e, JSON.stringify(msg));
            } else {
                log2.info("Success Sending", r);
            }
        });
    }

    txHtml(gid, m, beepmsg) {
        var name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendHtmlToGroup(gid, m, beepmsg, name, (e, r) => {
            log2.info("sending html", e, r);
        });
    }

    txQ(gid, m, beepmsg) {
        let msg = JSON.stringify({
            gid: gid,
            m: m,
            beepmsg: beepmsg
        });
        if (this._txQueue.indexOf(msg) > -1 && this.compress == true) {
            return;
        }
        this._txQueue.push(msg);
        this._txQ();
    }
    txQ2(gid, m, beepmsg, beepdata) {
        let msg = JSON.stringify({
            gid: gid,
            m: m,
            beepmsg: beepmsg,
            beepdata: beepdata,
        });
        if (this._txQueue.indexOf(msg) > -1 && this.compress == true) {
            return;
        }
        this._txQueue.push(msg);
        this._txQ();
    }

    _txQ() {
        if (this._txQueueTxing == true) {
            return;
        }
        this._txQueueTxing = true;
        let p = this._txQueue.splice(0, 1)[0];
        if (p) {
            p = JSON.parse(p);
        }
        if (this.compress == true && this.lastmsg != null && p != null) {
            if (p.m == this.lastmsg.m && p.gid == this.lastmsg.gid) {
                this._txQueueTxing = false;
                this._txQ();
                setTimeout(() => {
                    this.lastmsg = null
                }, 3000);
                return;
            }
        }
        if (p != null) {
            var name = this.groupsdb[p.gid].me.displayName;
            setTimeout(() => {
                this.eptcloud.sendTextToGroup2(p.gid, p.m, p.beepmsg, p.beepdata, name, (e, r) => {
                    this._txQueueTxing = false;
                    this.lastmsg = p;
                    this._txQ();
                });
            }, 100);
        } else {
            this._txQueueTxing = false;
        }
    }

    async tx(gid, m, beepmsg) {
        let flag = await this.getConfIncludeName()
        if (flag == "1") {
            beepmsg = { title: `[${this.getDeviceName()}]`, body: beepmsg }
        }

        let group = this.groupsdb[gid];
        if (group) {
            var name = this.groupsdb[gid].me.displayName;
            this.eptcloud.sendTextToGroup2(gid, m, beepmsg, null, name, (e, r) => {
                log2.info("sending text", e, r);
            });
        } else {
            log("Group " + gid + " doesn't exist");
        }
    }

    async tx2(gid, m, beepmsg, beepdata) {
        let flag = await this.getConfIncludeName()
        if (flag == "1" && beepmsg.title)
            beepmsg.title = `[${this.getDeviceName()}] ${beepmsg.title}`;

        let group = this.groupsdb[gid];
        if (group) {
            var name = this.groupsdb[gid].me.displayName;
            this.eptcloud.sendTextToGroup2(gid, m, beepmsg, beepdata, name, (e, r) => {
                log2.info("sending text", e, r);
            });
        } else {
            log("Group " + gid + " doesn't exist");
        }
    }

    // check if device name should be included
    // sometimes it is helpful if multiple devices are bound to one app
    getConfIncludeName() {
        return rclient.hgetAsync("sys:config", "includeNameInNotification");
    }
};
<commit_msg>update<commit_after>#!/usr/bin/env node

/*    Copyright 2016-2022 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program 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 Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// TODO Need to check argument, a null appid work ... shouldn't
'use strict';

let log2 = require('../net2/logger.js')(__filename, 'info');
let log = log2.info;
const Trace = require('../util/audit.js');
const rclient = require('../util/redis_manager.js').getRedisClient()

module.exports = class {
    // config is the controller json file + the controller section of fullConfig
    constructor(config, fullConfig, eptcloud, groups, gid, debug, offlineMode = false) {
        this.type = config.controller.type;
        this.id = eptcloud.eid + "." + config.controller.id;
        this.name = config.controller.name;

        this.fullConfig = fullConfig
        this.config = config;
        this.groups = groups;
        this.gid = gid;
        this.eptcloud = eptcloud;
        this.eid = eptcloud.eid;
        this.primarygid = gid;
        this.groupsdb = {};
        this.intercommDevices = {};
        this.defaultDevice = null;
        this.capabilitydb = {};

        this._txQueue = [];
        this._txQueueTxing = false;

        this.compress = true; // if this is on, if last msg send is same ... don't send

        this.offlineMode = offlineMode

        if (offlineMode) {
            log2.info("Skipping eptcloud feature in offline mode");
            return; // skip eptcloud stuff when in offline Mode
        }

        this.initEptCloud()
    }

    initEptCloud() {
        log2.info("Initializeing Controller", this.name, this.type, this.id);

        log2.info("Groups Counter : ", this.groups.length, this.gid);
        for (const group of this.groups) {
            log2.debug("Processing Groups : ", group);
            if (group.me == null) {
                continue;
            }
            log2.info("Group ID: ", group.gid, " Known as: ", group.me.displayName);
            this.groupsdb[group.gid] = group;
            if (this.fullConfig.listen == "all" || group.gid == this.gid) {
                log("Listening to group ", group.gid, group.name, "members count", group.symmetricKeys.length);
                if (group.symmetricKeys.length > 2) {
                    this.tx(group.gid, "Please use @" + group.me.displayName + " to talk to me");
                }
                this.eptcloud.pullMsgFromGroup(group.gid, 4, (e, r) => {
                    if (e) {
                      log2.error('Error getting message from group', e)
                      return
                    }

                    log2.debug(" - ----- Received :",r.length," msgs",r);
                    let myname = group.me.displayName;
                    if (r != null && r.length > 0) {
                        for (const ri of r) {
                            try {
                                let message = ri.message;
                                if (message && "msg" in message && message.msg.constructor.name === 'String') {
                                    let lmsg = message.msg.toLowerCase();
                                    if (lmsg.indexOf('@' + myname) == -1 && group.symmetricKeys.length > 2) {
                                        log2.info("Skipped");
                                        continue;
                                    }
                                    this.msgHandler(group.gid, ri);
                                } else {
                                    log2.error("Invalid error message:", ri);
                                }
                            } catch(err) {
                                log2.error("Got error when parsing messages, err:", err);
                            }
                        }
                    }
                }, (e, msg) => {
                    if (this.boneMsgHandler) {
                        this.boneMsgHandler(msg);
                    }
                });
            }
        }
    }

    getDeviceName() {
        for (const group of this.groups) {
            if (group.gid === this.primarygid) {
                return group.name;
            }
        }

        return "Unknown"; // should not reach this line, every device should belong to a group
    }

  updatePrimaryDeviceName(name) {
        for (const group of this.groups) {
            if (group.gid === this.primarygid) {
              group.name = name;
              break;
            }
        }
  }

    msgHandler(gid, msg, callback) { }

    // this is advertise locally or may be remotely like steaming addres ...
    addCapability(gid, eid, type, object, publish) {
        this.capabilitydb[type] = object;
        if (publish) {
            this.publish(gid, eid);
        }
    }

    // msg == type == video
    txFile(gid, thumb, file, msg, type, beepmsg, height, width) {
        let name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendFileToGroup(gid, msg, file, thumb, type, name, beepmsg, height, width, "", function (e, r) {
            if (e) {
                log2.info("Error Sending file", e);
            } else {
                log("Message is sent successfully");
            }
        });
    }

    txData(gid, msg, obj, type, beepmsg, whisper, callback, rawmsg) {
        if (rawmsg && rawmsg.appInfo && rawmsg.appInfo.eid && rawmsg.mtype != "init") {
            const appInfo = rawmsg.appInfo;
            //subscribe trace if command from app
            const filterItems = ["ping", 'liveStats']
            const item = rawmsg.data && rawmsg.data.item;
            if (filterItems.indexOf(item) == -1) {
                let tracelog = {
                    target: rawmsg.target,
                    action: rawmsg.data,
                    appInfo: {
                        deviceName: appInfo.deviceName,
                        eid: appInfo.eid,
                        appID: appInfo.appID,
                        platform: appInfo.platform,
                        version: appInfo.version
                    },
                    success: obj.code == 200,
                    mtype: rawmsg.mtype
                }
                if (!tracelog.success) tracelog.error = obj.message
                Trace(tracelog)
            }
        }
        if (typeof callback === 'function') { //direct callback mode
            callback(null, obj);
            return;
        }

        if (typeof callback === 'object') {
            if (callback.compressMode) {
                obj.compressMode = true;
            }
        }

        var name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendDataToGroup(gid, msg, obj, type, name, beepmsg, whisper, (e, r) => {
            if (e) {
                log2.error("Error Sending data", e, JSON.stringify(msg));
            } else {
                log2.info("Success Sending", r);
            }
        });
    }

    txHtml(gid, m, beepmsg) {
        var name = this.groupsdb[gid].me.displayName;
        this.eptcloud.sendHtmlToGroup(gid, m, beepmsg, name, (e, r) => {
            log2.info("sending html", e, r);
        });
    }

    txQ(gid, m, beepmsg) {
        let msg = JSON.stringify({
            gid: gid,
            m: m,
            beepmsg: beepmsg
        });
        if (this._txQueue.indexOf(msg) > -1 && this.compress == true) {
            return;
        }
        this._txQueue.push(msg);
        this._txQ();
    }
    txQ2(gid, m, beepmsg, beepdata) {
        let msg = JSON.stringify({
            gid: gid,
            m: m,
            beepmsg: beepmsg,
            beepdata: beepdata,
        });
        if (this._txQueue.indexOf(msg) > -1 && this.compress == true) {
            return;
        }
        this._txQueue.push(msg);
        this._txQ();
    }

    _txQ() {
        if (this._txQueueTxing == true) {
            return;
        }
        this._txQueueTxing = true;
        let p = this._txQueue.splice(0, 1)[0];
        if (p) {
            p = JSON.parse(p);
        }
        if (this.compress == true && this.lastmsg != null && p != null) {
            if (p.m == this.lastmsg.m && p.gid == this.lastmsg.gid) {
                this._txQueueTxing = false;
                this._txQ();
                setTimeout(() => {
                    this.lastmsg = null
                }, 3000);
                return;
            }
        }
        if (p != null) {
            var name = this.groupsdb[p.gid].me.displayName;
            setTimeout(() => {
                this.eptcloud.sendTextToGroup2(p.gid, p.m, p.beepmsg, p.beepdata, name, (e, r) => {
                    this._txQueueTxing = false;
                    this.lastmsg = p;
                    this._txQ();
                });
            }, 100);
        } else {
            this._txQueueTxing = false;
        }
    }

    async tx(gid, m, beepmsg) {
        let flag = await this.getConfIncludeName()
        if (flag == "1") {
            beepmsg = { title: `[${this.getDeviceName()}]`, body: beepmsg }
        }

        let group = this.groupsdb[gid];
        if (group) {
            var name = this.groupsdb[gid].me.displayName;
            this.eptcloud.sendTextToGroup2(gid, m, beepmsg, null, name, (e, r) => {
                log2.info("sending text", e, r);
            });
        } else {
            log("Group " + gid + " doesn't exist");
        }
    }

    async tx2(gid, m, beepmsg, beepdata) {
        let flag = await this.getConfIncludeName()
        if (flag == "1" && beepmsg.title)
            beepmsg.title = `[${this.getDeviceName()}] ${beepmsg.title}`;

        let group = this.groupsdb[gid];
        if (group) {
            var name = this.groupsdb[gid].me.displayName;
            this.eptcloud.sendTextToGroup2(gid, m, beepmsg, beepdata, name, (e, r) => {
                log2.info("sending text", e, r);
            });
        } else {
            log("Group " + gid + " doesn't exist");
        }
    }

    // check if device name should be included
    // sometimes it is helpful if multiple devices are bound to one app
    getConfIncludeName() {
        return rclient.hgetAsync("sys:config", "includeNameInNotification");
    }
};
<|endoftext|>


<commit_before>const plugins = require('./lib/plugins')
let argv = process.argv.slice(2)
argv.unshift('heroku')

function onexit (options) {
  const ansi = require('ansi-escapes')
  process.stderr.write(ansi.cursorShow)
  if (options.exit) process.exit(1)
}

process.on('exit', onexit)
process.on('SIGINT', onexit.bind(null, {exit: true}))

async function main () {
  let command
  try {
    let Command = plugins.commands.find(argv[1])
    if (!Command) Command = plugins.load().commands.find(argv[1])
    if (!Command) Command = require('./lib/commands/help')
    if (!Command._version) {
      // v5 command
      const {convertLegacy} = require('heroku-cli-command')
      Command = convertLegacy(Command)
    }
    command = new Command({argv})
    await command.init()
    await command.run()
    await command.done()
    process.exit(0)
  } catch (err) {
    if (command) command.error(err)
    else console.error(err)
    process.exit(1)
  }
}

module.exports = main()
<commit_msg>conditionally time-require<commit_after>if (process.env.HEROKU_TIME_REQUIRE) require('time-require')
const plugins = require('./lib/plugins')
let argv = process.argv.slice(2)
argv.unshift('heroku')

function onexit (options) {
  const ansi = require('ansi-escapes')
  process.stderr.write(ansi.cursorShow)
  if (options.exit) process.exit(1)
}

process.on('exit', onexit)
process.on('SIGINT', onexit.bind(null, {exit: true}))

async function main () {
  let command
  try {
    let Command = plugins.commands.find(argv[1])
    if (!Command) Command = plugins.load().commands.find(argv[1])
    if (!Command) Command = require('./lib/commands/help')
    if (!Command._version) {
      // v5 command
      const {convertLegacy} = require('heroku-cli-command')
      Command = convertLegacy(Command)
    }
    command = new Command({argv})
    await command.init()
    await command.run()
    await command.done()
    process.exit(0)
  } catch (err) {
    if (command) command.error(err)
    else console.error(err)
    process.exit(1)
  }
}

module.exports = main()
<|endoftext|>


<commit_before>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redexutil/LICENSE

import assert from 'assert';
import bunyan from 'bunyan';
import lodash from 'lodash';
import path from 'path';

const DefaultLevel = 'info';

const Levels = ['debug', 'info', 'warn', 'error'];
const ExtraLevels = ['state', 'digest', 'method'];
const AllLevels = Levels.concat(ExtraLevels);

function createService() {

   const state = {
      limit: 10,
      logging: {
         info: [],
         debug: [],
         warn: [],
         error: [],
         digest: [],
         state: [],
         method: []
      }
   };

   const Loggers = {
      create(name, level) {
         name = path.basename(name, '.js');
         level = level || global.loggerLevel || process.env.loggerLevel || DefaultLevel;
         if (lodash.includes(Levels, level)) {
            let logger = bunyan.createLogger({name, level});
            //logger.info('logger', level, global.loggerLevel);
            return decorate(logger, name, level);
         } else {
            assert(lodash.includes(ExtraLevels, level), 'level: ' + level);
            return decorate(null, name, level);
         }
      }
   };

   function logging(logger, name, loggerLevel, level, args, count) {
      args = [].slice.call(args); // convert arguments to array
      if (!state.logging.hasOwnProperty(level)) {
         args.splice(0, 0, 'Invalid level: ' + level);
         level = 'warn';
      }
      state.logging[level].splice(0, 0, args);
      if (state.logging[level].length > state.limit) { // trim
         state.logging[level].length = state.limit;
      }
      if (logger) {
         if (level === 'digest') {
            if (count % 5 === 0) {
               logger.debug('digest', count, ...args);
            }
         } else if (level === 'method') {
            logger.debug('method', ...args);
         } else if (lodash.includes(Levels, level)) {
            if (Levels.indexOf(level) >= Levels.indexOf(loggerLevel)) {
               logger[level].call(logger, ...args);
               //console.info('logging', name, loggerLevel, level, args);
            }
         }
      }
   }

   function decorate(logger, name, level) {
      let count = 0;
      const logr = {
         get name() {
            return name;
         },
         debug() {
            if (level === 'debug') {
               logging(logger, name, level, 'debug', arguments);
            }
         },
         info() {
            logging(logger, name, level, 'info', arguments);
         },
         warn() {
            logging(logger, name, level, 'warn', arguments);
         },
         error() {
            logging(logger, name, level, 'error', arguments);
         },
         state() {
            logging(logger, name, level, 'state', arguments);
         },
         digest() {
            if (level === 'debug') {
               count += 1;
               logging(logger, name, level, 'digest', arguments, count);
            }
         },
         method() {
            let label = arguments[0] + '(' + arguments[1] + ')';
            if (level === 'debug') {
               logging(logger, name, level, 'method', arguments);
            }
            return Loggers.create(name + '.' + label, level);
         }
      };
      return logr;
   }

   return Loggers;
};

module.exports = createService();
<commit_msg>update<commit_after>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redexutil/LICENSE

import assert from 'assert';
import bunyan from 'bunyan';
import lodash from 'lodash';
import path from 'path';

const DefaultLevel = 'info';

const Levels = ['debug', 'info', 'warn', 'error'];
const ExtraLevels = ['state', 'digest', 'method'];
const AllLevels = Levels.concat(ExtraLevels);

const state = {
   limit: 10,
   logging: {
      info: [],
      debug: [],
      warn: [],
      error: [],
      digest: [],
      state: [],
      method: []
   }
};

module.exports = {
   create(name, level) {
      name = path.basename(name, '.js');
      level = level || global.loggerLevel || process.env.loggerLevel || DefaultLevel;
      if (lodash.includes(Levels, level)) {
         let logger = bunyan.createLogger({name, level});
         //logger.info('logger', level, global.loggerLevel);
         return decorate(logger, name, level);
      } else {
         assert(lodash.includes(ExtraLevels, level), 'level: ' + level);
         return decorate(null, name, level);
      }
   }
};

function logging(logger, name, loggerLevel, level, args, count) {
   args = [].slice.call(args); // convert arguments to array
   if (!state.logging.hasOwnProperty(level)) {
      args.splice(0, 0, 'Invalid level: ' + level);
      level = 'warn';
   }
   state.logging[level].splice(0, 0, args);
   if (state.logging[level].length > state.limit) { // trim
      state.logging[level].length = state.limit;
   }
   if (logger) {
      if (level === 'digest') {
         if (count % 5 === 0) {
            logger.debug('digest', count, ...args);
         }
      } else if (level === 'method') {
         logger.debug('method', ...args);
      } else if (lodash.includes(Levels, level)) {
         if (Levels.indexOf(level) >= Levels.indexOf(loggerLevel)) {
            logger[level].call(logger, ...args);
            //console.info('logging', name, loggerLevel, level, args);
         }
      }
   }
}

function decorate(logger, name, level) {
   let count = 0;
   const those = {
      get name() {
         return name;
      },
      debug() {
         if (level === 'debug') {
            logging(logger, name, level, 'debug', arguments);
         }
      },
      info() {
         logging(logger, name, level, 'info', arguments);
      },
      warn() {
         logging(logger, name, level, 'warn', arguments);
      },
      error() {
         logging(logger, name, level, 'error', arguments);
      },
      state() {
         logging(logger, name, level, 'state', arguments);
      },
      digest() {
         if (level === 'debug') {
            count += 1;
            logging(logger, name, level, 'digest', arguments, count);
         }
      },
      method() {
         let label = arguments[0] + '(' + arguments[1] + ')';
         if (level === 'debug') {
            logging(logger, name, level, 'method', arguments);
         }
         return Loggers.create(name + '.' + label, level);
      }
   };
   return those;
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';
var updateNotifier = require('update-notifier');
var meow = require('meow');
var crin = require('./');

var cli = meow({
	help: [
		'Usage',
		'  $ crin latest'
	]
}, {
	string: ['_']
});

if (cli.input[0] === 'latest') {
    crin();
} else {
    console.error('Parameter latest missing.');
    cli.showHelp();
}
<commit_msg>show cli update message if available<commit_after>#!/usr/bin/env node

'use strict';

var updateNotifier = require('update-notifier');
var meow = require('meow');
var crin = require('./');

var cli = meow({
	help: [
		'Usage',
		'  $ crin latest'
	]
}, {
	string: ['_']
});

updateNotifier({pkg: cli.pkg}).notify();

var input = cli.input;
var opts = cli.flags;

if (input[0] === 'latest') {
    crin();
} else {
    console.error('Parameter latest missing.');
    cli.showHelp();
}
<|endoftext|>


<commit_before>"use strict";

/**
 * Main JS file for GhostScroll behaviours
 */

var $sitehead = $('#site-head');

/* Globals jQuery, document */
(function ($) {
	$(document).ready(initialize);
	$(window).resize(onViewportChange);
	$(window).scroll(onViewportChange);

	function initialize() {
		setupJumpHandlers();
		fontAwesomeDecorators();
		fontAwesomeReplacement();

		function setupJumpHandlers() {
                    // FitVids for responsive videos
            $('.post-content').fitVids();
                    // Arrow top in fixed menu
            $('#totop-arrow').click(function () {
            smoothScroll($sitehead);
            });
			$('#header-arrow').click( function () {
				var $first = $(".post").first();
				smoothScroll($first);
			});

			$('.fn-item, .btn').click(function (evt) {
				var slug = $(this).attr("href");
        // We don't want to prevent a link from working if it is external.
        if (slug.slice(0,1) === "#") {
          evt.preventDefault();
          // TODO: update URL with deep link
          smoothScroll($(slug))
        }
			});
		}

		function fontAwesomeDecorators() {
			$('ul > li').before('');
			$('blockquote p').prepend('');
			$('blockquote p').append('');
		}

		function fontAwesomeReplacement() {
			// // Matches
			// @fa-singleword@
			// @fa-dash-separated-words@
			// // Does *not* match
			// \@fa-escaped@
			// @fa-space separated@
			// @fa-Invalid-Case@
			// @fa-Invalid-(H^R@
			// @fa-will-match@@fa-will-not-match@
			//
			// Note: JS doesn't support negative lookbehinds. Thus to permit
			// escaped examples (\@fa-foo@) we capture the preceeding
			// character. This shouldn't pose a problem except in the event of
			// adjacent font-awesome characters (that are not space separated).
			// See last example above.
			var reg = /([^\\])@(fa-[-a-z]+)@/g;

			var $posts = $("#posts-wrapper")
			$posts.html($posts.html().replace(reg, "$1"));
		}
	}

	function onViewportChange() {
		conditionallyShowNav();
		highlightActiveSection();

		function conditionallyShowNav() {
      var isTooNarrow = $(window).width() < 500;
      var shouldDisplayNav = !isTooNarrow && !isElementInViewport($sitehead);
			var navOpacity = shouldDisplayNav ? 1 : 0;
			$(".fixed-nav").css("opacity", navOpacity);
		}

		function highlightActiveSection() {
			$(".post-holder").each(function () {
				var $this = $(this);
				var postId = $this.attr("id");
				var $thisNavLink = $(".fn-item[href='#" + postId + "']");
				var $previousArrow = $(this).prev('.post-holder').find('.post-after');

				if(isElementInViewport($this)) {
					$thisNavLink.addClass('active');
					$previousArrow.fadeOut('slow');
				} else {
					$thisNavLink.removeClass('active');
					$previousArrow.fadeIn('slow');
				}
			});
		}

		function isElementInViewport($element) {
			var scrollPosition = $(window).scrollTop();
			var topOfElement = $element.offset().top;
			var bottomOfElement = topOfElement + $element.height();

			var isAboveElement = topOfElement > scrollPosition;
			var isBelowElement = scrollPosition >= bottomOfElement;

			return (!isAboveElement && !isBelowElement);
		}
	}

	function smoothScroll ($element) {
		$('html, body').animate({
			scrollTop: $element.offset().top
		}, 400);
	}

}(jQuery));<commit_msg>Update url to reflect link click<commit_after>"use strict";

/**
 * Main JS file for GhostScroll behaviours
 */

var $sitehead = $('#site-head');

/* Globals jQuery, document */
(function ($) {
	$(document).ready(initialize);
	$(window).resize(onViewportChange);
	$(window).scroll(onViewportChange);

	function initialize() {
		setupJumpHandlers();
		fontAwesomeDecorators();
		fontAwesomeReplacement();

		function setupJumpHandlers() {
                    // FitVids for responsive videos
            $('.post-content').fitVids();
                    // Arrow top in fixed menu
            $('#totop-arrow').click(function () {
            smoothScroll($sitehead);
            });
			$('#header-arrow').click( function () {
				var $first = $(".post").first();
				smoothScroll($first);
			});

			$('.fn-item, .btn').click(function (evt) {
        var $this = $(this);
				var href = $this.attr("href");

        // We don't want to prevent a link from working if it is external.
        if (href.slice(0,1) === "#") {
          evt.preventDefault();
          var title = $(this).text();
          window.history.pushState(title, title, href);
          smoothScroll($(href))
        }
			});
		}

		function fontAwesomeDecorators() {
			$('ul > li').before('');
			$('blockquote p').prepend('');
			$('blockquote p').append('');
		}

		function fontAwesomeReplacement() {
			// // Matches
			// @fa-singleword@
			// @fa-dash-separated-words@
			// // Does *not* match
			// \@fa-escaped@
			// @fa-space separated@
			// @fa-Invalid-Case@
			// @fa-Invalid-(H^R@
			// @fa-will-match@@fa-will-not-match@
			//
			// Note: JS doesn't support negative lookbehinds. Thus to permit
			// escaped examples (\@fa-foo@) we capture the preceeding
			// character. This shouldn't pose a problem except in the event of
			// adjacent font-awesome characters (that are not space separated).
			// See last example above.
			var reg = /([^\\])@(fa-[-a-z]+)@/g;

			var $posts = $("#posts-wrapper")
			$posts.html($posts.html().replace(reg, "$1"));
		}
	}

	function onViewportChange() {
		conditionallyShowNav();
		highlightActiveSection();

		function conditionallyShowNav() {
      var isTooNarrow = $(window).width() < 500;
      var shouldDisplayNav = !isTooNarrow && !isElementInViewport($sitehead);
			var navOpacity = shouldDisplayNav ? 1 : 0;
			$(".fixed-nav").css("opacity", navOpacity);
		}

		function highlightActiveSection() {
			$(".post-holder").each(function () {
				var $this = $(this);
				var postId = $this.attr("id");
				var $thisNavLink = $(".fn-item[href='#" + postId + "']");
				var $previousArrow = $(this).prev('.post-holder').find('.post-after');

				if(isElementInViewport($this)) {
					$thisNavLink.addClass('active');
					$previousArrow.fadeOut('slow');
				} else {
					$thisNavLink.removeClass('active');
					$previousArrow.fadeIn('slow');
				}
			});
		}

		function isElementInViewport($element) {
			var scrollPosition = $(window).scrollTop();
			var topOfElement = $element.offset().top;
			var bottomOfElement = topOfElement + $element.height();

			var isAboveElement = topOfElement > scrollPosition;
			var isBelowElement = scrollPosition >= bottomOfElement;

			return (!isAboveElement && !isBelowElement);
		}
	}

	function smoothScroll ($element) {
		$('html, body').animate({
			scrollTop: $element.offset().top
		}, 400);
	}

}(jQuery));<|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict';

const meow = require('meow');
const autoDistTag = require('./src/auto-dist-tag');

const cli = meow(`
	Usage
	  $ auto-dist-tag [path]

	Options
	  --write, -w  Write calculated dist-tag to the package.json file
`);

let cwd = cli.input[0] || process.cwd();

autoDistTag(cwd, cli.flags)
  .then(json => console.log(json));
<commit_msg>Fix #20 - `-w` not working<commit_after>#!/usr/bin/env node

'use strict';

const meow = require('meow');
const autoDistTag = require('./src/auto-dist-tag');

const cli = meow(`
	Usage
	  $ auto-dist-tag [path]

	Options
	  --write, -w  Write calculated dist-tag to the package.json file
`, {
  flags: {
    write: {
      type: boolean,
      alias: 'w'
    }
  }
});

let cwd = cli.input[0] || process.cwd();

autoDistTag(cwd, cli.flags)
  .then(json => console.log(json));
<|endoftext|>


<commit_before>/**
 * Main JS file for kaldorei behaviours
 */

/* globals jQuery, document */
(function($, undefined) {
    "use strict";

    var $document = $(document);

    $document.ready(function() {
        var $postContent = $(".post-content");
        $postContent.fitVids();

        $(".scroll-down").arctic_scroll();

        $(".menu-button, .nav-cover, .nav-close").on("click", function(e) {
            e.preventDefault();
            $("body").toggleClass("nav-opened nav-closed");
        });

        $(window).scroll(function() {
            var scrollerToTop = $('.backTop');
            var scrollerTOC = $('.widget-toc');
            document.documentElement.scrollTop + document.body.scrollTop > 200 ?
                scrollerToTop.fadeIn() :
                scrollerToTop.fadeOut();
            document.documentElement.scrollTop + document.body.scrollTop > 250 ?
                scrollerTOC.addClass("widget-toc-fixed") :
                scrollerTOC.removeClass("widget-toc-fixed");
        });

        // #backTop Button Event
        $("#backTop").on("click", function() {
            scrollToTop();
        });

        // highlight config
        hljs.initHighlightingOnLoad();

        // numbering for pre>code blocks
        $(function() {
            $('pre code').each(function() {
                var lines = $(this).text().split('\n').length - 1;
                var $numbering = $('
').addClass('pre-numbering');
                $(this).addClass('has-numbering').parent().append($numbering);
                for (var i = 1; i <= lines; i++) {
                    $numbering.append($('
').text(i));
                }
            });
        });

        var toc = $('.toc');
        // toc config
        toc.toc({
            content: ".post-content",
            headings: "h2,h3,h4,h5"
        });

        if (toc.children().length == 0) $(".widget-toc").hide();

        var tocHieght = toc.height();
        var tocFixedHeight = $(window).height() - 192;
        tocHieght > tocFixedHeight ?
            toc.css('height', tocFixedHeight) :
            toc.css('height', tocHieght)

        $(window).resize(function() {
            var tocFixedHeight = $(this).height() - 192;
            tocHieght > tocFixedHeight ?
                toc.css('height', tocFixedHeight) :
                toc.css('height', tocHieght)
        })

        // toc animate effect
        // bind click event to all internal page anchors
        $('a.data-scroll').on('click', function(e) {
            // prevent default action and bubbling
            e.preventDefault();
            e.stopPropagation();
            // set target to anchor's "href" attribute
            var target = $(this).attr('href');
            // scroll to each target
            $(target).velocity('scroll', {
                duration: 500,
                easing: 'ease-in-out'
                //easing: 'spring'
            });
        });

        // tooltip config
        $('[data-rel=tooltip]').tooltip();

        // fancybox 3.7.5 config
        $('.post-content a:has(img)').addClass('fancybox');
        $(".fancybox").attr('data-fancybox', 'images').fancybox({
            selector       : '[data-fancybox="images"]',
            loop           : true,
            slidesToShow   : 3,
            slidesToScroll : 3,
            infinite       : true
        });

        // add archives year
        var yearArray = new Array();
        $(".archives-item").each(function() {
            var archivesYear = $(this).attr("date");
            yearArray.push(archivesYear);
        });
        var uniqueYear = $.unique(yearArray);
        for (var i = 0; i < uniqueYear.length; i++) {
            var html = "
" +
                "
" +
                "
<time datetime='" + uniqueYear[i] + "'>" + uniqueYear[i] + "</time>
" +
                "

";
            $("[date='" + uniqueYear[i] + "']:first").before(html);
        }
    });

    // Arctic Scroll by Paul Adam Davis
    // https://github.com/PaulAdamDavis/Arctic-Scroll
    $.fn.arctic_scroll = function(options) {
        var defaults = {
            elem: $(this),
            speed: 500
        },

        allOptions = $.extend(defaults, options);

        allOptions.elem.click(function(event) {
            event.preventDefault();
            var $this = $(this),
                $htmlBody = $('html, body'),
                offset = ($this.attr('data-offset')) ? $this.attr('data-offset') : false,
                position = ($this.attr('data-position')) ? $this.attr('data-position') : false,
                toMove;

            if (offset) {
                toMove = parseInt(offset);
                $htmlBody.stop(true, false).animate({
                    scrollTop: ($(this.hash).offset().top + toMove)
                }, allOptions.speed);
            } else if (position) {
                toMove = parseInt(position);
                $htmlBody.stop(true, false).animate({
                    scrollTop: toMove
                }, allOptions.speed);
            } else {
                $htmlBody.stop(true, false).animate({
                    scrollTop: ($(this.hash).offset().top)
                }, allOptions.speed);
            }
        });
    };
})(jQuery);

function scrollToTop(name, speed) {
    if (!speed) speed = 300
    if (!name) {
        $('html,body').animate({
            scrollTop: 0
        }, speed)
    } else {
        if ($(name).length > 0) {
            $('html,body').animate({
                scrollTop: $(name).offset().top
            }, speed)
        }
    }
}
<commit_msg>update: 调整table样式<commit_after>/**
 * Main JS file for kaldorei behaviours
 */

/* globals jQuery, document */
(function($, undefined) {
    "use strict";

    var $document = $(document);

    $document.ready(function() {
        var $postContent = $(".post-content");
        $postContent.fitVids();

        $(".scroll-down").arctic_scroll();

        $(".menu-button, .nav-cover, .nav-close").on("click", function(e) {
            e.preventDefault();
            $("body").toggleClass("nav-opened nav-closed");
        });

        $(window).scroll(function() {
            var scrollerToTop = $('.backTop');
            var scrollerTOC = $('.widget-toc');
            document.documentElement.scrollTop + document.body.scrollTop > 200 ?
                scrollerToTop.fadeIn() :
                scrollerToTop.fadeOut();
            document.documentElement.scrollTop + document.body.scrollTop > 250 ?
                scrollerTOC.addClass("widget-toc-fixed") :
                scrollerTOC.removeClass("widget-toc-fixed");
        });

        // #backTop Button Event
        $("#backTop").on("click", function() {
            scrollToTop();
        });

        // highlight config
        hljs.initHighlightingOnLoad();

        // numbering for pre>code blocks
        $(function() {
            $('pre code').each(function() {
                var lines = $(this).text().split('\n').length - 1;
                var $numbering = $('
').addClass('pre-numbering');
                $(this).addClass('has-numbering').parent().append($numbering);
                for (var i = 1; i <= lines; i++) {
                    $numbering.append($('
').text(i));
                }
            });
        });

        var toc = $('.toc');
        // toc config
        toc.toc({
            content: ".post-content",
            headings: "h2,h3,h4,h5"
        });

        if (toc.children().length == 0) $(".widget-toc").hide();

        var tocHieght = toc.height();
        var tocFixedHeight = $(window).height() - 192;
        tocHieght > tocFixedHeight ?
            toc.css('height', tocFixedHeight) :
            toc.css('height', tocHieght)

        $(window).resize(function() {
            var tocFixedHeight = $(this).height() - 192;
            tocHieght > tocFixedHeight ?
                toc.css('height', tocFixedHeight) :
                toc.css('height', tocHieght)
        })

        // toc animate effect
        // bind click event to all internal page anchors
        $('a.data-scroll').on('click', function(e) {
            // prevent default action and bubbling
            e.preventDefault();
            e.stopPropagation();
            // set target to anchor's "href" attribute
            var target = $(this).attr('href');
            // scroll to each target
            $(target).velocity('scroll', {
                duration: 500,
                easing: 'ease-in-out'
                //easing: 'spring'
            });
        });

        // tooltip config
        $('[data-rel=tooltip]').tooltip();

        //table
        $('.post-content table').addClass('table table-border');

        // fancybox 3.7.5 config
        $('.post-content a:has(img)').addClass('fancybox');
        $(".fancybox").attr('data-fancybox', 'images').fancybox({
            selector       : '[data-fancybox="images"]',
            loop           : true,
            slidesToShow   : 3,
            slidesToScroll : 3,
            infinite       : true
        });

        // add archives year
        var yearArray = new Array();
        $(".archives-item").each(function() {
            var archivesYear = $(this).attr("date");
            yearArray.push(archivesYear);
        });
        var uniqueYear = $.unique(yearArray);
        for (var i = 0; i < uniqueYear.length; i++) {
            var html = "
" +
                "
" +
                "
<time datetime='" + uniqueYear[i] + "'>" + uniqueYear[i] + "</time>
" +
                "

";
            $("[date='" + uniqueYear[i] + "']:first").before(html);
        }
    });

    // Arctic Scroll by Paul Adam Davis
    // https://github.com/PaulAdamDavis/Arctic-Scroll
    $.fn.arctic_scroll = function(options) {
        var defaults = {
            elem: $(this),
            speed: 500
        },

        allOptions = $.extend(defaults, options);

        allOptions.elem.click(function(event) {
            event.preventDefault();
            var $this = $(this),
                $htmlBody = $('html, body'),
                offset = ($this.attr('data-offset')) ? $this.attr('data-offset') : false,
                position = ($this.attr('data-position')) ? $this.attr('data-position') : false,
                toMove;

            if (offset) {
                toMove = parseInt(offset);
                $htmlBody.stop(true, false).animate({
                    scrollTop: ($(this.hash).offset().top + toMove)
                }, allOptions.speed);
            } else if (position) {
                toMove = parseInt(position);
                $htmlBody.stop(true, false).animate({
                    scrollTop: toMove
                }, allOptions.speed);
            } else {
                $htmlBody.stop(true, false).animate({
                    scrollTop: ($(this.hash).offset().top)
                }, allOptions.speed);
            }
        });
    };
})(jQuery);

function scrollToTop(name, speed) {
    if (!speed) speed = 300
    if (!name) {
        $('html,body').animate({
            scrollTop: 0
        }, speed)
    } else {
        if ($(name).length > 0) {
            $('html,body').animate({
                scrollTop: $(name).offset().top
            }, speed)
        }
    }
}
<|endoftext|>


<commit_before>/* global Module Log moment config */

/* Magic Mirror
 * Module: MMM-NFL
 *
 * By fewieden https://github.com/fewieden/MMM-NFL
 * MIT Licensed.
 */

Module.register('MMM-NFL', {

    modes: {
        P: 'Pre-Season',
        R: 'Regular-Season',
        POST: 'Post-Season'
    },

    details: {
        y: (new Date()).getFullYear(),
        t: 'P'
    },

    states: {
        1: '1ST_QUARTER',
        2: '2ND_QUARTER',
        3: '3RD_QUARTER',
        4: '4TH_QUARTER',
        H: 'HALF_TIME',
        OT: 'OVER_TIME',
        F: 'FINAL',
        FO: 'FINAL_OVERTIME',
        T: 'TIE',
        P: 'UPCOMING'
    },

    defaults: {
        colored: false,
        helmets: false,
        focus_on: false,
        format: 'ddd h:mm',
        reloadInterval: 30 * 60 * 1000       // every 30 minutes
    },

    statistics: false,
    help: false,

    voice: {
        mode: 'FOOTBALL',
        sentences: [
            'OPEN HELP',
            'CLOSE HELP',
            'SHOW HELMETS',
            'SHOW LOGOS',
            'COLOR ON',
            'COLOR OFF',
            'SHOW PASSING YARDS STATISTIC',
            'SHOW RUSHING YARDS STATISTIC',
            'SHOW RECEIVING YARDS STATISTIC',
            'SHOW TACKLES STATISTIC',
            'SHOW SACKS STATISTIC',
            'SHOW INTERCEPTIONS STATISTIC',
            'HIDE STATISTIC'
        ]
    },

    getTranslations() {
        return {
            en: 'translations/en.json',
            de: 'translations/de.json'
        };
    },

    getScripts() {
        return ['moment.js'];
    },

    getStyles() {
        return ['font-awesome.css', 'MMM-NFL.css'];
    },

    start() {
        Log.info(`Starting module: ${this.name}`);
        this.sendSocketNotification('CONFIG', this.config);
        moment.locale(config.language);
    },

    notificationReceived(notification, payload, sender) {
        if (notification === 'ALL_MODULES_STARTED') {
            this.sendNotification('REGISTER_VOICE_MODULE', this.voice);
        } else if (notification === 'VOICE_FOOTBALL' && sender.name === 'MMM-voice') {
            this.checkCommands(payload);
        } else if (notification === 'VOICE_MODE_CHANGED' && sender.name === 'MMM-voice' && payload.old === this.voice.mode) {
            this.help = false;
            this.statistics = false;
            this.updateDom(300);
        }
    },

    socketNotificationReceived(notification, payload) {
        if (notification === 'SCORES') {
            this.scores = payload.scores;
            this.details = payload.details;
            this.updateDom(300);
        } else if (notification === 'STATISTICS') {
            this.help = false;
            this.statistics = payload;
            this.updateDom(300);
        }
    },

    checkCommands(data) {
        if (/(HELP)/g.test(data)) {
            if (/(CLOSE)/g.test(data) || (this.help && !/(OPEN)/g.test(data))) {
                this.help = false;
            } else if (/(OPEN)/g.test(data) || (!this.help && !/(CLOSE)/g.test(data))) {
                this.statistics = false;
                this.help = true;
            }
        } else if (/(HELMETS)/g.test(data)) {
            this.config.helmets = true;
        } else if (/(LOGOS)/g.test(data)) {
            this.config.helmets = false;
        } else if (/(COLOR)/g.test(data)) {
            if (/(OFF)/g.test(data) || (this.config.colored && !/(ON)/g.test(data))) {
                this.config.colored = false;
            } else if (/(ON)/g.test(data) || (!this.config.colored && !/(OFF)/g.test(data))) {
                this.config.colored = true;
            }
        } else if (/(STATISTIC)/g.test(data)) {
            if (/(HIDE)/g.test(data)) {
                this.statistics = false;
            } else if (/(PASSING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Passing Yards');
            } else if (/(RUSHING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Rushing Yards');
            } else if (/(RECEIVING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Receiving Yards');
            } else if (/(TACKLES)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Tackles');
            } else if (/(SACKS)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Sacks');
            } else if (/(INTERCEPTIONS)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Interceptions');
            }
        }
        this.updateDom(300);
    },

    getDom() {
        const wrapper = document.createElement('div');
        const scores = document.createElement('div');
        const header = document.createElement('header');
        header.innerHTML = `NFL ${this.modes[this.details.t] || this.details.t} ${this.details.y}`;
        scores.appendChild(header);

        if (!this.scores) {
            const text = document.createElement('div');
            text.innerHTML = this.translate('LOADING');
            text.classList.add('dimmed', 'light');
            scores.appendChild(text);
        } else {
            const table = document.createElement('table');
            table.classList.add('small', 'table');

            table.appendChild(this.createLabelRow());

            for (let i = 0; i < this.scores.length; i += 1) {
                this.appendDataRow(this.scores[i].$, table);
            }

            if (Array.isArray(this.config.focus_on)) {
                for (let i = 0; i < this.config.focus_on.length; i += 1) {
                    let hasMatch = false;
                    for (let n = 0; n < this.scores.length; n += 1) {
                        if (this.config.focus_on[i] === this.scores[n].$.h ||
                            this.config.focus_on[i] === this.scores[n].$.v) {
                            hasMatch = true;
                            break;
                        }
                    }
                    if (!hasMatch) {
                        this.appendByeWeek(this.config.focus_on[i], table);
                    }
                }
            }

            scores.appendChild(table);

            const modules = document.querySelectorAll('.module');
            for (let i = 0; i < modules.length; i += 1) {
                if (!modules[i].classList.contains('MMM-NFL')) {
                    if (this.statistics || this.help) {
                        modules[i].classList.add('MMM-NFL-blur');
                    } else {
                        modules[i].classList.remove('MMM-NFL-blur');
                    }
                }
            }

            if (this.statistics || this.help) {
                scores.classList.add('MMM-NFL-blur');
                const modal = document.createElement('div');
                modal.classList.add('modal');
                if (this.statistics) {
                    this.appendStatistics(modal);
                } else {
                    this.appendHelp(modal);
                }
                wrapper.appendChild(modal);
            }
        }

        wrapper.appendChild(scores);

        return wrapper;
    },

    createLabelRow() {
        const labelRow = document.createElement('tr');

        const dateLabel = document.createElement('th');
        const dateIcon = document.createElement('i');
        dateIcon.classList.add('fa', 'fa-calendar');
        dateLabel.appendChild(dateIcon);
        labelRow.appendChild(dateLabel);

        const homeLabel = document.createElement('th');
        homeLabel.innerHTML = this.translate('HOME');
        homeLabel.setAttribute('colspan', 3);
        labelRow.appendChild(homeLabel);

        const vsLabel = document.createElement('th');
        vsLabel.innerHTML = '';
        labelRow.appendChild(vsLabel);

        const awayLabel = document.createElement('th');
        awayLabel.innerHTML = this.translate('AWAY');
        awayLabel.setAttribute('colspan', 3);
        labelRow.appendChild(awayLabel);

        return labelRow;
    },

    appendDataRow(data, appendTo) {
        if (!this.config.focus_on || this.config.focus_on.indexOf(data.h) !== -1 ||
            this.config.focus_on.indexOf(data.v) !== -1) {
            const row = document.createElement('tr');
            row.classList.add('row');

            const date = document.createElement('td');
            if (data.q in ['1', '2', '3', '4', 'H', 'OT']) {
                const quarter = document.createElement('div');
                quarter.innerHTML = this.translate(this.states[data.q]);
                if (Object.prototype.hasOwnProperty.call(data, 'k')) {
                    quarter.classList.add('live');
                    date.appendChild(quarter);
                    const time = document.createElement('div');
                    time.classList.add('live');
                    time.innerHTML = `${data.k} ${this.translate('TIME_LEFT')}`;
                    date.appendChild(time);
                } else {
                    date.appendChild(quarter);
                }
            } else if (data.q === 'P') {
                date.innerHTML = moment(data.starttime).format(this.config.format);
            } else {
                date.innerHTML = this.translate(this.states[data.q]);
                date.classList.add('dimmed');
            }
            row.appendChild(date);

            const homeTeam = document.createElement('td');
            homeTeam.classList.add('align-right');
            this.appendBallPossession(data, true, homeTeam);
            const homeTeamSpan = document.createElement('span');
            homeTeamSpan.innerHTML = data.h;
            homeTeam.appendChild(homeTeamSpan);
            row.appendChild(homeTeam);

            const homeLogo = document.createElement('td');
            homeLogo.appendChild(this.createIcon(data.h));
            row.appendChild(homeLogo);

            const homeScore = document.createElement('td');
            homeScore.innerHTML = data.hs;
            row.appendChild(homeScore);

            const vs = document.createElement('td');
            vs.innerHTML = ':';
            row.appendChild(vs);

            const awayScore = document.createElement('td');
            awayScore.innerHTML = data.vs;
            row.appendChild(awayScore);

            const awayLogo = document.createElement('td');
            awayLogo.appendChild(this.createIcon(data.v, true));
            row.appendChild(awayLogo);

            const awayTeam = document.createElement('td');
            awayTeam.classList.add('align-left');
            const awayTeamSpan = document.createElement('span');
            awayTeamSpan.innerHTML = data.v;
            awayTeam.appendChild(awayTeamSpan);
            this.appendBallPossession(data, false, awayTeam);
            row.appendChild(awayTeam);

            appendTo.appendChild(row);
        }
    },

    appendBallPossession(data, homeTeam, appendTo) {
        const team = homeTeam ? data.h : data.v;
        if (data.p === team) {
            const ballIcon = document.createElement('img');
            ballIcon.src = this.file('icons/football.png');
            if (homeTeam) {
                ballIcon.classList.add('ball-home');
            } else {
                ballIcon.classList.add('ball-away');
            }
            if (data.rz === '1') {
                ballIcon.classList.add('redzone');
            }
            appendTo.appendChild(ballIcon);
        }
    },

    appendByeWeek(teamName, appendTo) {
        const row = document.createElement('tr');
        row.classList.add('row');

        const date = document.createElement('td');
        date.innerHTML = `${this.translate('WEEK')} ${this.details.w}`;
        row.appendChild(date);

        const team = document.createElement('td');
        team.innerHTML = teamName;
        row.appendChild(team);

        const logo = document.createElement('td');
        logo.appendChild(this.createIcon(teamName));
        row.appendChild(logo);

        const byeWeek = document.createElement('td');
        byeWeek.setAttribute('colspan', 5);
        byeWeek.classList.add('align-left');
        byeWeek.innerHTML = this.translate('BYE_WEEK');
        row.appendChild(byeWeek);

        appendTo.appendChild(row);
    },

    appendStatistics(appendTo) {
        const type = document.createElement('div');
        type.classList.add('large');
        type.innerHTML = this.statistics.type;
        appendTo.appendChild(type);

        const table = document.createElement('table');
        table.classList.add('medium', 'table');

        const labelRow = document.createElement('tr');

        const posLabel = document.createElement('th');
        posLabel.innerHTML = '#';
        labelRow.appendChild(posLabel);

        const playerLabel = document.createElement('th');
        playerLabel.innerHTML = this.translate('PLAYER');
        labelRow.appendChild(playerLabel);

        const teamLabel = document.createElement('th');
        teamLabel.setAttribute('colspan', 2);
        teamLabel.innerHTML = this.translate('TEAM');
        labelRow.appendChild(teamLabel);

        const unitLabel = document.createElement('th');
        unitLabel.innerHTML = this.statistics.data.unit;
        labelRow.appendChild(unitLabel);

        table.appendChild(labelRow);

        for (let i = 0; i < this.statistics.data.players.length; i += 1) {
            const row = document.createElement('tr');
            row.classList.add('row');

            const position = document.createElement('td');
            position.innerHTML = this.statistics.data.players[i].position;
            row.appendChild(position);

            const player = document.createElement('td');
            player.classList.add('align-left');
            player.innerHTML = this.statistics.data.players[i].player;
            row.appendChild(player);

            if (this.config.focus_on && this.config.focus_on.indexOf(this.statistics.data.players[i].team) !== -1) {
                row.classList.add('bright');
            }

            const teamName = document.createElement('td');
            teamName.innerHTML = this.statistics.data.players[i].team;
            row.appendChild(teamName);

            const team = document.createElement('td');
            team.appendChild(this.createIcon(this.statistics.data.players[i].team));
            row.appendChild(team);

            const value = document.createElement('td');
            value.innerHTML = this.statistics.data.players[i].value;
            row.appendChild(value);

            table.appendChild(row);
        }

        appendTo.appendChild(table);
    },

    appendHelp(appendTo) {
        const title = document.createElement('h1');
        title.classList.add('medium');
        title.innerHTML = `${this.name} - ${this.translate('COMMAND_LIST')}`;
        appendTo.appendChild(title);

        const mode = document.createElement('div');
        mode.innerHTML = `${this.translate('MODE')}: ${this.voice.mode}`;
        appendTo.appendChild(mode);

        const listLabel = document.createElement('div');
        listLabel.innerHTML = `${this.translate('VOICE_COMMANDS')}:`;
        appendTo.appendChild(listLabel);

        const list = document.createElement('ul');
        for (let i = 0; i < this.voice.sentences.length; i += 1) {
            const item = document.createElement('li');
            item.innerHTML = this.voice.sentences[i];
            list.appendChild(item);
        }
        appendTo.appendChild(list);
    },

    createIcon(team, flip = false) {
        const teamIcon = document.createElement('img');
        teamIcon.src = this.file(`icons/${team}${this.config.helmets ? '_helmet' : ''}.png`);
        if (!this.config.colored) {
            teamIcon.classList.add('icon');
        }
        if (flip && this.config.helmets) {
            teamIcon.classList.add('away');
        }
        return teamIcon;
    }
});
<commit_msg>config option to reverse teams<commit_after>/* global Module Log moment config */

/* Magic Mirror
 * Module: MMM-NFL
 *
 * By fewieden https://github.com/fewieden/MMM-NFL
 * MIT Licensed.
 */

Module.register('MMM-NFL', {

    modes: {
        P: 'Pre-Season',
        R: 'Regular-Season',
        POST: 'Post-Season'
    },

    details: {
        y: (new Date()).getFullYear(),
        t: 'P'
    },

    states: {
        1: '1ST_QUARTER',
        2: '2ND_QUARTER',
        3: '3RD_QUARTER',
        4: '4TH_QUARTER',
        H: 'HALF_TIME',
        OT: 'OVER_TIME',
        F: 'FINAL',
        FO: 'FINAL_OVERTIME',
        T: 'TIE',
        P: 'UPCOMING'
    },

    defaults: {
        colored: false,
        helmets: false,
        focus_on: false,
        format: 'ddd h:mm',
        reloadInterval: 30 * 60 * 1000,       // every 30 minutes
        reverseTeams: false
    },

    statistics: false,
    help: false,

    voice: {
        mode: 'FOOTBALL',
        sentences: [
            'OPEN HELP',
            'CLOSE HELP',
            'SHOW HELMETS',
            'SHOW LOGOS',
            'COLOR ON',
            'COLOR OFF',
            'SHOW PASSING YARDS STATISTIC',
            'SHOW RUSHING YARDS STATISTIC',
            'SHOW RECEIVING YARDS STATISTIC',
            'SHOW TACKLES STATISTIC',
            'SHOW SACKS STATISTIC',
            'SHOW INTERCEPTIONS STATISTIC',
            'HIDE STATISTIC'
        ]
    },

    getTranslations() {
        return {
            en: 'translations/en.json',
            de: 'translations/de.json'
        };
    },

    getScripts() {
        return ['moment.js'];
    },

    getStyles() {
        return ['font-awesome.css', 'MMM-NFL.css'];
    },

    start() {
        Log.info(`Starting module: ${this.name}`);
        this.sendSocketNotification('CONFIG', this.config);
        moment.locale(config.language);
    },

    notificationReceived(notification, payload, sender) {
        if (notification === 'ALL_MODULES_STARTED') {
            this.sendNotification('REGISTER_VOICE_MODULE', this.voice);
        } else if (notification === 'VOICE_FOOTBALL' && sender.name === 'MMM-voice') {
            this.checkCommands(payload);
        } else if (notification === 'VOICE_MODE_CHANGED' && sender.name === 'MMM-voice' && payload.old === this.voice.mode) {
            this.help = false;
            this.statistics = false;
            this.updateDom(300);
        }
    },

    socketNotificationReceived(notification, payload) {
        if (notification === 'SCORES') {
            this.scores = payload.scores;
            this.details = payload.details;
            this.updateDom(300);
        } else if (notification === 'STATISTICS') {
            this.help = false;
            this.statistics = payload;
            this.updateDom(300);
        }
    },

    checkCommands(data) {
        if (/(HELP)/g.test(data)) {
            if (/(CLOSE)/g.test(data) || (this.help && !/(OPEN)/g.test(data))) {
                this.help = false;
            } else if (/(OPEN)/g.test(data) || (!this.help && !/(CLOSE)/g.test(data))) {
                this.statistics = false;
                this.help = true;
            }
        } else if (/(HELMETS)/g.test(data)) {
            this.config.helmets = true;
        } else if (/(LOGOS)/g.test(data)) {
            this.config.helmets = false;
        } else if (/(COLOR)/g.test(data)) {
            if (/(OFF)/g.test(data) || (this.config.colored && !/(ON)/g.test(data))) {
                this.config.colored = false;
            } else if (/(ON)/g.test(data) || (!this.config.colored && !/(OFF)/g.test(data))) {
                this.config.colored = true;
            }
        } else if (/(STATISTIC)/g.test(data)) {
            if (/(HIDE)/g.test(data)) {
                this.statistics = false;
            } else if (/(PASSING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Passing Yards');
            } else if (/(RUSHING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Rushing Yards');
            } else if (/(RECEIVING)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Receiving Yards');
            } else if (/(TACKLES)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Tackles');
            } else if (/(SACKS)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Sacks');
            } else if (/(INTERCEPTIONS)/g.test(data)) {
                this.sendSocketNotification('GET_STATISTICS', 'Interceptions');
            }
        }
        this.updateDom(300);
    },

    getDom() {
        const wrapper = document.createElement('div');
        const scores = document.createElement('div');
        const header = document.createElement('header');
        header.innerHTML = `NFL ${this.modes[this.details.t] || this.details.t} ${this.details.y}`;
        scores.appendChild(header);

        if (!this.scores) {
            const text = document.createElement('div');
            text.innerHTML = this.translate('LOADING');
            text.classList.add('dimmed', 'light');
            scores.appendChild(text);
        } else {
            const table = document.createElement('table');
            table.classList.add('small', 'table');

            table.appendChild(this.createLabelRow());

            for (let i = 0; i < this.scores.length; i += 1) {
                this.appendDataRow(this.scores[i].$, table);
            }

            if (Array.isArray(this.config.focus_on)) {
                for (let i = 0; i < this.config.focus_on.length; i += 1) {
                    let hasMatch = false;
                    for (let n = 0; n < this.scores.length; n += 1) {
                        if (this.config.focus_on[i] === this.scores[n].$.h ||
                            this.config.focus_on[i] === this.scores[n].$.v) {
                            hasMatch = true;
                            break;
                        }
                    }
                    if (!hasMatch) {
                        this.appendByeWeek(this.config.focus_on[i], table);
                    }
                }
            }

            scores.appendChild(table);

            const modules = document.querySelectorAll('.module');
            for (let i = 0; i < modules.length; i += 1) {
                if (!modules[i].classList.contains('MMM-NFL')) {
                    if (this.statistics || this.help) {
                        modules[i].classList.add('MMM-NFL-blur');
                    } else {
                        modules[i].classList.remove('MMM-NFL-blur');
                    }
                }
            }

            if (this.statistics || this.help) {
                scores.classList.add('MMM-NFL-blur');
                const modal = document.createElement('div');
                modal.classList.add('modal');
                if (this.statistics) {
                    this.appendStatistics(modal);
                } else {
                    this.appendHelp(modal);
                }
                wrapper.appendChild(modal);
            }
        }

        wrapper.appendChild(scores);

        return wrapper;
    },

    createLabelRow() {
        const labelRow = document.createElement('tr');

        const dateLabel = document.createElement('th');
        const dateIcon = document.createElement('i');
        dateIcon.classList.add('fa', 'fa-calendar');
        dateLabel.appendChild(dateIcon);
        labelRow.appendChild(dateLabel);

        const firstLabel = document.createElement('th');
        firstLabel.innerHTML = this.translate(this.config.reverseTeams ? 'AWAY' : 'HOME');
        firstLabel.setAttribute('colspan', 3);
        labelRow.appendChild(firstLabel);

        const vsLabel = document.createElement('th');
        vsLabel.innerHTML = '';
        labelRow.appendChild(vsLabel);

        const secondLabel = document.createElement('th');
        secondLabel.innerHTML = this.translate(this.config.reverseTeams ? 'HOME' : 'AWAY');
        secondLabel.setAttribute('colspan', 3);
        labelRow.appendChild(secondLabel);

        return labelRow;
    },

    appendDataRow(data, appendTo) {
        if (!this.config.focus_on || this.config.focus_on.indexOf(data.h) !== -1 ||
            this.config.focus_on.indexOf(data.v) !== -1) {
            const row = document.createElement('tr');
            row.classList.add('row');

            const date = document.createElement('td');
            if (data.q in ['1', '2', '3', '4', 'H', 'OT']) {
                const quarter = document.createElement('div');
                quarter.innerHTML = this.translate(this.states[data.q]);
                if (Object.prototype.hasOwnProperty.call(data, 'k')) {
                    quarter.classList.add('live');
                    date.appendChild(quarter);
                    const time = document.createElement('div');
                    time.classList.add('live');
                    time.innerHTML = `${data.k} ${this.translate('TIME_LEFT')}`;
                    date.appendChild(time);
                } else {
                    date.appendChild(quarter);
                }
            } else if (data.q === 'P') {
                date.innerHTML = moment(data.starttime).format(this.config.format);
            } else {
                date.innerHTML = this.translate(this.states[data.q]);
                date.classList.add('dimmed');
            }
            row.appendChild(date);

            const firstTeam = document.createElement('td');
            firstTeam.classList.add('align-right');
            this.appendBallPossession(data, true, firstTeam);
            const firstTeamSpan = document.createElement('span');
            firstTeamSpan.innerHTML = data[this.config.reverseTeams ? 'v' : 'h'];
            firstTeam.appendChild(firstTeamSpan);
            row.appendChild(firstTeam);

            const firstLogo = document.createElement('td');
            firstLogo.appendChild(this.createIcon(data[this.config.reverseTeams ? 'v' : 'h']));
            row.appendChild(firstLogo);

            const firstScore = document.createElement('td');
            firstScore.innerHTML = data[this.config.reverseTeams ? 'vs' : 'hs'];
            row.appendChild(firstScore);

            const vs = document.createElement('td');
            vs.innerHTML = ':';
            row.appendChild(vs);

            const secondScore = document.createElement('td');
            secondScore.innerHTML = data[this.config.reverseTeams ? 'hs' : 'vs'];
            row.appendChild(secondScore);

            const secondLogo = document.createElement('td');
            secondLogo.appendChild(this.createIcon(data[this.config.reverseTeams ? 'h' : 'v'], true));
            row.appendChild(secondLogo);

            const secondTeam = document.createElement('td');
            secondTeam.classList.add('align-left');
            const secondTeamSpan = document.createElement('span');
            secondTeamSpan.innerHTML = data[this.config.reverseTeams ? 'h' : 'v'];
            secondTeam.appendChild(secondTeamSpan);
            this.appendBallPossession(data, false, secondTeam);
            row.appendChild(secondTeam);

            appendTo.appendChild(row);
        }
    },

    appendBallPossession(data, homeTeam, appendTo) {
        const team = homeTeam ? data.h : data.v;
        if (data.p === team) {
            const ballIcon = document.createElement('img');
            ballIcon.src = this.file('icons/football.png');
            if (homeTeam) {
                ballIcon.classList.add('ball-home');
            } else {
                ballIcon.classList.add('ball-away');
            }
            if (data.rz === '1') {
                ballIcon.classList.add('redzone');
            }
            appendTo.appendChild(ballIcon);
        }
    },

    appendByeWeek(teamName, appendTo) {
        const row = document.createElement('tr');
        row.classList.add('row');

        const date = document.createElement('td');
        date.innerHTML = `${this.translate('WEEK')} ${this.details.w}`;
        row.appendChild(date);

        const team = document.createElement('td');
        team.innerHTML = teamName;
        row.appendChild(team);

        const logo = document.createElement('td');
        logo.appendChild(this.createIcon(teamName));
        row.appendChild(logo);

        const byeWeek = document.createElement('td');
        byeWeek.setAttribute('colspan', 5);
        byeWeek.classList.add('align-left');
        byeWeek.innerHTML = this.translate('BYE_WEEK');
        row.appendChild(byeWeek);

        appendTo.appendChild(row);
    },

    appendStatistics(appendTo) {
        const type = document.createElement('div');
        type.classList.add('large');
        type.innerHTML = this.statistics.type;
        appendTo.appendChild(type);

        const table = document.createElement('table');
        table.classList.add('medium', 'table');

        const labelRow = document.createElement('tr');

        const posLabel = document.createElement('th');
        posLabel.innerHTML = '#';
        labelRow.appendChild(posLabel);

        const playerLabel = document.createElement('th');
        playerLabel.innerHTML = this.translate('PLAYER');
        labelRow.appendChild(playerLabel);

        const teamLabel = document.createElement('th');
        teamLabel.setAttribute('colspan', 2);
        teamLabel.innerHTML = this.translate('TEAM');
        labelRow.appendChild(teamLabel);

        const unitLabel = document.createElement('th');
        unitLabel.innerHTML = this.statistics.data.unit;
        labelRow.appendChild(unitLabel);

        table.appendChild(labelRow);

        for (let i = 0; i < this.statistics.data.players.length; i += 1) {
            const row = document.createElement('tr');
            row.classList.add('row');

            const position = document.createElement('td');
            position.innerHTML = this.statistics.data.players[i].position;
            row.appendChild(position);

            const player = document.createElement('td');
            player.classList.add('align-left');
            player.innerHTML = this.statistics.data.players[i].player;
            row.appendChild(player);

            if (this.config.focus_on && this.config.focus_on.indexOf(this.statistics.data.players[i].team) !== -1) {
                row.classList.add('bright');
            }

            const teamName = document.createElement('td');
            teamName.innerHTML = this.statistics.data.players[i].team;
            row.appendChild(teamName);

            const team = document.createElement('td');
            team.appendChild(this.createIcon(this.statistics.data.players[i].team));
            row.appendChild(team);

            const value = document.createElement('td');
            value.innerHTML = this.statistics.data.players[i].value;
            row.appendChild(value);

            table.appendChild(row);
        }

        appendTo.appendChild(table);
    },

    appendHelp(appendTo) {
        const title = document.createElement('h1');
        title.classList.add('medium');
        title.innerHTML = `${this.name} - ${this.translate('COMMAND_LIST')}`;
        appendTo.appendChild(title);

        const mode = document.createElement('div');
        mode.innerHTML = `${this.translate('MODE')}: ${this.voice.mode}`;
        appendTo.appendChild(mode);

        const listLabel = document.createElement('div');
        listLabel.innerHTML = `${this.translate('VOICE_COMMANDS')}:`;
        appendTo.appendChild(listLabel);

        const list = document.createElement('ul');
        for (let i = 0; i < this.voice.sentences.length; i += 1) {
            const item = document.createElement('li');
            item.innerHTML = this.voice.sentences[i];
            list.appendChild(item);
        }
        appendTo.appendChild(list);
    },

    createIcon(team, flip = false) {
        const teamIcon = document.createElement('img');
        teamIcon.src = this.file(`icons/${team}${this.config.helmets ? '_helmet' : ''}.png`);
        if (!this.config.colored) {
            teamIcon.classList.add('icon');
        }
        if (flip && this.config.helmets) {
            teamIcon.classList.add('away');
        }
        return teamIcon;
    }
});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
'use strict';

var resolve = require('path').resolve,
    log = require('./lib/log'),
    getopts = require('./lib/getopts'),
    getenv = require('./lib/getenv');

/**
 * wrap `require` in a try/catch with some debug logging
 * @param {string} str require pathname
 * @return {object|false} module returned by `require`, or false
 */
function tryRequire(str) {
    var mod = false;
    try {
        mod = require(str);
        log.debug('required %s', str);
    } catch(err) {
        if (('MODULE_NOT_FOUND' === err.code) && ~err.message.indexOf(str)) {
            // could not find the module using "str"
            log.debug('module %s not found', str);
        } else {
            // module was loaded, and threw an exception
            log.debug('module error', err);
        }
    }
    return mod;
}

/**
 * try to require a module for use as a subcommand by looking in these places:
 * - require’ing the command-key value from the commands hash in config.json
 * - require’ing 'mojito-cli-' + cmd
 * - require’ing the command name from the mojito library path
 * @param {string} sub-command, like 'help', 'create', etc.
 * @param {object} cli, app, and mojito, metadata
 * @return {object|function} module loaded via `require`
 */
function load(cmd, env) {
    var mod = false,
        mo_cmd_list = env.mojito && env.mojito.commands,
        mo_cmd_path = mo_cmd_list && env.mojito.commandsPath;

    // command path is registered in ./config.js:commands hash
    if (env.cli.commands.hasOwnProperty(cmd)) {
        mod = tryRequire(env.cli.commands[cmd]);

    } else {
        // command is requireable, presumably in $NODE_PATH `npm root -g`
        // http://nodejs.org/api/modules.html#modules_all_together
        mod = tryRequire('mojito-cli-' + cmd);

        // command is in local mojito commands path (BC)
        if (!mod && mo_cmd_list && (mo_cmd_list.indexOf(cmd) > -1)) {
            mod = tryRequire(resolve(mo_cmd_path, cmd));
        }
    }

    return mod;
}

/**
 * invoke subcommand with env metadata and callback
 * @param {object} env
 *   @param {string} command, the first non-option cli arg (i.e. "create")
 *   @param {array} args command line arguments (see getopts.js)
 *   @param {object} opts command line options (see getopts.js)
 *   @param {array} orig the argv array originaly passed to index.js
 *   @param {string} cwd absolute path to current working directory
 *   @param {object} cli metadata (see getenv.js:cli())
 *   @param {object|false} app metadata (see getenv.js:read())
 *   @param {object|false} mojito metadata (see getenv.js:mojito())
 * @param {function(err, msg)} callback
 */
function exec(env, cb) {
    var mod = load(env.command, env);

    // re-parse command line arguments; may modify env.args & env.opts
    getopts.redux(env, mod.options);

    // display with --log silly, too verbose for --debug
    log.silly('env:', env);

    if (mod && mod.hasOwnProperty('run')) {
        log.debug('invoking legacy command %s', env.command);
        mod.run(env.args, env.opts, cb);

    } else if ('function' === typeof mod) {
        log.debug('invoking command %s', env.command);
        mod(env, cb);

    } else {
        cb('Unable to invoke command ' + env.command);
    }
}

/**
 * gather configs and metadata in env object, and call exec
 * @param {array} argv command line arguments process.argv.slice(2)
 * @param {string} cwd current working directory process.cwd()
 * @param {function(err, msg)} callback
 * @return {string} subcommand name
 */
function main(argv, cwd, cb) {
    var cli = getenv.cli(__dirname),
        env = getopts(argv, cli.options), // {command:"…", args:{…}, opts:{…}}
        lib = env.opts.libmojito; // alternate path to mojito library

    if (env.opts.loglevel) {
        log.level = env.opts.loglevel;
        log.silly('logging level set to', env.opts.loglevel);
    }

    if (!env.command) {
        log.error('No command.');
        env.command = 'help';
    }

    // apply command alias, i.e. "docs" -> "doc"
    if (!(env.command in cli.commands) && (env.command in cli.aliases)) {
        env.command = cli.aliases[env.command];
    }

    // collect parsed args and env metadata
    env.cwd = cwd;
    env.cli = cli;
    env.app = (cwd !== __dirname) && getenv(cwd);
    env.mojito = (env.app || lib) && getenv.mojito(cwd, lib);

    exec(env, cb);
    return env.command;
}

module.exports = main;
module.exports.load = load; // help.js
<commit_msg>test property truthyness instead of `in`<commit_after>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
'use strict';

var resolve = require('path').resolve,
    log = require('./lib/log'),
    getopts = require('./lib/getopts'),
    getenv = require('./lib/getenv');

/**
 * wrap `require` in a try/catch with some debug logging
 * @param {string} str require pathname
 * @return {object|false} module returned by `require`, or false
 */
function tryRequire(str) {
    var mod = false;
    try {
        mod = require(str);
        log.debug('required %s', str);
    } catch(err) {
        if (('MODULE_NOT_FOUND' === err.code) && ~err.message.indexOf(str)) {
            // could not find the module using "str"
            log.debug('module %s not found', str);
        } else {
            // module was loaded, and threw an exception
            log.debug('module error', err);
        }
    }
    return mod;
}

/**
 * try to require a module for use as a subcommand by looking in these places:
 * - require’ing the command-key value from the commands hash in config.json
 * - require’ing 'mojito-cli-' + cmd
 * - require’ing the command name from the mojito library path
 * @param {string} sub-command, like 'help', 'create', etc.
 * @param {object} cli, app, and mojito, metadata
 * @return {object|function} module loaded via `require`
 */
function load(cmd, env) {
    var mod = false,
        mo_cmd_list = env.mojito && env.mojito.commands,
        mo_cmd_path = mo_cmd_list && env.mojito.commandsPath;

    // command path is registered in ./config.js:commands hash
    if (env.cli.commands.hasOwnProperty(cmd)) {
        mod = tryRequire(env.cli.commands[cmd]);

    } else {
        // command is requireable, presumably in $NODE_PATH `npm root -g`
        // http://nodejs.org/api/modules.html#modules_all_together
        mod = tryRequire('mojito-cli-' + cmd);

        // command is in local mojito commands path (BC)
        if (!mod && mo_cmd_list && (mo_cmd_list.indexOf(cmd) > -1)) {
            mod = tryRequire(resolve(mo_cmd_path, cmd));
        }
    }

    return mod;
}

/**
 * invoke subcommand with env metadata and callback
 * @param {object} env
 *   @param {string} command, the first non-option cli arg (i.e. "create")
 *   @param {array} args command line arguments (see getopts.js)
 *   @param {object} opts command line options (see getopts.js)
 *   @param {array} orig the argv array originaly passed to index.js
 *   @param {string} cwd absolute path to current working directory
 *   @param {object} cli metadata (see getenv.js:cli())
 *   @param {object|false} app metadata (see getenv.js:read())
 *   @param {object|false} mojito metadata (see getenv.js:mojito())
 * @param {function(err, msg)} callback
 */
function exec(env, cb) {
    var mod = load(env.command, env);

    // re-parse command line arguments; may modify env.args & env.opts
    getopts.redux(env, mod.options);

    // display with --log silly, too verbose for --debug
    log.silly('env:', env);

    if (mod && mod.hasOwnProperty('run')) {
        log.debug('invoking legacy command %s', env.command);
        mod.run(env.args, env.opts, cb);

    } else if ('function' === typeof mod) {
        log.debug('invoking command %s', env.command);
        mod(env, cb);

    } else {
        cb('Unable to invoke command ' + env.command);
    }
}

/**
 * gather configs and metadata in env object, and call exec
 * @param {array} argv command line arguments process.argv.slice(2)
 * @param {string} cwd current working directory process.cwd()
 * @param {function(err, msg)} callback
 * @return {string} subcommand name
 */
function main(argv, cwd, cb) {
    var cli = getenv.cli(__dirname),
        env = getopts(argv, cli.options), // {command:"…", args:{…}, opts:{…}}
        lib = env.opts.libmojito; // alternate path to mojito library

    if (env.opts.loglevel) {
        log.level = env.opts.loglevel;
        log.silly('logging level set to', env.opts.loglevel);
    }

    if (!env.command) {
        log.error('No command.');
        env.command = 'help';
    }

    // apply command alias, i.e. "docs" -> "doc"
    if (!cli.commands[env.command] && cli.aliases[env.command]) {
        env.command = cli.aliases[env.command];
    }

    // collect parsed args and env metadata
    env.cwd = cwd;
    env.cli = cli;
    env.app = (cwd !== __dirname) && getenv(cwd);
    env.mojito = (env.app || lib) && getenv.mojito(cwd, lib);

    exec(env, cb);
    return env.command;
}

module.exports = main;
module.exports.load = load; // help.js
<|endoftext|>