<commit_before>'use strict';
var assert = require('assert');
var minimatch = require('minimatch');
var glob = require('glob');
var path = require('path');
var slash = require('slash');
var fs = require('fs');

var defaultOptions = {
  publicPath: true,
  hash: false,
  jsExtensions: ['.js'],
  cssExtensions: ['.css']
};

function isObject (v) {
  return v !== null && v !== undefined && typeof v === 'object' && !isArray(v);
}

function isBoolean (v) {
  return v === true || v === false;
}

function isString (v) {
  return v !== null && v !== undefined && (typeof v === 'string' || v instanceof String);
}

function isArray (v) {
  return Array.isArray(v);
}

function hasExtension (v, ending) {
  if (v.indexOf('?') !== -1) { // Remove anything after `?`
    v = v.substr(0, v.indexOf('?'));
  }
  var lastIndex = v.lastIndexOf(ending);
  return lastIndex !== -1 && lastIndex === v.length - ending.length;
}

function hasExtensions (v, extensions) {
  var found = false;
  var i;
  var count = extensions.length;
  for (i = 0; i < count; i++) {
    found = hasExtension(v, extensions[i]);
    if (found) {
      break;
    }
  }
  return found;
}

function isOneOf (v, values) {
  return values.indexOf(v) !== -1;
}

function forOwn (object, iterator) {
  var properties = Object.keys(object);
  var propertyCount = properties.length;
  var property;

  for (var i = 0; i < propertyCount; i++) {
    property = properties[i];
    iterator(object[property], property, object);
  }
}

function extend (target, source) {
  forOwn(source, function (value, property) {
    target[property] = value;
  });

  return target;
}

function HtmlWebpackIncludeAssetsPlugin (options) {
  assert(isObject(options), 'HtmlWebpackIncludeAssetsPlugin options are required');
  var assets;
  if (isString(options.assets) || isObject(options.assets)) {
    assets = [options.assets];
  } else {
    assets = options.assets;
  }
  assert(isArray(assets), 'HtmlWebpackIncludeAssetsPlugin options must have an assets key with an array or string value');
  var jsExtensions;
  if (options.jsExtensions !== undefined) {
    if (isString(options.jsExtensions)) {
      jsExtensions = [options.jsExtensions];
    } else {
      jsExtensions = options.jsExtensions;
      assert(isArray(jsExtensions), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key should be a string or array of strings (' + jsExtensions + ')');
      var jsExtensionCount = jsExtensions.length;
      var jsExtension;
      for (var j = 0; j < jsExtensionCount; j++) {
        jsExtension = jsExtensions[j];
        assert(isString(jsExtension), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key array should not contain non-strings (' + jsExtension + ')');
      }
    }
  } else {
    jsExtensions = defaultOptions.jsExtensions;
  }
  var cssExtensions;
  if (options.cssExtensions !== undefined) {
    if (isString(options.cssExtensions)) {
      cssExtensions = [options.cssExtensions];
    } else {
      cssExtensions = options.cssExtensions;
      assert(isArray(cssExtensions), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key should be a string or array of strings (' + cssExtensions + ')');
      var cssExtensionCount = cssExtensions.length;
      var cssExtension;
      for (var c = 0; c < cssExtensionCount; c++) {
        cssExtension = cssExtensions[c];
        assert(isString(cssExtension), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key array should not contain non-strings (' + cssExtension + ')');
      }
    }
  } else {
    cssExtensions = defaultOptions.cssExtensions;
  }
  var assetCount = assets.length;
  var asset;
  for (var i = 0; i < assetCount; i++) {
    asset = assets[i];
    if (isString(asset)) {
      assert(hasExtensions(asset, jsExtensions) || hasExtensions(asset, cssExtensions),
        'HtmlWebpackIncludeAssetsPlugin options assets key array should only contain strings ending with the js or css extensions (' + asset + ')');
    } else if (isObject(asset)) {
      assert(isString(asset.path),
        'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property must be a string (' + asset.path + ')');
      if (asset.glob !== undefined) {
        assert(isString(asset.glob),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should be a string (' + asset.glob + ')');
      }
      if (asset.globPath !== undefined) {
        assert(isString(asset.globPath),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects globPath property should be a string (' + asset.globPath + ')');
      }
      if (asset.type !== undefined) {
        assert(isOneOf(asset.type, ['js', 'css']),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects type property should be a string set to either `js` or `css` (' + asset.type + ')');
      } else {
        if (asset.glob !== undefined) {
          assert(hasExtensions(asset.glob, jsExtensions) || hasExtensions(asset.glob, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        } else {
          assert(hasExtensions(asset.path, jsExtensions) || hasExtensions(asset.path, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        }
      }
      if (asset.attributes !== undefined) {
        assert(isObject(asset.attributes), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object');
        forOwn(asset.attributes, function (value) {
          assert(isString(value), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object with string values');
        });
      }
    } else {
      assert(false, 'HtmlWebpackIncludeAssetsPlugin options assets key array must contain only strings and objects (' + asset + ')');
    }
  }
  assert(isBoolean(options.append), 'HtmlWebpackIncludeAssetsPlugin options must have an append key with a boolean value');
  var publicPath;
  if (options.publicPath !== undefined) {
    assert(isBoolean(options.publicPath) || isString(options.publicPath),
      'HtmlWebpackIncludeAssetsPlugin options should specify a publicPath that is either a boolean or a string');
    publicPath = options.publicPath;
  } else {
    publicPath = defaultOptions.publicPath;
  }
  var hash;
  if (options.hash !== undefined) {
    assert(isBoolean(options.hash), 'HtmlWebpackIncludeAssetsPlugin options should specify a hash key with a boolean value');
    hash = options.hash;
  } else {
    hash = defaultOptions.hash;
  }
  var files;
  if (isString(options.files)) {
    files = [options.files];
  } else {
    files = options.files;
  }
  if (files !== undefined) {
    assert(isArray(files), 'HtmlWebpackIncludeAssetsPlugin options should specify a files key with an array or string value');
    var fileCount = files.length;
    var file;
    for (var f = 0; f < fileCount; f++) {
      file = files[f];
      assert(isString(file),
        'HtmlWebpackIncludeAssetsPlugin options files key array must contain only strings (' + file + ')');
    }
  }
  this.options = {
    assets: assets,
    jsExtensions: jsExtensions,
    cssExtensions: cssExtensions,
    append: options.append,
    publicPath: publicPath,
    hash: hash,
    files: files
  };
}

HtmlWebpackIncludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  function onCompilation (compilation) {
    var shouldSkip = function (htmlPluginData) {
      var files = self.options.files;
      return files !== undefined && !files.some(function (file) {
        return minimatch(htmlPluginData.outputName, file);
      });
    };

    var defaultPublicPath;
    var getAssetPath = function (includeAssetPath) {
      var publicPath = self.options.publicPath;
      var hash = self.options.hash;
      var includeAssetPrefix = publicPath === true ? defaultPublicPath : isString(publicPath) ? publicPath : '';
      var includeAssetHash = hash === true ? ('?' + compilation.hash) : '';
      return path.resolve(includeAssetPrefix + includeAssetPath + includeAssetHash);
    };

    function onBeforeHtmlGeneration (htmlPluginData, callback) {
      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      var includeAssets = self.options.assets;
      var jsExtensions = self.options.jsExtensions;
      var cssExtensions = self.options.cssExtensions;
      var appendAssets = self.options.append;
      var assets = htmlPluginData.assets;
      defaultPublicPath = assets.publicPath;

      var includeAsset;
      var includeAssetString;
      var includeAssetPaths;
      var includeAssetCount;
      var includeAssetPath;
      var includeAssetType;
      var includeCount = includeAssets.length;
      var rawData = {};
      var jsAssets = [];
      var cssAssets = [];
      for (var i = 0; i < includeCount; i++) {
        includeAsset = includeAssets[i];
        if (isObject(includeAsset)) {
          includeAssetType = includeAsset.type;
          if (includeAsset.glob === undefined) {
            includeAssetPaths = [includeAsset.path];
          } else {
            var cwd = includeAsset.globPath !== undefined ? includeAsset.globPath : path.join(compiler.options.output.path, includeAsset.path);
            var globOptions = {cwd: cwd};
                    // assets will be an array of strings with all matching asset file names
            includeAssetPaths = glob.sync(includeAsset.glob, globOptions).map(
                        function (globAsset) {
                          var icPath = slash(path.join(includeAsset.path, globAsset));
                          var fullPath = path.join(cwd, globAsset);
                          rawData[icPath] = {
                            fullPath: fullPath,
                            includeAssetPath: icPath,
                            assetPath: globAsset,
                            source: fs.readFileSync(fullPath,{encoding:'utf8'})
                          }
                          return icPath;
                        });
          }
        } else {
          includeAssetType = null;
          includeAssetPaths = [includeAsset];
        }
        includeAssetCount = includeAssetPaths.length;
        for (var a = 0; a < includeAssetCount; a++) {
          includeAssetPath = includeAssetPaths[a];

          // var fsContent = fs.readFileSync(includeAssetPath);
          // rawData[includeAssetPath] = fsContent;
          // console.log(includeAssetPath);
          includeAssetString = getAssetPath(includeAssetPath);
          if ((includeAssetType && includeAssetType === 'js') || hasExtensions(includeAssetString, jsExtensions)) {
            if (assets.js.indexOf(includeAssetString) === -1 && jsAssets.indexOf(includeAssetString) === -1) {
              jsAssets.push(includeAssetString);
            }
          } else if ((includeAssetType && includeAssetType === 'css') || hasExtensions(includeAssetString, cssExtensions)) {
            if (assets.css.indexOf(includeAssetString) === -1 && cssAssets.indexOf(includeAssetString) === -1) {
              cssAssets.push(includeAssetString);
            }
          }
        }
      }
      if (appendAssets) {
        assets.js = assets.js.concat(jsAssets);
        assets.css = assets.css.concat(cssAssets);
      } else {
        assets.js = jsAssets.concat(assets.js);
        assets.css = cssAssets.concat(assets.css);
      }
      assets.rawData = rawData;
      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    function onAlterAssetTag (htmlPluginData, callback) {
      var tags;
      var tagCount;
      var tag;
      var includeAssets = self.options.assets;
      var assetAttributes;
      var findAttributesForAsset = function (assets, href) {
        var assetCount = assets.length;
        var asset;

        for (var i = 0; i < assetCount; i++) {
          asset = assets[i];
          if (!isString(asset) && asset.attributes && href === getAssetPath(asset.path)) {
            return asset.attributes;
          }
        }
        return null;
      };

      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      tags = htmlPluginData.head.concat(htmlPluginData.body);
      tagCount = tags.length;
      for (var i = 0; i < tagCount; i++) {
        tag = tags[i];
        assetAttributes = findAttributesForAsset(includeAssets, tag.attributes.href || tag.attributes.src);
        if (assetAttributes) {
          extend(tag.attributes, assetAttributes);
        }
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    // Webpack 4+
    if (compilation.hooks) {
      compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('htmlWebpackIncludeAssetsPlugin', onBeforeHtmlGeneration);
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('htmlWebpackIncludeAssetsPlugin', onAlterAssetTag);
    } else {
        // Webpack 3
      compilation.plugin('html-webpack-plugin-before-html-generation', onBeforeHtmlGeneration);
      compilation.plugin('html-webpack-plugin-alter-asset-tags', onAlterAssetTag);
    }
  }

  // Webpack 4+
  if (compiler.hooks) {
    compiler.hooks.compilation.tap('htmlWebpackIncludeAssetsPlugin', onCompilation);
  } else {
    // Webpack 3
    compiler.plugin('compilation', onCompilation);
  }
};

module.exports = HtmlWebpackIncludeAssetsPlugin;
<commit_msg>Update index.js<commit_after>'use strict';
var assert = require('assert');
var minimatch = require('minimatch');
var glob = require('glob');
var path = require('path');
var slash = require('slash');
var fs = require('fs');

var defaultOptions = {
  publicPath: true,
  hash: false,
  jsExtensions: ['.js'],
  cssExtensions: ['.css']
};

function isObject (v) {
  return v !== null && v !== undefined && typeof v === 'object' && !isArray(v);
}

function isBoolean (v) {
  return v === true || v === false;
}

function isString (v) {
  return v !== null && v !== undefined && (typeof v === 'string' || v instanceof String);
}

function isArray (v) {
  return Array.isArray(v);
}

function hasExtension (v, ending) {
  if (v.indexOf('?') !== -1) { // Remove anything after `?`
    v = v.substr(0, v.indexOf('?'));
  }
  var lastIndex = v.lastIndexOf(ending);
  return lastIndex !== -1 && lastIndex === v.length - ending.length;
}

function hasExtensions (v, extensions) {
  var found = false;
  var i;
  var count = extensions.length;
  for (i = 0; i < count; i++) {
    found = hasExtension(v, extensions[i]);
    if (found) {
      break;
    }
  }
  return found;
}

function isOneOf (v, values) {
  return values.indexOf(v) !== -1;
}

function forOwn (object, iterator) {
  var properties = Object.keys(object);
  var propertyCount = properties.length;
  var property;

  for (var i = 0; i < propertyCount; i++) {
    property = properties[i];
    iterator(object[property], property, object);
  }
}

function extend (target, source) {
  forOwn(source, function (value, property) {
    target[property] = value;
  });

  return target;
}

function HtmlWebpackIncludeAssetsPlugin (options) {
  assert(isObject(options), 'HtmlWebpackIncludeAssetsPlugin options are required');
  var assets;
  if (isString(options.assets) || isObject(options.assets)) {
    assets = [options.assets];
  } else {
    assets = options.assets;
  }
  assert(isArray(assets), 'HtmlWebpackIncludeAssetsPlugin options must have an assets key with an array or string value');
  var jsExtensions;
  if (options.jsExtensions !== undefined) {
    if (isString(options.jsExtensions)) {
      jsExtensions = [options.jsExtensions];
    } else {
      jsExtensions = options.jsExtensions;
      assert(isArray(jsExtensions), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key should be a string or array of strings (' + jsExtensions + ')');
      var jsExtensionCount = jsExtensions.length;
      var jsExtension;
      for (var j = 0; j < jsExtensionCount; j++) {
        jsExtension = jsExtensions[j];
        assert(isString(jsExtension), 'HtmlWebpackIncludeAssetsPlugin options jsExtensions key array should not contain non-strings (' + jsExtension + ')');
      }
    }
  } else {
    jsExtensions = defaultOptions.jsExtensions;
  }
  var cssExtensions;
  if (options.cssExtensions !== undefined) {
    if (isString(options.cssExtensions)) {
      cssExtensions = [options.cssExtensions];
    } else {
      cssExtensions = options.cssExtensions;
      assert(isArray(cssExtensions), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key should be a string or array of strings (' + cssExtensions + ')');
      var cssExtensionCount = cssExtensions.length;
      var cssExtension;
      for (var c = 0; c < cssExtensionCount; c++) {
        cssExtension = cssExtensions[c];
        assert(isString(cssExtension), 'HtmlWebpackIncludeAssetsPlugin options cssExtensions key array should not contain non-strings (' + cssExtension + ')');
      }
    }
  } else {
    cssExtensions = defaultOptions.cssExtensions;
  }
  var assetCount = assets.length;
  var asset;
  for (var i = 0; i < assetCount; i++) {
    asset = assets[i];
    if (isString(asset)) {
      assert(hasExtensions(asset, jsExtensions) || hasExtensions(asset, cssExtensions),
        'HtmlWebpackIncludeAssetsPlugin options assets key array should only contain strings ending with the js or css extensions (' + asset + ')');
    } else if (isObject(asset)) {
      assert(isString(asset.path),
        'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property must be a string (' + asset.path + ')');
      if (asset.glob !== undefined) {
        assert(isString(asset.glob),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should be a string (' + asset.glob + ')');
      }
      if (asset.globPath !== undefined) {
        assert(isString(asset.globPath),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects globPath property should be a string (' + asset.globPath + ')');
      }
      if (asset.type !== undefined) {
        assert(isOneOf(asset.type, ['js', 'css']),
          'HtmlWebpackIncludeAssetsPlugin options assets key array objects type property should be a string set to either `js` or `css` (' + asset.type + ')');
      } else {
        if (asset.glob !== undefined) {
          assert(hasExtensions(asset.glob, jsExtensions) || hasExtensions(asset.glob, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects glob property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        } else {
          assert(hasExtensions(asset.path, jsExtensions) || hasExtensions(asset.path, cssExtensions),
            'HtmlWebpackIncludeAssetsPlugin options assets key array objects path property should only contain strings ending with the js or css extensions if the type property is not set (' + asset + ')');
        }
      }
      if (asset.attributes !== undefined) {
        assert(isObject(asset.attributes), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object');
        forOwn(asset.attributes, function (value) {
          assert(isString(value), 'HtmlWebpackIncludeAssetsPlugin options assets key array objects attributes property should be an object with string values');
        });
      }
    } else {
      assert(false, 'HtmlWebpackIncludeAssetsPlugin options assets key array must contain only strings and objects (' + asset + ')');
    }
  }
  assert(isBoolean(options.append), 'HtmlWebpackIncludeAssetsPlugin options must have an append key with a boolean value');
  var publicPath;
  if (options.publicPath !== undefined) {
    assert(isBoolean(options.publicPath) || isString(options.publicPath),
      'HtmlWebpackIncludeAssetsPlugin options should specify a publicPath that is either a boolean or a string');
    publicPath = options.publicPath;
  } else {
    publicPath = defaultOptions.publicPath;
  }
  var hash;
  if (options.hash !== undefined) {
    assert(isBoolean(options.hash), 'HtmlWebpackIncludeAssetsPlugin options should specify a hash key with a boolean value');
    hash = options.hash;
  } else {
    hash = defaultOptions.hash;
  }
  var files;
  if (isString(options.files)) {
    files = [options.files];
  } else {
    files = options.files;
  }
  if (files !== undefined) {
    assert(isArray(files), 'HtmlWebpackIncludeAssetsPlugin options should specify a files key with an array or string value');
    var fileCount = files.length;
    var file;
    for (var f = 0; f < fileCount; f++) {
      file = files[f];
      assert(isString(file),
        'HtmlWebpackIncludeAssetsPlugin options files key array must contain only strings (' + file + ')');
    }
  }
  this.options = {
    assets: assets,
    jsExtensions: jsExtensions,
    cssExtensions: cssExtensions,
    append: options.append,
    publicPath: publicPath,
    hash: hash,
    files: files
  };
}

HtmlWebpackIncludeAssetsPlugin.prototype.apply = function (compiler) {
  var self = this;

  // Hook into the html-webpack-plugin processing
  function onCompilation (compilation) {
    var shouldSkip = function (htmlPluginData) {
      var files = self.options.files;
      return files !== undefined && !files.some(function (file) {
        return minimatch(htmlPluginData.outputName, file);
      });
    };

    var defaultPublicPath;
    var getAssetPath = function (includeAssetPath) {
      var publicPath = self.options.publicPath;
      var hash = self.options.hash;
      var includeAssetPrefix = publicPath === true ? defaultPublicPath : isString(publicPath) ? publicPath : '';
      var includeAssetHash = hash === true ? ('?' + compilation.hash) : '';
      return path.resolve(includeAssetPrefix + includeAssetPath + includeAssetHash);
    };

    function onBeforeHtmlGeneration (htmlPluginData, callback) {
      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      var includeAssets = self.options.assets;
      var jsExtensions = self.options.jsExtensions;
      var cssExtensions = self.options.cssExtensions;
      var appendAssets = self.options.append;
      var assets = htmlPluginData.assets;
      defaultPublicPath = assets.publicPath;

      var includeAsset;
      var includeAssetString;
      var includeAssetPaths;
      var includeAssetCount;
      var includeAssetPath;
      var includeAssetType;
      var includeCount = includeAssets.length;
      var rawData = {};
      var jsAssets = [];
      var cssAssets = [];
      for (var i = 0; i < includeCount; i++) {
        includeAsset = includeAssets[i];
        if (isObject(includeAsset)) {
          includeAssetType = includeAsset.type;
          if (includeAsset.glob === undefined) {
            includeAssetPaths = [includeAsset.path];
          } else {
            var cwd = includeAsset.globPath !== undefined ? includeAsset.globPath : path.join(compiler.options.output.path, includeAsset.path);
            var globOptions = {cwd: cwd};
                    // assets will be an array of strings with all matching asset file names
            includeAssetPaths = glob.sync(includeAsset.glob, globOptions).map(
                        function (globAsset) {
                          var icPath = slash(path.join(includeAsset.path, globAsset));
                          var fullPath = path.join(cwd, globAsset);
                          rawData[icPath] = {
                            fullPath: fullPath,
                            includeAssetPath: icPath,
                            assetPath: globAsset,
                            source: fs.readFileSync(fullPath,{encoding:'utf8'})
                          }
                          return icPath;
                        });
          }
        } else {
          includeAssetType = null;
          includeAssetPaths = [includeAsset];
        }
        includeAssetCount = includeAssetPaths.length;
        for (var a = 0; a < includeAssetCount; a++) {
          includeAssetPath = includeAssetPaths[a];
          includeAssetString = getAssetPath(includeAssetPath);
          if ((includeAssetType && includeAssetType === 'js') || hasExtensions(includeAssetString, jsExtensions)) {
            if (assets.js.indexOf(includeAssetString) === -1 && jsAssets.indexOf(includeAssetString) === -1) {
              jsAssets.push(includeAssetString);
            }
          } else if ((includeAssetType && includeAssetType === 'css') || hasExtensions(includeAssetString, cssExtensions)) {
            if (assets.css.indexOf(includeAssetString) === -1 && cssAssets.indexOf(includeAssetString) === -1) {
              cssAssets.push(includeAssetString);
            }
          }
        }
      }
      if (appendAssets) {
        assets.js = assets.js.concat(jsAssets);
        assets.css = assets.css.concat(cssAssets);
      } else {
        assets.js = jsAssets.concat(assets.js);
        assets.css = cssAssets.concat(assets.css);
      }
      assets.rawData = rawData;
      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    function onAlterAssetTag (htmlPluginData, callback) {
      var tags;
      var tagCount;
      var tag;
      var includeAssets = self.options.assets;
      var assetAttributes;
      var findAttributesForAsset = function (assets, href) {
        var assetCount = assets.length;
        var asset;

        for (var i = 0; i < assetCount; i++) {
          asset = assets[i];
          if (!isString(asset) && asset.attributes && href === getAssetPath(asset.path)) {
            return asset.attributes;
          }
        }
        return null;
      };

      if (shouldSkip(htmlPluginData)) {
        if (callback) {
          return callback(null, htmlPluginData);
        } else {
          return Promise.resolve(htmlPluginData);
        }
      }

      tags = htmlPluginData.head.concat(htmlPluginData.body);
      tagCount = tags.length;
      for (var i = 0; i < tagCount; i++) {
        tag = tags[i];
        assetAttributes = findAttributesForAsset(includeAssets, tag.attributes.href || tag.attributes.src);
        if (assetAttributes) {
          extend(tag.attributes, assetAttributes);
        }
      }

      if (callback) {
        callback(null, htmlPluginData);
      } else {
        return Promise.resolve(htmlPluginData);
      }
    }

    // Webpack 4+
    if (compilation.hooks) {
      compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration.tapAsync('htmlWebpackIncludeAssetsPlugin', onBeforeHtmlGeneration);
      compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('htmlWebpackIncludeAssetsPlugin', onAlterAssetTag);
    } else {
        // Webpack 3
      compilation.plugin('html-webpack-plugin-before-html-generation', onBeforeHtmlGeneration);
      compilation.plugin('html-webpack-plugin-alter-asset-tags', onAlterAssetTag);
    }
  }

  // Webpack 4+
  if (compiler.hooks) {
    compiler.hooks.compilation.tap('htmlWebpackIncludeAssetsPlugin', onCompilation);
  } else {
    // Webpack 3
    compiler.plugin('compilation', onCompilation);
  }
};

module.exports = HtmlWebpackIncludeAssetsPlugin;
<|endoftext|>


<commit_before>// get character map
var CharacterMap = require("./character-map");

/**
 * private: encodeCharacter
 *  This function returns the encoded character using CharacterMap
 *
 *  Arguments
 *    @input: string that must be encoded
 *
 *  Returns
 *    The encoded character or @input
 *
 */
function encodeCharacter (input) {
    for (var i = 0; i < CharacterMap.length; ++i) {
        var cCharacterMapObj = CharacterMap[i];
        if (cCharacterMapObj.decoded === input) {
            return cCharacterMapObj.encoded;
        }
    }
    return input;
}

/**
 * TODO
 *
 */
var HTMLEncoderDecoder = {
    decode: function (input) {

    }
    /**
     *  TODO
     *
     */
  , encode: function (input) {

        // force input to be a string
        input = String(input);
        var output = "";

        // each character
        for (var c in input) {

            // apend encoded character to output
            output += encodeCharacter[c];
        }

        // and return the output
        return output;
    }
};

module.exports = HTMLEncoderDecoder
<commit_msg>Built the decode method<commit_after>// get character map
var CharacterMap = require("./character-map");

/**
 * private: encodeCharacter
 *  This function returns the encoded character using CharacterMap
 *
 *  Arguments
 *    @input: string that must be encoded
 *
 *  Returns
 *    The encoded character or @input
 *
 */
function encodeCharacter (input) {
    for (var i = 0; i < CharacterMap.length; ++i) {
        var cCharacterMapObj = CharacterMap[i];
        if (cCharacterMapObj.decoded === input) {
            return cCharacterMapObj.encoded;
        }
    }
    return input;
}

/**
 * TODO
 *
 */
var HTMLEncoderDecoder = {
    decode: function (input) {

        // force input to be a string
        var output = String(input);

        // each character object in charactermap
        for (var i = 0; i < CharacterMap.length; ++i) {
            output = output.replace(new RegExp (CharacterMap[i].encoded, "g"), CharacterMap[i].decoded)
        }

        return output;
    }
    /**
     *  TODO
     *
     */
  , encode: function (input) {

        // force input to be a string
        input = String(input);
        var output = "";

        // each character
        for (var c in input) {

            // apend encoded character to output
            output += encodeCharacter(input[c]);
        }

        // and return the output
        return output;
    }
};

module.exports = HTMLEncoderDecoder
<|endoftext|>


<commit_before>var matrix = require("./matrix");

// sRGB primaries
// http://www.brucelindbloom.com/WorkingSpaceInfo.html
var x_r = 0.64,
    y_r = 0.33,
    x_g = 0.30,
    y_g = 0.60,
    x_b = 0.15,
    y_b = 0.06;

// D65 illuminant
var refX = 0.95047,
	refY = 1.00000,
	refZ = 1.08883,
	refU = (4 * refX) / (refX + (15 * refY) + (3 * refZ)),
	refV = (9 * refY) / (refX + (15 * refY) + (3 * refZ));

// RGB<->XYZ conversion matrices
// http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
var X_r = x_r / y_r,
    Y_r = 1,
    Z_r = (1 - x_r - y_r) / y_r;

var X_g = x_g / y_g,
    Y_g = 1,
    Z_g = (1 - x_g - y_g) / y_g;

var X_b = x_b / y_b,
    Y_b = 1,
    Z_b = (1 - x_b - y_b) / y_b;

var M_P = [[X_r, X_g, X_b],
           [Y_r, Y_g, Y_b],
           [Z_r, Z_g, Z_b]];

var M_S = matrix.multiply(matrix.inverse(M_P), [refX, refY, refZ]),
    M_RGB_XYZ = matrix.scalar(M_P, M_S),
    M_XYZ_RGB = matrix.inverse(M_RGB_XYZ);

// CIE standard constants
var epsilon = Math.pow(6 / 29, 3),
	kappa = Math.pow(29 / 3, 3);

var sRGBGamma = {
	encode: function (v) {
		return (v <= 0.0031308 ? 12.92*v : 1.055*Math.pow(v, 1/2.4)-0.055);
	},
	decode: function (v) {
		return (v <= 0.04045 ? v/12.92 : Math.pow((v+0.055)/1.055, 2.4));
	}
};

function rgbToXyz (R, G, B) {
	var rgb = [R, G, B].map(sRGBGamma.decode),
		xyz = matrix.multiply(M_RGB_XYZ, rgb);
	return xyz;
}

function xyzToRgb (X, Y, Z) {
	var xyz = [X, Y, Z],
		rgb = matrix.multiply(M_XYZ_RGB, xyz);
	return rgb.map(sRGBGamma.encode);
}

function xyzToLuv (X, Y, Z) {
	var y = Y / refY,
		L = (y <= epsilon ? kappa*y : 116*Math.pow(y, 1/3)-16);
	if (L === 0) {
		return [0, 0, 0];
	}
	var u = 4 * X / (X + 15 * Y + 3 * Z),
		v = 9 * Y / (X + 15 * Y + 3 * Z),
		U = 13 * L * (u - refU),
		V = 13 * L * (v - refV);
	return [L, U, V];
}

function luvToXyz (L, U, V) {
	if (L === 0) {
		return [0, 0, 0];
	}
	var u = refU + U / (13 * L),
		v = refV + V / (13 * L),
		Y = refY * (L > kappa*epsilon ? Math.pow((L+16)/116, 3) : L/kappa),
		X = Y * (9 * u) / (4 * v),
		Z = Y * (12 - 3 * u - 20 * v) / (4 * v);
	return [X, Y, Z];
}

var NEAR_ZERO = 1e-13;

function luvToLhc (L, U, V) {
	if (L === 0 || L === 100) {
		return [L, 0, 0];
	}
	var C = Math.sqrt(Math.pow(U, 2) + Math.pow(V, 2)),
		H = 180 * Math.atan2(V, U) / Math.PI;
	while (H < 0) {
		H += 360;
	}
	while (H > 360) {
		H -= 360;
	}
	if (C < NEAR_ZERO) {
		C = 0;
		H = 0;
	}
	return [L, H, C];
}

function lhcToLuv (L, H, C) {
	var rad = Math.PI * H / 180,
		U = C * Math.cos(rad),
		V = C * Math.sin(rad);
	return [L, U, V];
}

function chroma (L, H, m1, m2, m3, t) {
	var psi = (L > kappa*epsilon ? Math.pow(L+16, 3)/1560896 : L/kappa),
		rad = Math.PI * H / 180,
		cos = Math.cos(rad),
		sin = Math.sin(rad),
		top1 = 11120499 * m1 + 11700000 * m2 + 12739311 * m3,
		top2 = t * 11700000 * L,
		bot1 = 9608480 * m3 - 1921696 * m2,
		bot2 = 1441272 * m3 - 4323816 * m1,
		bot3 = t * 1921696 * sin;
	return (top1 * L * psi - top2) / ((bot1 * sin + bot2 * cos) * psi + bot3);
}

function maxChroma (L, H) {
	if (L === 0 || L === 100) {
		return 0;
	}
	var max = Infinity,
		limits = [0, 1];
	for (var i = 0; i < M_XYZ_RGB.length; i++) {
		var row = M_XYZ_RGB[i];
		for (var j = 0; j < limits.length; j++) {
			var C = chroma(L, H, row[0], row[1], row[2], limits[j]);
			if (C > 0 && C < max) {
				max = C;
			}
		}
	}
	return max;
}

function hexToRgb (hex) {
	var rgb = [];
	for (var i = 0; i < 3; i++) {
		rgb.push(parseInt(hex.substr(1 + 2 * i, 2), 16) / 255);
	}
	return rgb;
}

function hexToLuv (hex) {
	var rgb = hexToRgb(hex),
		xyz = rgbToXyz(rgb[0], rgb[1], rgb[2]),
		luv = xyzToLuv(xyz[0], xyz[1], xyz[2]);
	return luv;
}

function fromHex (hex) {
	var luv = hexToLuv(hex),
		lhc = luvToLhc(luv[0], luv[1], luv[2]);
	return {L: lhc[0], H: lhc[1], C: lhc[2]};
}

function toHex (LHC) {
	var luv = lhcToLuv(LHC.L, LHC.H, LHC.C),
		xyz = luvToXyz(luv[0], luv[1], luv[2]),
		rgb = xyzToRgb(xyz[0], xyz[1], xyz[2]);
	var hex = "";
	for (var i = 0; i < rgb.length; i++) {
		var c = Math.round(rgb[i] * 255).toString(16);
		if (c.length == 1) {
			c = "0" + c;
		}
		hex += c;
	}
	return "#" + hex;
}

function distance (hex1, hex2) {
	var v1 = hexToLuv(hex1),
		v2 = hexToLuv(hex2);
	return Math.sqrt(Math.pow(v1[0]-v2[0], 2)+Math.pow(v1[1]-v2[1], 2)+Math.pow(v1[2]-v2[2], 2));
}

module.exports = {
	toHex: toHex,
	fromHex: fromHex,
	distance: distance,
	maxChroma: maxChroma
};
<commit_msg>Align with spaces<commit_after>var matrix = require("./matrix");

// sRGB primaries
// http://www.brucelindbloom.com/WorkingSpaceInfo.html
var x_r = 0.64,
    y_r = 0.33,
    x_g = 0.30,
    y_g = 0.60,
    x_b = 0.15,
    y_b = 0.06;

// D65 illuminant
var refX = 0.95047,
    refY = 1.00000,
    refZ = 1.08883,
    refU = (4 * refX) / (refX + (15 * refY) + (3 * refZ)),
    refV = (9 * refY) / (refX + (15 * refY) + (3 * refZ));

// RGB<->XYZ conversion matrices
// http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html
var X_r = x_r / y_r,
    Y_r = 1,
    Z_r = (1 - x_r - y_r) / y_r;

var X_g = x_g / y_g,
    Y_g = 1,
    Z_g = (1 - x_g - y_g) / y_g;

var X_b = x_b / y_b,
    Y_b = 1,
    Z_b = (1 - x_b - y_b) / y_b;

var M_P = [[X_r, X_g, X_b],
           [Y_r, Y_g, Y_b],
           [Z_r, Z_g, Z_b]];

var M_S = matrix.multiply(matrix.inverse(M_P), [refX, refY, refZ]),
    M_RGB_XYZ = matrix.scalar(M_P, M_S),
    M_XYZ_RGB = matrix.inverse(M_RGB_XYZ);

// CIE standard constants
var epsilon = Math.pow(6 / 29, 3),
    kappa = Math.pow(29 / 3, 3);

var sRGBGamma = {
	encode: function (v) {
		return (v <= 0.0031308 ? 12.92*v : 1.055*Math.pow(v, 1/2.4)-0.055);
	},
	decode: function (v) {
		return (v <= 0.04045 ? v/12.92 : Math.pow((v+0.055)/1.055, 2.4));
	}
};

function rgbToXyz (R, G, B) {
	var rgb = [R, G, B].map(sRGBGamma.decode),
	    xyz = matrix.multiply(M_RGB_XYZ, rgb);
	return xyz;
}

function xyzToRgb (X, Y, Z) {
	var xyz = [X, Y, Z],
	    rgb = matrix.multiply(M_XYZ_RGB, xyz);
	return rgb.map(sRGBGamma.encode);
}

function xyzToLuv (X, Y, Z) {
	var y = Y / refY,
	    L = (y <= epsilon ? kappa*y : 116*Math.pow(y, 1/3)-16);
	if (L === 0) {
		return [0, 0, 0];
	}
	var u = 4 * X / (X + 15 * Y + 3 * Z),
	    v = 9 * Y / (X + 15 * Y + 3 * Z),
	    U = 13 * L * (u - refU),
	    V = 13 * L * (v - refV);
	return [L, U, V];
}

function luvToXyz (L, U, V) {
	if (L === 0) {
		return [0, 0, 0];
	}
	var u = refU + U / (13 * L),
	    v = refV + V / (13 * L),
	    Y = refY * (L > kappa*epsilon ? Math.pow((L+16)/116, 3) : L/kappa),
	    X = Y * (9 * u) / (4 * v),
	    Z = Y * (12 - 3 * u - 20 * v) / (4 * v);
	return [X, Y, Z];
}

var NEAR_ZERO = 1e-13;

function luvToLhc (L, U, V) {
	if (L === 0 || L === 100) {
		return [L, 0, 0];
	}
	var C = Math.sqrt(Math.pow(U, 2) + Math.pow(V, 2)),
	    H = 180 * Math.atan2(V, U) / Math.PI;
	while (H < 0) {
		H += 360;
	}
	while (H > 360) {
		H -= 360;
	}
	if (C < NEAR_ZERO) {
		C = 0;
		H = 0;
	}
	return [L, H, C];
}

function lhcToLuv (L, H, C) {
	var rad = Math.PI * H / 180,
	    U = C * Math.cos(rad),
	    V = C * Math.sin(rad);
	return [L, U, V];
}

function chroma (L, H, m1, m2, m3, t) {
	var psi = (L > kappa*epsilon ? Math.pow(L+16, 3)/1560896 : L/kappa),
	    rad = Math.PI * H / 180,
	    cos = Math.cos(rad),
	    sin = Math.sin(rad),
	    top1 = 11120499 * m1 + 11700000 * m2 + 12739311 * m3,
	    top2 = t * 11700000 * L,
	    bot1 = 9608480 * m3 - 1921696 * m2,
	    bot2 = 1441272 * m3 - 4323816 * m1,
	    bot3 = t * 1921696 * sin;
	return (top1 * L * psi - top2) / ((bot1 * sin + bot2 * cos) * psi + bot3);
}

function maxChroma (L, H) {
	if (L === 0 || L === 100) {
		return 0;
	}
	var max = Infinity,
	    limits = [0, 1];
	for (var i = 0; i < M_XYZ_RGB.length; i++) {
		var row = M_XYZ_RGB[i];
		for (var j = 0; j < limits.length; j++) {
			var C = chroma(L, H, row[0], row[1], row[2], limits[j]);
			if (C > 0 && C < max) {
				max = C;
			}
		}
	}
	return max;
}

function hexToRgb (hex) {
	var rgb = [];
	for (var i = 0; i < 3; i++) {
		rgb.push(parseInt(hex.substr(1 + 2 * i, 2), 16) / 255);
	}
	return rgb;
}

function hexToLuv (hex) {
	var rgb = hexToRgb(hex),
	    xyz = rgbToXyz(rgb[0], rgb[1], rgb[2]),
	    luv = xyzToLuv(xyz[0], xyz[1], xyz[2]);
	return luv;
}

function fromHex (hex) {
	var luv = hexToLuv(hex),
	    lhc = luvToLhc(luv[0], luv[1], luv[2]);
	return {L: lhc[0], H: lhc[1], C: lhc[2]};
}

function toHex (LHC) {
	var luv = lhcToLuv(LHC.L, LHC.H, LHC.C),
	    xyz = luvToXyz(luv[0], luv[1], luv[2]),
	    rgb = xyzToRgb(xyz[0], xyz[1], xyz[2]);
	var hex = "";
	for (var i = 0; i < rgb.length; i++) {
		var c = Math.round(rgb[i] * 255).toString(16);
		if (c.length == 1) {
			c = "0" + c;
		}
		hex += c;
	}
	return "#" + hex;
}

function distance (hex1, hex2) {
	var v1 = hexToLuv(hex1),
	    v2 = hexToLuv(hex2);
	return Math.sqrt(Math.pow(v1[0]-v2[0], 2)+Math.pow(v1[1]-v2[1], 2)+Math.pow(v1[2]-v2[2], 2));
}

module.exports = {
	toHex: toHex,
	fromHex: fromHex,
	distance: distance,
	maxChroma: maxChroma
};
<|endoftext|>


<commit_before>const instance = require('../helpers/weak_cache');
const ssHandler = require('../helpers/samesite_handler');

module.exports = async function sessionHandler(ctx, next) {
  ctx.oidc.session = new Proxy(await ctx.oidc.provider.Session.get(ctx), {
    set(obj, prop, value) {
      if (prop === 'touched') {
        Reflect.defineProperty(obj, 'touched', { writable: true, value });
      } else if (prop === 'destroyed') {
        Reflect.defineProperty(obj, 'destroyed', { configurable: false, writable: true, value });
        Reflect.defineProperty(obj, 'touched', { configurable: false, writable: false, value: false });
      } else {
        Reflect.set(obj, prop, value);
        Reflect.defineProperty(obj, 'touched', { writable: true, value: true });
      }
      return true;
    },
  });

  try {
    await next();
  } finally {
    const sessionCookieName = ctx.oidc.provider.cookieName('session');
    const stateCookieName = ctx.oidc.provider.cookieName('state');
    const longRegexp = new RegExp(`^(${sessionCookieName}|${stateCookieName}\\.[^=]+)(?:\\.sig)?=`);

    // refresh the session duration
    if ((!ctx.oidc.session.new || ctx.oidc.session.touched) && !ctx.oidc.session.destroyed) {
      ssHandler.set(
        ctx.oidc.cookies,
        sessionCookieName,
        ctx.oidc.session.id,
        instance(ctx.oidc.provider).configuration('cookies.long'),
      );
      await ctx.oidc.session.save();
    }

    if (ctx.response.get('set-cookie')) {
      ctx.response.get('set-cookie').forEach((cookie, index, ary) => {
        /* eslint-disable no-param-reassign */
        if (ctx.oidc.session.transient && cookie.includes('expires=') && !cookie.includes('expires=Thu, 01 Jan 1970') && cookie.match(longRegexp)) {
          ary[index] = cookie.replace(/(; ?expires=([\w\d:, ]+))/, '');
        }
        /* eslint-enable */
      });
    }
  }
};
<commit_msg>fix: set samesite compatibility cookie as transient when the session is<commit_after>const instance = require('../helpers/weak_cache');
const ssHandler = require('../helpers/samesite_handler');

module.exports = async function sessionHandler(ctx, next) {
  ctx.oidc.session = new Proxy(await ctx.oidc.provider.Session.get(ctx), {
    set(obj, prop, value) {
      if (prop === 'touched') {
        Reflect.defineProperty(obj, 'touched', { writable: true, value });
      } else if (prop === 'destroyed') {
        Reflect.defineProperty(obj, 'destroyed', { configurable: false, writable: true, value });
        Reflect.defineProperty(obj, 'touched', { configurable: false, writable: false, value: false });
      } else {
        Reflect.set(obj, prop, value);
        Reflect.defineProperty(obj, 'touched', { writable: true, value: true });
      }
      return true;
    },
  });

  try {
    await next();
  } finally {
    const sessionCookieName = ctx.oidc.provider.cookieName('session');
    const stateCookieName = ctx.oidc.provider.cookieName('state');
    const longRegexp = new RegExp(`^(${sessionCookieName}|${stateCookieName}\\.[^=]+)(?:\\.legacy)?(?:\\.sig)?=`);

    // refresh the session duration
    if ((!ctx.oidc.session.new || ctx.oidc.session.touched) && !ctx.oidc.session.destroyed) {
      ssHandler.set(
        ctx.oidc.cookies,
        sessionCookieName,
        ctx.oidc.session.id,
        instance(ctx.oidc.provider).configuration('cookies.long'),
      );
      await ctx.oidc.session.save();
    }

    if (ctx.response.get('set-cookie')) {
      ctx.response.get('set-cookie').forEach((cookie, index, ary) => {
        /* eslint-disable no-param-reassign */
        if (ctx.oidc.session.transient && cookie.includes('expires=') && !cookie.includes('expires=Thu, 01 Jan 1970') && cookie.match(longRegexp)) {
          ary[index] = cookie.replace(/(; ?expires=([\w\d:, ]+))/, '');
        }
        /* eslint-enable */
      });
    }
  }
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
const Promise = require('bluebird');
const debug = require('debug')('pogono');
const fs = require('fs');
const path = require('path');
const request = require('request-promise');
const moment = require('moment');
const _ = require('lodash');
const errors = require('request-promise/errors');

const args = require('./args.js');
const config = _.assign({
    filteredPokemonIds: null,
    filteredAddressKeywords: null,
    trustedUserId: null,
    minLatitude: 24.783617562869416,
    maxLatitude: 24.82740393838965,
    minLongitude: 120.93629837036131,
    maxLongitude: 121.0129451751709,
    queryInterval: 10000,
    telegramChatId: null,
    telegramBotToken: null,
    telegramBotEnable: false,
    source: 'pokeradar',
    pokemonGoMapAPI: null
}, require(path.resolve(args.config)));

if (config.centerLatitude && config.centerLongitude && config.nearbyDistance) {
    config.minLatitude = config.centerLatitude - config.nearbyDistance/110.574;
    config.maxLatitude = config.centerLatitude + config.nearbyDistance/110.574;
    config.minLongitude = config.centerLongitude - config.nearbyDistance/(111.32 * Math.cos(config.centerLatitude));
    config.maxLongitude = config.centerLongitude + config.nearbyDistance/(111.32 * Math.cos(config.centerLatitude));
};

const TelegramBot = require('./telegram_bot.js');
const pokemonNames = require('./pokemon_names.js');
const pokemonStickers = require('./stickers.js');
const getReverseGeocode = require('./get_reverse_geocode.js');
const messageTemplate = fs.readFileSync('./message_template.md.raw', 'utf-8');

let telegramBot = config.telegramBotEnable ? new TelegramBot(config) : null;
let sentPokemons = [];

const generateMessage = function(pokemon) {
    let message = messageTemplate;
    let replacements = {
        pokemon_id: pokemon.pokemonId,
        pokemon_name_zh: pokemon.pokemonName.zh,
        pokemon_name_en: pokemon.pokemonName.en,
        address_components: pokemon.reverseGeocode.components.map((x) => '#' + x).join(' '),
        address: pokemon.reverseGeocode.formatted_address,
        remaining_time: pokemon.remainingTime.format('mm:ss'),
        direction: pokemon.direction,
        until: pokemon.until.format('YYYY-MM-DD HH:mm:ss')
    };
    for (let placeholder in replacements) {
        message = message.replace('{' + placeholder + '}', replacements[placeholder]);
    }
    return message;
}

const pushNotifications = function(pokemons) {
    sentPokemons = _.filter(sentPokemons, (s) => s.until.isAfter(moment()));

    // remove sent pokemons
    debug('fetch', 'fetch pokemons from the provider:', pokemons.length, 'pokemons left');
    let filteredPokemons = _.filter(pokemons, function (p) {
        return !_.find(sentPokemons, (s) => p.uniqueId == s.uniqueId) && p.remainingTime.diff(moment.utc(0)) > 0;
    });
    debug('filter', 'filter by sent pokemons:', filteredPokemons.length, 'pokemons left');

    debug('get reverse geocode');
    return Promise.each(filteredPokemons, function(p) {
            return getReverseGeocode(p.latitude, p.longitude)
                .then((reverseGeocode) => p.reverseGeocode = reverseGeocode);
        })
        .then(function filterByAddressKeywords() {
            filteredPokemons = _.filter(filteredPokemons, function(p) {
                if (config.filteredAddressKeywords) {
                    let keywords = _.isArray(config.filteredAddressKeywords) ? config.filteredAddressKeywords : [config.filteredAddressKeywords];
                    for (let keyword of keywords) {
                        if (p.reverseGeocode.formatted_address.includes(keyword) || p.reverseGeocode.components.join(',').includes(keyword)) {
                            return true;
                        }
                    }
                    sentPokemons.push(p);
                    return false;
                }
                return true;
            });
            debug('filter', 'filter by address keywords', config.filteredAddressKeywords, ':', filteredPokemons.length, 'pokemons left');
            debug('notify', filteredPokemons.length, 'pokemons');
            return filteredPokemons;
        })
        .each(function(p) {
            let message = generateMessage(p);
            console.log(moment().format(), 'message:', message);

            if (config.telegramBotEnable && telegramBot && config.telegramChatId) {
                // push a notification
                return Promise.resolve()
                    .then(() => telegramBot.sendSticker(config.telegramChatId, pokemonStickers[p.pokemonId]))
                    .then(() => telegramBot.sendLocation(config.telegramChatId, p.latitude, p.longitude))
                    .then(() => telegramBot.sendMessage(config.telegramChatId, message, { parse_mode: 'Markdown' }))
                    .then(() => sentPokemons.push(p))
                    .catch(function(err) {
                        console.error(moment().format(), 'telegram bot error:', err.message);
                    })
            }
        });
}

let Provider = require('./providers/' + config.source);
let provider = new Provider(config);

debug('request loop starts');

provider
    .init()
    .then(function requestLoop() {
        return provider
            .getPokemons()
            .then(pushNotifications)
            .catch(errors.StatusCodeError, function (reason) {
                console.error(moment().format(), 'status code error:', reason.message);
            })
            .catch(errors.RequestError, function (reason) {
                console.error(moment().format(), 'request error:', reason.message);
            })
            .catch(SyntaxError, function(reason) {
                console.error(moment().format(), 'syntax error:', reason.message);
            })
            .delay(config.queryInterval)
            .then(requestLoop);
    })
    .catch(function(reason) {
        console.error(moment().format(), reason);
        // TODO: use TelegramBot#stopPolling instead
        telegramBot._polling.abort = true;
        console.error('the program has been terminated')
    });
<commit_msg>Disable notifications on sendSticker and sendLocation<commit_after>#!/usr/bin/env node
const Promise = require('bluebird');
const debug = require('debug')('pogono');
const fs = require('fs');
const path = require('path');
const request = require('request-promise');
const moment = require('moment');
const _ = require('lodash');
const errors = require('request-promise/errors');

const args = require('./args.js');
const config = _.assign({
    filteredPokemonIds: null,
    filteredAddressKeywords: null,
    trustedUserId: null,
    minLatitude: 24.783617562869416,
    maxLatitude: 24.82740393838965,
    minLongitude: 120.93629837036131,
    maxLongitude: 121.0129451751709,
    queryInterval: 10000,
    telegramChatId: null,
    telegramBotToken: null,
    telegramBotEnable: false,
    source: 'pokeradar',
    pokemonGoMapAPI: null
}, require(path.resolve(args.config)));

if (config.centerLatitude && config.centerLongitude && config.nearbyDistance) {
    config.minLatitude = config.centerLatitude - config.nearbyDistance/110.574;
    config.maxLatitude = config.centerLatitude + config.nearbyDistance/110.574;
    config.minLongitude = config.centerLongitude - config.nearbyDistance/(111.32 * Math.cos(config.centerLatitude));
    config.maxLongitude = config.centerLongitude + config.nearbyDistance/(111.32 * Math.cos(config.centerLatitude));
};

const TelegramBot = require('./telegram_bot.js');
const pokemonNames = require('./pokemon_names.js');
const pokemonStickers = require('./stickers.js');
const getReverseGeocode = require('./get_reverse_geocode.js');
const messageTemplate = fs.readFileSync('./message_template.md.raw', 'utf-8');

let telegramBot = config.telegramBotEnable ? new TelegramBot(config) : null;
let sentPokemons = [];

const generateMessage = function(pokemon) {
    let message = messageTemplate;
    let replacements = {
        pokemon_id: pokemon.pokemonId,
        pokemon_name_zh: pokemon.pokemonName.zh,
        pokemon_name_en: pokemon.pokemonName.en,
        address_components: pokemon.reverseGeocode.components.map((x) => '#' + x).join(' '),
        address: pokemon.reverseGeocode.formatted_address,
        remaining_time: pokemon.remainingTime.format('mm:ss'),
        direction: pokemon.direction,
        until: pokemon.until.format('YYYY-MM-DD HH:mm:ss')
    };
    for (let placeholder in replacements) {
        message = message.replace('{' + placeholder + '}', replacements[placeholder]);
    }
    return message;
}

const pushNotifications = function(pokemons) {
    sentPokemons = _.filter(sentPokemons, (s) => s.until.isAfter(moment()));

    // remove sent pokemons
    debug('fetch', 'fetch pokemons from the provider:', pokemons.length, 'pokemons left');
    let filteredPokemons = _.filter(pokemons, function (p) {
        return !_.find(sentPokemons, (s) => p.uniqueId == s.uniqueId) && p.remainingTime.diff(moment.utc(0)) > 0;
    });
    debug('filter', 'filter by sent pokemons:', filteredPokemons.length, 'pokemons left');

    debug('get reverse geocode');
    return Promise.each(filteredPokemons, function(p) {
            return getReverseGeocode(p.latitude, p.longitude)
                .then((reverseGeocode) => p.reverseGeocode = reverseGeocode);
        })
        .then(function filterByAddressKeywords() {
            filteredPokemons = _.filter(filteredPokemons, function(p) {
                if (config.filteredAddressKeywords) {
                    let keywords = _.isArray(config.filteredAddressKeywords) ? config.filteredAddressKeywords : [config.filteredAddressKeywords];
                    for (let keyword of keywords) {
                        if (p.reverseGeocode.formatted_address.includes(keyword) || p.reverseGeocode.components.join(',').includes(keyword)) {
                            return true;
                        }
                    }
                    sentPokemons.push(p);
                    return false;
                }
                return true;
            });
            debug('filter', 'filter by address keywords', config.filteredAddressKeywords, ':', filteredPokemons.length, 'pokemons left');
            debug('notify', filteredPokemons.length, 'pokemons');
            return filteredPokemons;
        })
        .each(function(p) {
            let message = generateMessage(p);
            console.log(moment().format(), 'message:', message);

            if (config.telegramBotEnable && telegramBot && config.telegramChatId) {
                // push a notification
                return Promise.resolve()
                    .then(() => telegramBot.sendSticker(config.telegramChatId, pokemonStickers[p.pokemonId], { disable_notification: true }))
                    .then(() => telegramBot.sendLocation(config.telegramChatId, p.latitude, p.longitude, { disable_notification: true }))
                    .then(() => telegramBot.sendMessage(config.telegramChatId, message, { parse_mode: 'Markdown' }))
                    .then(() => sentPokemons.push(p))
                    .catch(function(err) {
                        console.error(moment().format(), 'telegram bot error:', err.message);
                    })
            }
        });
}

let Provider = require('./providers/' + config.source);
let provider = new Provider(config);

debug('request loop starts');

provider
    .init()
    .then(function requestLoop() {
        return provider
            .getPokemons()
            .then(pushNotifications)
            .catch(errors.StatusCodeError, function (reason) {
                console.error(moment().format(), 'status code error:', reason.message);
            })
            .catch(errors.RequestError, function (reason) {
                console.error(moment().format(), 'request error:', reason.message);
            })
            .catch(SyntaxError, function(reason) {
                console.error(moment().format(), 'syntax error:', reason.message);
            })
            .delay(config.queryInterval)
            .then(requestLoop);
    })
    .catch(function(reason) {
        console.error(moment().format(), reason);
        // TODO: use TelegramBot#stopPolling instead
        telegramBot._polling.abort = true;
        console.error('the program has been terminated')
    });
<|endoftext|>


<commit_before>/*jslint onevar: false, eqeqeq: false*/
/*global require*/
var sys = require("sys");

var sinon = require("sinon");
sinon.sandbox = require("sinon/sandbox");
sinon.assert = require("sinon/assert");

var nodeunit = {
    core: require("core"),
    types: require("types")
};

var typesTest = nodeunit.types.test;

nodeunit.types.test = function () {
    var obj = typesTest.apply(this, arguments);
    var config = sinon.getConfig(sinon.config);
    config.useFakeServer = false;
    obj.sandbox = sinon.sandbox.create(config);
    obj.sandbox.inject(obj);

    obj.useFakeTimers = function () {
        return obj.sandbox.useFakeTimers.apply(obj.sandbox, arguments);
    };

    var assertion, method;

    for (assertion in sinon.assert) {
        if (sinon.assert.hasOwnProperty(assertion)) {
            method = sinon.assert[assertion];

            if (typeof method == "function" && assertion != "fail" &&
                assertion != "pass" && assertion != "expose") {
                obj[assertion] = obj.wrapAssert(assertion, assertion, method.length + 1, sinon.assert);
            }
        }
    }

    return obj;
};

var nuRunTest = nodeunit.core.runTest;

nodeunit.core.runTest = function () {
    var testFn = arguments[1];

    arguments[1] = function (test) {
        var exception, retVal;

        try {
            retVal = testFn.apply(this, arguments);
        } catch (e) {
            exception = e;
        }

        test.sandbox.verifyAndRestore();

        if (exception) {
            throw exception;
        }

        return retVal;
    };

    return nuRunTest.apply(this, arguments);
};
<commit_msg>Remove sys require<commit_after>/*jslint onevar: false, eqeqeq: false*/
/*global require*/
var sinon = require("sinon");
sinon.sandbox = require("sinon/sandbox");
sinon.assert = require("sinon/assert");

var nodeunit = {
    core: require("core"),
    types: require("types")
};

var typesTest = nodeunit.types.test;

nodeunit.types.test = function () {
    var obj = typesTest.apply(this, arguments);
    var config = sinon.getConfig(sinon.config);
    config.useFakeServer = false;
    obj.sandbox = sinon.sandbox.create(config);
    obj.sandbox.inject(obj);

    obj.useFakeTimers = function () {
        return obj.sandbox.useFakeTimers.apply(obj.sandbox, arguments);
    };

    var assertion, method;

    for (assertion in sinon.assert) {
        if (sinon.assert.hasOwnProperty(assertion)) {
            method = sinon.assert[assertion];

            if (typeof method == "function" && assertion != "fail" &&
                assertion != "pass" && assertion != "expose") {
                obj[assertion] = obj.wrapAssert(assertion, assertion, method.length + 1, sinon.assert);
            }
        }
    }

    return obj;
};

var nuRunTest = nodeunit.core.runTest;

nodeunit.core.runTest = function () {
    var testFn = arguments[1];

    arguments[1] = function (test) {
        var exception, retVal;

        try {
            retVal = testFn.apply(this, arguments);
        } catch (e) {
            exception = e;
        }

        test.sandbox.verifyAndRestore();

        if (exception) {
            throw exception;
        }

        return retVal;
    };

    return nuRunTest.apply(this, arguments);
};
<|endoftext|>


<commit_before>/* eslint-disable no-var */

module.exports = {
  name: 'ember-cli-svn',

  dependencies() {
    const dependencies = {};
    const svnDependencies = this.parent.pkg.svnDependencies;

    if (svnDependencies) {
      Object.keys(svnDependencies).forEach((key) => {
        const tagIdx = key.indexOf('@');
        const value = svnDependencies[key];
        key = tagIdx === -1 ? key : key.substring(0, tagIdx);

        dependencies[key] = value;
      });
    }

    this.pkg = this.pkg || {};
    this.pkg.dependencies = dependencies;

    return {};
  },
};
<commit_msg>Returning object<commit_after>/* eslint-disable no-var */

module.exports = {
  name: 'ember-cli-svn',

  dependencies() {
    const dependencies = {};
    const svnDependencies = this.parent.pkg.svnDependencies;

    if (svnDependencies) {
      Object.keys(svnDependencies).forEach((key) => {
        const tagIdx = key.indexOf('@');
        const value = svnDependencies[key];
        key = tagIdx === -1 ? key : key.substring(0, tagIdx);

        dependencies[key] = value;
      });
    }

    this.pkg = this.pkg || {};
    this.pkg.dependencies = dependencies;

    return dependencies;
  },
};
<|endoftext|>


<commit_before>// Load dotenv first
require('dotenv').config({silent: true});

var Accuser = require('accuser');

var accuser = new Accuser();

var githubAuthToken = {
  "type": "oauth",
  "token": process.env.GITHUB_TOKEN
};

accuser.authenticate(githubAuthToken);

var initializeSemesterRepositories = require('./src/semester');
initializeSemesterRepositories(accuser, 'addressbook-level1');
initializeSemesterRepositories(accuser, 'addressbook-level2');
initializeSemesterRepositories(accuser, 'addressbook-level3');
//initializeSemesterRepositories(accuser, 'addressbook-level4');

var intializeSeEduRepositories = require('./src/seedu');
intializeSeEduRepositories(accuser, 'addressbook-level1');
intializeSeEduRepositories(accuser, 'addressbook-level2');
intializeSeEduRepositories(accuser, 'addressbook-level3');
intializeSeEduRepositories(accuser, 'addressbook-level4');

var intializeSeEduRcsRepository = require('./src/seedu-rcs');
intializeSeEduRcsRepository(accuser, 'rcs');

console.log ("Server has started");

accuser
  .run({assignee: "none"});
<commit_msg>enable PR processing for level 4<commit_after>// Load dotenv first
require('dotenv').config({silent: true});

var Accuser = require('accuser');

var accuser = new Accuser();

var githubAuthToken = {
  "type": "oauth",
  "token": process.env.GITHUB_TOKEN
};

accuser.authenticate(githubAuthToken);

var initializeSemesterRepositories = require('./src/semester');
initializeSemesterRepositories(accuser, 'addressbook-level1');
initializeSemesterRepositories(accuser, 'addressbook-level2');
initializeSemesterRepositories(accuser, 'addressbook-level3');
initializeSemesterRepositories(accuser, 'addressbook-level4');

var intializeSeEduRepositories = require('./src/seedu');
intializeSeEduRepositories(accuser, 'addressbook-level1');
intializeSeEduRepositories(accuser, 'addressbook-level2');
intializeSeEduRepositories(accuser, 'addressbook-level3');
intializeSeEduRepositories(accuser, 'addressbook-level4');

var intializeSeEduRcsRepository = require('./src/seedu-rcs');
intializeSeEduRcsRepository(accuser, 'rcs');

console.log ("Server has started");

accuser
  .run({assignee: "none"});
<|endoftext|>


<commit_before>var resource = require('resource'),
    queue = resource.define('queue');

queue.schema.description = "a queue for resource events";

queue.persist('memory');

queue.property('concurrency', {
  description: 'how many jobs to run at once',
  type: 'number',
  default: 1
});

queue.property('interval', {
  description: 'time interval between processing items (ms)',
  type: 'number',
  default: 5000
});

queue.property('wait', {
  description: 'wait until all running jobs are completed before executing next set',
  type: 'boolean',
  default: true
});

queue.property('repeat', {
  description: 'automatically push completed elements back onto the queue',
  type: 'boolean',
  default: false
});

queue.property('autosave', {
  description: 'automatically save the queue after a processing step',
  type: 'boolean',
  default: true
});

queue.property('elements', {
  description: 'the elements currently inside the queue',
  type: 'array',
  default: []
});

//
// Basic push/shift methods for queue
//
// Remark: Typically, array shifts are considered expensive, and there is
// a way to optimize this by offsetting the index instead of shifting,
// reusing earlier elements in the array, and then resizing the array (only
// here would slicing be used) and resetting the offsets when the array becomes
// full. That said, this is probably negligible compared to the overhead of
// persisting the queue to a datasource anyway.
//
queue.method('push', push, {
  description: 'push an element onto the queue',
  properties: {
    queue: queue.schema,
    job: {
      properties: {
        method: {
          type: 'string'
        },
        with: {
          type: 'any',
          default: {}
        }
      }
    }
  }
});
function push(q, job) {
  var elems = q.elements.push(job);
  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }
  return elems;
}

queue.method('shift', shift, {
  description: 'shift an element off the queue',
  properties: {
    options: {
      properties: queue.schema.properties
    }
  }
});
function shift (q) {
  var shifted = q.elements.shift();
  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }
  return shifted;
}

queue.method('take', take, {
  description: 'take `queue.concurrency` elements off the queue',
  properties: {
    options: {
      properties: queue.schema.properties
    },
    n: {
      type: 'number'
    }
  }
});
function take (q, n) {

  if (typeof n !== 'number') {
    n = q.concurrency;
  }

  var xs = [];
  //
  // We do multiple shifts instead of a concat because q is actually a different
  // object and not a direct reference. However, the underlying array methods
  // are still bound to the original argument.
  //
  // This should probably be considered a bug in the resource engine.
  //
  while (n && q.elements.length) {
    var elem = q.elements.shift();
    xs.push(elem);
    n--;
  }

  return xs;
}

//
// Lists in python have an analogous method of the same name
// http://docs.python.org/2/library/stdtypes.html#typesseq-mutable
//
queue.method('extend', extend, {
  description: 'extend the queue with an array of elements',
  properties: {
    instance: {
      properties: queue.schema.properties
    },
    elems: {
      type: 'any'
    }
  }
});
function extend(q, xs) {
  //
  // This method suffers the same problems with modifying objects in-place,
  // as explained in the `take` method. Here, we get around this in a
  // similar technique.
  //
  xs.forEach(function (elem) {
    q.elements.push(elem);
  });

  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }

  return q.elements;
}

//
// Run a single job by executing the specified method with the specified
// metadata
//
queue.method('run', run, {
  description: 'run a job',
  type: 'object',
  properties: {
    job: {
      properties: {
        method: {
          type: 'string',
          required: true
        },
        with: {
          type: 'any',
          default: {}
        }
      }
    },
    callback: {
      type: 'function'
    }
  }
});
function run(j, callback) {

  var properties = j.method.split('::'),
      method = resource,
      err;

  properties.forEach(function (p) {
    if (typeof method[p] !== 'undefined') {
      method = method[p];
    }
    else if (!err) {
      err = new Error('could not execute method `' + j.method + '`');
    }
  });

  if (typeof method == 'undefined' ) {
    err = new Error('could not execute method `' + j.method + '`');
  }

  if (err) {
    return callback(err);
  }

  method(j.with, callback);
}

//
// This method takes q.concurrency elements off the front of the queue and
// `queue.run`s them.
//
queue.method('process', process, {
  description: 'process elements off the queue',
  properties: {
    options: queue.schema,
    callback: {
      type: 'function'
    }
  }
});
function process (q, callback) {
  //
  // Process the top q.concurrency elements at once
  //
  var elements = queue.take(q, q.concurrency);

  if (!elements.length) {
    return callback();
  }

  var i;

  elements.forEach(function (elem) {
    queue.run(elem, function (err) {
      i--;

      if (err) {
        queue.emit('error', err);
      }

      //
      // If element repeating is turned on, push the just-processed element
      // back onto the queue
      //
      if (q.repeat) {
        queue.push(q, elem);
      }

      //
      // If autosave is turned on, save the current queue.
      //
      if (q.autosave) {
        queue.updateOrCreate(q, function (_err, _q) {
          if (_err) {
            return queue.emit(_err);
          }
        });
      }

      if (i === 0) {
        callback(null, q);
      }
    });
  });
}

//
// TODO: `unload` method
//
queue.method('load', load, {
  description: 'start processing a queue',
  properties: {
    options: queue.schema
  }
});
function load(q) {
  _process();

  function _process() {
    var completed = false,
        timedOut = false;

    //
    // Process again at the end of the timeout *if* the last process step
    // completed
    //
    queue._loop = setTimeout(function () {
      //
      // If waiting is turned off, always process the next set of elements
      //
      if (completed || !q.wait) {
        _process();
      }
      else {
        timedOut = true;
      }
    }, q.interval);

    queue.process(q, function (err, result) {
      if (err) {
        //
        // Remark: Emit error, keep going (for now, may change behavior)
        //
        // TODO: Attach some context to the error?
        //
        queue.emit('error', err);
      }

      completed = true;

      //
      // If the timeout occurred while the process step was still running,
      // execute it late
      //
      if (timedOut) {
        _process();
      }
    });
  }
}

queue.method('unload', unload, {
  description: 'start processing a queue',
  properties: {
    options: queue.schema
  }
});
function unload(q) {
  clearInterval(q._loop);
}

queue.on('error', function (error, data) {
  resource.logger.error(error);
  resource.logger.data(data);
});

//
// after hooks to add methods to instances of queue
//
queue.after('create', addMethods);
queue.after('get', addMethods);
queue.after('updateOrCreate', addMethods);
queue.after('all', addMethods);
queue.after('find', addMethods);
function addMethods (instance, next) {
  //
  // In cases of 'all' and 'find', "instance" will actually be an *array* of
  // queue instances.
  //
  if (Array.isArray(instance)) {
    //
    // Use "some" to short
    //
    instance.some(function (q, i) {
      var err;
      //
      // Remark: addMethods runs synchronously
      //
      addMethods(q, function (e, _q) {
        if (e) {
          err = e;
          return;
        }
        instance[i] = _q;
      });

      if (err) {
        next(err);
      }

      //
      // If err is truthy, this will short-circuit the .some method
      //
      return err;
    });

    return next(null, instance);
  }

  //
  // Assuming instance is *not* an array, add all the bound methods to
  // the instance
  //
  [
    'push',
    'shift',
    'take',
    'extend',
    'process',
    'load',
    'unload'
  ].forEach(function (m) {
    instance[m] = function (options) {
      return queue[m](instance, options);
    }
  });

  //
  // Alias "start" to "load" at the instance level
  //
  instance.start = instance.load;
  instance.stop = instance.unload;

  next(null, instance);
}

exports.queue = queue;
<commit_msg>[queue] [api] It is now possible to stop a queue<commit_after>var resource = require('resource'),
    queue = resource.define('queue');

queue.schema.description = "a queue for resource events";

queue.persist('memory');

queue.property('concurrency', {
  description: 'how many jobs to run at once',
  type: 'number',
  default: 1
});

queue.property('interval', {
  description: 'time interval between processing items (ms)',
  type: 'number',
  default: 5000
});

queue.property('wait', {
  description: 'wait until all running jobs are completed before executing next set',
  type: 'boolean',
  default: true
});

queue.property('repeat', {
  description: 'automatically push completed elements back onto the queue',
  type: 'boolean',
  default: false
});

queue.property('autosave', {
  description: 'automatically save the queue after a processing step',
  type: 'boolean',
  default: true
});

queue.property('elements', {
  description: 'the elements currently inside the queue',
  type: 'array',
  default: []
});

queue.property('started', {
  description: 'whether or not the queue has been started',
  type: 'boolean',
  default: false
});

//
// Basic push/shift methods for queue
//
// Remark: Typically, array shifts are considered expensive, and there is
// a way to optimize this by offsetting the index instead of shifting,
// reusing earlier elements in the array, and then resizing the array (only
// here would slicing be used) and resetting the offsets when the array becomes
// full. That said, this is probably negligible compared to the overhead of
// persisting the queue to a datasource anyway.
//
queue.method('push', push, {
  description: 'push an element onto the queue',
  properties: {
    queue: queue.schema,
    job: {
      properties: {
        method: {
          type: 'string'
        },
        with: {
          type: 'any',
          default: {}
        }
      }
    }
  }
});
function push(q, job) {
  var elems = q.elements.push(job);
  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }
  return elems;
}

queue.method('shift', shift, {
  description: 'shift an element off the queue',
  properties: {
    options: {
      properties: queue.schema.properties
    }
  }
});
function shift (q) {
  var shifted = q.elements.shift();
  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }
  return shifted;
}

queue.method('take', take, {
  description: 'take `queue.concurrency` elements off the queue',
  properties: {
    options: {
      properties: queue.schema.properties
    },
    n: {
      type: 'number'
    }
  }
});
function take (q, n) {

  if (typeof n !== 'number') {
    n = q.concurrency;
  }

  var xs = [];
  //
  // We do multiple shifts instead of a concat because q is actually a different
  // object and not a direct reference. However, the underlying array methods
  // are still bound to the original argument.
  //
  // This should probably be considered a bug in the resource engine.
  //
  while (n && q.elements.length) {
    var elem = q.elements.shift();
    xs.push(elem);
    n--;
  }

  return xs;
}

//
// Lists in python have an analogous method of the same name
// http://docs.python.org/2/library/stdtypes.html#typesseq-mutable
//
queue.method('extend', extend, {
  description: 'extend the queue with an array of elements',
  properties: {
    instance: {
      properties: queue.schema.properties
    },
    elems: {
      type: 'any'
    }
  }
});
function extend(q, xs) {
  //
  // This method suffers the same problems with modifying objects in-place,
  // as explained in the `take` method. Here, we get around this in a
  // similar technique.
  //
  xs.forEach(function (elem) {
    q.elements.push(elem);
  });

  if (q.autosave) {
    queue.updateOrCreate(q, function (err, _q) {
      if (err) {
        queue.emit('error', err);
      }
    });
  }

  return q.elements;
}

//
// Run a single job by executing the specified method with the specified
// metadata
//
queue.method('run', run, {
  description: 'run a job',
  type: 'object',
  properties: {
    job: {
      properties: {
        method: {
          type: 'string',
          required: true
        },
        with: {
          type: 'any',
          default: {}
        }
      }
    },
    callback: {
      type: 'function'
    }
  }
});
function run(j, callback) {

  var properties = j.method.split('::'),
      method = resource,
      err;

  properties.forEach(function (p) {
    if (typeof method[p] !== 'undefined') {
      method = method[p];
    }
    else if (!err) {
      err = new Error('could not execute method `' + j.method + '`');
    }
  });

  if (typeof method == 'undefined' ) {
    err = new Error('could not execute method `' + j.method + '`');
  }

  if (err) {
    return callback(err);
  }

  method(j.with, callback);
}

//
// This method takes q.concurrency elements off the front of the queue and
// `queue.run`s them.
//
queue.method('process', process, {
  description: 'process elements off the queue',
  properties: {
    options: queue.schema,
    callback: {
      type: 'function'
    }
  }
});
function process (q, callback) {
  //
  // Process the top q.concurrency elements at once
  //
  var elements = queue.take(q, q.concurrency);

  if (!elements.length) {
    return callback();
  }

  var i;

  elements.forEach(function (elem) {
    queue.run(elem, function (err) {
      i--;

      if (err) {
        queue.emit('error', err);
      }

      //
      // If element repeating is turned on, push the just-processed element
      // back onto the queue
      //
      if (q.repeat) {
        queue.push(q, elem);
      }

      //
      // If autosave is turned on, save the current queue.
      //
      if (q.autosave) {
        queue.updateOrCreate(q, function (_err, _q) {
          if (_err) {
            return queue.emit(_err);
          }
        });
      }

      if (i === 0) {
        callback(null, q);
      }
    });
  });
}

//
// TODO: `unload` method
//
queue.method('load', load, {
  description: 'start processing a queue',
  properties: {
    options: queue.schema
  }
});
function load(q) {

  q.started = true;
  queue.updateOrCreate(q, function (err, q) {
    if (err) {
      queue.emit('error', err);
    }

    _process();

    function _process() {
      queue.get(q.id, function (err, q) {
        if (err) {
          queue.emit('error', err);
        }
        var completed = false,
            timedOut = false;

        //
        // Process again at the end of the timeout *if* the last process step
        // completed
        //
        q._loop = setTimeout(function () {
          //
          // If waiting is turned off, always process the next set of elements
          //
          if (q.started && (completed || !q.wait)) {
            _process();
          }
          else {
            timedOut = true;
          }
        }, q.interval);

        queue.process(q, function (err, result) {
          if (err) {
            //
            // Remark: Emit error, keep going (for now, may change behavior)
            //
            // TODO: Attach some context to the error?
            //
            queue.emit('error', err);
          }

          completed = true;

          //
          // If the timeout occurred while the process step was still running,
          // execute it late
          //
          if (timedOut && q.started) {
            _process();
          }
        });
      });
    }
  });
}

queue.method('unload', unload, {
  description: 'start processing a queue',
  properties: {
    options: queue.schema
  }
});
function unload(q) {
  clearInterval(q._loop);
  q.started = false;

  queue.updateOrCreate(q, function (err, res) {
    if (err) throw err;
  });
}

queue.on('error', function (error, data) {
  resource.logger.error(error);
  resource.logger.data(data);
});

//
// after hooks to add methods to instances of queue
//
queue.after('create', addMethods);
queue.after('get', addMethods);
queue.after('updateOrCreate', addMethods);
queue.after('all', addMethods);
queue.after('find', addMethods);
function addMethods (instance, next) {
  //
  // In cases of 'all' and 'find', "instance" will actually be an *array* of
  // queue instances.
  //
  if (Array.isArray(instance)) {
    //
    // Use "some" to short
    //
    instance.some(function (q, i) {
      var err;
      //
      // Remark: addMethods runs synchronously
      //
      addMethods(q, function (e, _q) {
        if (e) {
          err = e;
          return;
        }
        instance[i] = _q;
      });

      if (err) {
        next(err);
      }

      //
      // If err is truthy, this will short-circuit the .some method
      //
      return err;
    });

    return next(null, instance);
  }

  //
  // Assuming instance is *not* an array, add all the bound methods to
  // the instance
  //
  [
    'push',
    'shift',
    'take',
    'extend',
    'process',
    'load',
    'unload'
  ].forEach(function (m) {
    instance[m] = function (options) {
      return queue[m](instance, options);
    }
  });

  //
  // Alias "start" to "load" at the instance level
  //
  instance.start = instance.load;
  instance.stop = instance.unload;

  next(null, instance);
}

exports.queue = queue;
<|endoftext|>