code/hooks.js

'use strict';

/**
 * HooksManager
 * @namespace HooksManager
 * @description Manage Hooks registration/loading
 */

const config = require('./config');
const logger = require('./logger');

const Promise = require('promise');
const escape_string_regexp = require('escape-string-regexp');
const _ = require('underscore');
const s = require('underscore.string');
_.mixin(s.exports());

const path = require('path');
const dir = path.resolve(__dirname, '..');
let hooks_dir = process.env.TEL_HOOKS_DIR || path.isAbsolute(config.get('hooks:folder')) ? config.get('hooks:folder') : path.resolve(dir, config.get('hooks:folder'));

let hooks_cache = [];

/**
 * @function work_hook
 * @description Validates and extends passed Hook
 * @static
 * @param {Object} hook_def Hook reference
 * @param {String} hook_path Hook path relative to hooks_dir
 * @memberof HooksManager
 * @private
 * @returns {Object|Boolean}
 */

const work_hook = function (hook_def, hook_path) {
  if (hook_def) {
    if (_.isArray(hook_def)) {
      let out_array = _.map(hook_def, function (inner_hook_def) {
        return work_hook(inner_hook_def, hook_path);
      });
      return out_array;
    }

    if (((hook_def.match || hook_def.command) && (hook_def.action || hook_def.shell || hook_def.signal)) || (hook_def.route) || (hook_def.exec) || (hook_def.gpio) || (hook_def.parse_response) || (hook_def.check) || (hook_def.start_monitor && hook_def.stop_monitor)) {
      hook_def.path = hook_path;
      hook_def.namespace = hook_def.namespace || path.dirname(hook_path) || 'default';
      hook_def.name = hook_def.name || path.basename(hook_path, path.extname(hook_path));
      hook_def.full_name = `${_.underscored(_.slugify(hook_def.namespace))}/${_.underscored(_.slugify(hook_def.name))}`;
      hook_def.route_path = hook_def.route ? (hook_def.route_path || _.replaceAll(hook_def.full_name.toLowerCase(), '_', '/')) : null;
      hook_def.cmd_name = hook_def.exec ? (hook_def.cmd_name || _.replaceAll(_.replaceAll(hook_def.full_name.toLowerCase(), '_', ':'), '/', ':')) : null;

      hook_def.has_monitor_hook = _.isFunction(hook_def.check) || (_.isFunction(hook_def.start_monitor) && _.isFunction(hook_def.stop_monitor)) || (_.isObject(hook_def.gpio) && _.isFunction(hook_def.gpio.handler));
      hook_def.has_local_hook = (_.isFunction(hook_def.signal) || _.isArray(hook_def.signal)) || _.isString(hook_def.shell) || _.isFunction(hook_def.action) || _.isString(hook_def.action) || _.isFunction(hook_def.parse_response);
      hook_def.has_web_hook = _.isFunction(hook_def.route);
      hook_def.has_command_line_hook = _.isFunction(hook_def.exec) || _.isString(hook_def.exec);

      if (hook_def.parse_response && !hook_def.action) {
        hook_def.action = true;
      }

      if (hook_def.command) {
        if (hook_def.command.indexOf('/') === -1) {
          hook_def.command = `/${hook_def.command}`;
        }
        hook_def.command = hook_def.command.toLowerCase();
        hook_def.match = new RegExp(`${escape_string_regexp(hook_def.command)}\s*(.*)`, 'i');
      }

      if (hook_def.match && _.isString(hook_def.match)) {
        hook_def.match = new RegExp(escape_string_regexp(hook_def.match), 'im');
      }

      return hook_def;
    }
  }
  return false;
};

/**
 * @class
 * @classdesc Manage Hooks registration/loading
 */

const HooksManager = {
  /**
   * @function get_hooks_dir
   * @description Return hooks dir
   * @static
   * @memberof HooksManager
   * @public
   * @returns {String}
   */
  get_hooks_dir: function () {
    return hooks_dir;
  },

  /**
   * @function get_hooks
   * @description Return loaded hooks, filtering and/or grouping them if required
   * @param {String} filter_by Filter hooks by this field
   * @param {String} group_by Group returned hook by this field
   * @static
   * @memberof HooksManager
   * @public
   * @returns {Object|Array}
   */

  get_hooks: function (filter_by, group_by) {
    let out_val = hooks_cache;

    if (filter_by) {
      out_val = _.filter(out_val, function (hook) {
        return !!(hook[filter_by]);
      });
    }

    if (group_by) {
      out_val = _.indexBy(_.sortBy(out_val, group_by), group_by);
    }

    return out_val;
  },

  /**
   * @function get_commands
   * @description Return local hooks
   * @static
   * @memberof HooksManager
   * @public
   * @returns {Object[]}
   */

  get_commands: function () {
    let cmd_hooks = HooksManager.get_hooks('has_local_hook');
    let out = [];
    _.each(cmd_hooks, function (el) {
      if (el.command) {
        out.push({
          command: el.command,
          description: el.description
        });
      }
    });
    return out;
  },
  /**
   * @function reload
   * @description Load hooks from directory
   * @static
   * @memberof HooksManager
   * @public
   * @returns {Promise}
   */
  reload: function (new_hooks_dir) {
    if (new_hooks_dir) {
      hooks_dir = new_hooks_dir;
    }
    logger.log(`Reloading hooks`);
    hooks_cache = [];
    return HooksManager.load();
  },
  /**
   * @function load
   * @description Load hooks from directory or cache
   * @static
   * @memberof HooksManager
   * @public
   * @returns {Promise}
   */
  load: function () {
    if (hooks_cache.length > 0) {
      return Promise.resolve();
    }

    return new Promise(function (resolve, reject) {
      const glob = require('glob');
      const options = {
        cwd: hooks_dir,
        root: hooks_dir,
        ignore: [
          'node_modules/*.{js,json}',
          'node_modules/**/*.{js,json}',
          '**/node_modules/*.{js,json}',
          '**/node_modules/**/*.{js,json}'
        ]
      };
      const hooks_pattern = '**/*.{js,json}';

      glob(hooks_pattern, options, function (error, matches) {
        if (error) {
          return reject(error);
        }
        matches = matches || [];
        hooks_cache = _.compact(_.flatten(_.map(matches, function (hook_path) {
          let hook_complete_path = path.resolve(hooks_dir, hook_path);
          let hook_def = require(hook_complete_path);

          return work_hook(hook_def, hook_path);
        })));
        resolve();
      });
    });
  }
};

module.exports = HooksManager;