<commit_before>// dependencies
var AWS = require('aws-sdk');
var gm = require('gm')
            .subClass({ imageMagick: true }); // Enable ImageMagick integration.
var ffmpeg = require('fluent-ffmpeg');
var q = require('q');
var fs = require('fs');
var mime = require('mime');

process.env['PATH'] = process.env['PATH'] + ':' + process.env['LAMBDA_TASK_ROOT']

if (!process.env.NODE_ENV || process.env.NODE_ENV != 'testing') {
  //production
  var handleS3Event = require('handle-s3-event');
} else {
  //local
  var handleS3Event = require('./local_modules/handle-s3-event');
}

var s3 = new AWS.S3();

var moveAndChmodFfmpegBinary = function() {
  var def = q.defer()

  require('child_process').exec(
    'cp /var/task/ffmpeg /tmp/.; chmod 755 /tmp/ffmpeg',
    function (error, stdout, stderr) {
      if (error) {
        def.reject(error)
      } else {
        def.resolve()
      }
    }
  )

  return def.promise;
}

var printFormats = function() {
    var def = q.defer()

    ffmpeg.getAvailableFormats(function(err, formats) {
      if (err) {
        def.reject(err);
      } else {
        def.resolve()
      }
    });

    return def.promise;
}

var lastFunc = function(context) {
  var awsContext = context;
  return function(s3Data) {
    var def = q.defer()

    console.log('s3Data');
    console.log(s3Data);
    def.resolve()
    awsContext.done()

    return def.promise
  }
}

//TODO: unit test
var isValidKey = function(key) {
  function endsWith(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
  }

  return endsWith(key, '.gif')
}

exports.handler = function(event, context) {
  //assign these for prod – if ffmpeg-fluent doesn't find them,
  //it falls back to the machine's local `ffmpeg`
  process.env['FFMPEG_PATH'] = '/tmp/ffmpeg';
  process.env['FFPROBE_PATH'] = '/tmp/ffprobe';

  var promises = [];

  if (!process.env.NODE_ENV || process.env.NODE_ENV != 'testing') {
    promises.push(moveAndChmodFfmpegBinary);
  }

  promises.push(printFormats);
  promises.push(handleS3Event(event));

  promises.push(function(s3Data) {
    var def = q.defer()

    if (isValidKey(s3Data.srcKey)) {
      def.resolve(s3Data);
    } else {
      def.reject("Uploaded file does not end in `.gif`")
    }

    return def.promise
  });

  //fetch .gif from s3

  promises.push(function(s3Data) {
    //TODO: unit tests
    var def = q.defer();

    var params = {Bucket: s3Data.srcBucket, Key: s3Data.srcKey};
    var s3Req = s3.getObject(params)
    s3Req.on('success', function() {
      def.resolve({
        file: file,
        s3Data: s3Data
      });
    })
    s3Req.on('error', function(error) {
      def.reject(error);
    })

    //TODO: rethink path, ensure folder exists
    var file = fs.createWriteStream('./tmp/' + s3Data.srcKey);
    s3Req.createReadStream().pipe(file);

    return def.promise;
  });

  //convert to .mp4

  //send it back to s3
  promises.push(function(options) {
    var def = q.defer();

    var stream = fs.createReadStream(options.file.path);
    //TODO: build elsewhere
    var params = {
      Bucket: options.s3Data.srcBucket + "-resized",
      Key: options.s3Data.srcKey + "-converted.gif",
      Body: stream,
      ContentType: mime.lookup(options.file.path)
    };

    s3.putObject(params, function(err, data) {
      if (err) {
        def.reject(err);
      } else {
        console.log('successful upload');
        def.resolve({})
      }
    });

    return def.promise;
  });

  promises.push(lastFunc(context));

  promises.reduce(q.when, q()).fail(function(err){
    console.log('rejected err');
    console.log(err);
    context.done(err);
  });

};
<commit_msg>specifies /tmp/ for production file writes/reads<commit_after>// dependencies
var AWS = require('aws-sdk');
var gm = require('gm')
            .subClass({ imageMagick: true }); // Enable ImageMagick integration.
var ffmpeg = require('fluent-ffmpeg');
var q = require('q');
var fs = require('fs');
var mime = require('mime');

process.env['PATH'] = process.env['PATH'] + ':' + process.env['LAMBDA_TASK_ROOT']

if (!process.env.NODE_ENV || process.env.NODE_ENV != 'testing') {
  //production
  var handleS3Event = require('handle-s3-event');
} else {
  //local
  var handleS3Event = require('./local_modules/handle-s3-event');
}

var s3 = new AWS.S3();

var moveAndChmodFfmpegBinary = function() {
  var def = q.defer()

  require('child_process').exec(
    'cp /var/task/ffmpeg /tmp/.; chmod 755 /tmp/ffmpeg',
    function (error, stdout, stderr) {
      if (error) {
        def.reject(error)
      } else {
        def.resolve()
      }
    }
  )

  return def.promise;
}

var printFormats = function() {
    var def = q.defer()

    ffmpeg.getAvailableFormats(function(err, formats) {
      if (err) {
        def.reject(err);
      } else {
        def.resolve()
      }
    });

    return def.promise;
}

var lastFunc = function(context) {
  var awsContext = context;
  return function(s3Data) {
    var def = q.defer()

    console.log('s3Data');
    console.log(s3Data);
    def.resolve()
    awsContext.done()

    return def.promise
  }
}

//TODO: unit test
var isValidKey = function(key) {
  function endsWith(str, suffix) {
    return str.indexOf(suffix, str.length - suffix.length) !== -1;
  }

  return endsWith(key, '.gif')
}

exports.handler = function(event, context) {
  //assign these for prod – if ffmpeg-fluent doesn't find them,
  //it falls back to the machine's local `ffmpeg`
  process.env['FFMPEG_PATH'] = '/tmp/ffmpeg';
  process.env['FFPROBE_PATH'] = '/tmp/ffprobe';

  var promises = [];

  if (!process.env.NODE_ENV || process.env.NODE_ENV != 'testing') {
    promises.push(moveAndChmodFfmpegBinary);
  }

  promises.push(printFormats);
  promises.push(handleS3Event(event));

  promises.push(function(s3Data) {
    var def = q.defer()

    if (isValidKey(s3Data.srcKey)) {
      def.resolve(s3Data);
    } else {
      def.reject("Uploaded file does not end in `.gif`")
    }

    return def.promise
  });

  promises.push(function(s3Data) {
    //TODO: unit tests
    var def = q.defer();

    var params = {Bucket: s3Data.srcBucket, Key: s3Data.srcKey};
    var s3Req = s3.getObject(params)
    s3Req.on('success', function() {
      def.resolve({
        file: file,
        s3Data: s3Data
      });
    })
    s3Req.on('error', function(error) {
      def.reject(error);
    })

    var filePath = s3Data.srcKey
    if (!process.env.NODE_ENV || process.env.NODE_ENV != 'testing') {
      //production
      filePath = '/tmp/' + filePath
    } else {
      //local
      filePath = './' + filePath
    }
    var file = fs.createWriteStream(filePath);
    s3Req.createReadStream().pipe(file);

    return def.promise;
  });

  //convert to .mp4

  promises.push(function(options) {
    var def = q.defer();

    var stream = fs.createReadStream(options.file.path);

    var params = {
      Bucket: options.s3Data.srcBucket + "-resized",
      Key: "new-" + options.s3Data.srcKey,
      Body: stream,
      ContentType: mime.lookup(options.file.path)
    };

    s3.putObject(params, function(err, data) {
      if (err) {
        def.reject(err);
      } else {
        console.log('successful upload');
        def.resolve({})
      }
    });

    return def.promise;
  });

  promises.push(lastFunc(context));

  promises.reduce(q.when, q()).fail(function(err){
    console.log('rejected err');
    console.log(err);
    context.done(err);
  });

};
<|endoftext|>


<commit_before>/**
 * Created by cfchou on 02/12/2016.
 */
'use strict';

const BbPromise = require('bluebird');
const _ = require('lodash');
const Path = require('path');
const Fse = require('fs-extra');
const ChildProcess = require('child_process');
const process = require('process');

BbPromise.promisifyAll(Fse);
BbPromise.promisifyAll(ChildProcess);

/**
 * IgnorableError, like Error, breaks the promise chain unless we catch it.
 * It's sole purpose is to distinguish with other Errors. So that it can be
 * seletively caught.
 */
function IgnorableError(message) {
  this.message = message;
  this.name = 'IgnorableError';
  Error.captureStackTrace(this, IgnorableError);
}

IgnorableError.prototype = Object.create(Error.prototype);
IgnorableError.prototype.constructor = IgnorableError;

class PythonIndividually {

  isEnabled() {
    if (this.options['pi-disable']) {
      throw new IgnorableError('disabled due to --pi-disable')
    }
  }

  needCleanup() {
    if (!this.cleanup) {
      throw new IgnorableError('Cleanup is disabled');
    }
  }

  overwriteDefault() {
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    if (!pyIndividually) {
      throw new IgnorableError('no custom.pyIndividually found.');
    }

    if (pyIndividually['wrapName']) {
      this.wrapName = pyIndividually['wrapName'];
    }
    this.log('wrapName: ' + this.wrapName);

    if (pyIndividually['libSubDir']) {
      this.libSubDir = pyIndividually['libSubDir'];
    }
    this.log('libSubDir: ' + this.libSubDir);

    const updater = (key, cliOption, yamlOption, origin) => {
      const enable = 'pi-' + key;
      const disable = 'pi-no-' + key;
      if (cliOption[enable] && cliOption[disable]) {
        throw new that.serverless.classes.Error(
          '[pyIndividually] --' + enable + ' --' + disable + ' both presented');
      }
      if (cliOption[enable]) {
        return true;
      } else if (cliOption[disable]) {
        return false;
      } else if (key in yamlOption) {
        return yamlOption[key];
      }
      return origin;
    }

    this.cleanup = updater('cleanup', this.options, pyIndividually, this.cleanup)
    this.log('cleanup: ' + this.cleanup);
    this.dockerizedPip = updater('dockerizedPip', this.options, pyIndividually,
      this.dockerizedPip)
    this.log('dockerizedPip: ' + this.dockerizedPip);
  };

  checkDocker() {
    if (this.dockerizedPip) {
      this.log('docker version');
      const ret = ChildProcess.spawnSync('docker', ['version']);
      if (ret.error) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version: ' + ret.error.message);
      }
      if (ret.stderr.length != 0) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version: ' + ret.stderr.toString());
      }
      const out = ret.stdout.toString();
      this.log(out);
      if (!out.startsWith('Client') || out.search('Server') == -1) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version invalid output: ' + out);
      }
    }
  }

  selectOne() {
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    const target = this.options.function;
    const targetObj = this.options.functionObj;
    const targetKey = this.wrapName + ':' + target;
    const wrapper = this.wrapName + '.handler';

    if (_.has(pyIndividually, targetKey) &&
      _.endsWith(targetObj.handler, wrapper)) {
      return {
        'name': target,
        'function': targetObj,
        'realHandler': pyIndividually[targetKey]
      };
    }
    throw new IgnorableError('custom.pyIndividually is not set up properly');
  }

  selectAll() {
    const functions = this.serverless.service.functions;
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    const prefix = this.wrapName + ':';
    const prefixLen = (this.wrapName + ':').length;
    const wrapper = this.wrapName + '.handler';
    // validation
    const targetKeys = _.keys(pyIndividually).filter((targetKey) => {
      if (!_.startsWith(targetKey, prefix) || targetKey.length <= prefixLen) {
        return false;
      }
      const target = targetKey.substring(prefixLen);
      return _.has(functions, target) &&
        _.endsWith(functions[target].handler, wrapper);
    });

    // selection
    return _.map(targetKeys, (targetKey) => {
      const target = targetKey.substring(prefixLen);
      return {
        'name': target,
        'function': functions[target],
        'realHandler': pyIndividually[targetKey]
      };
    });
  }

  /**
   * Create a wrapper. Install packages.
   * @param target
   * @returns {Promise.<undefined>}
   */
  work(target) {
    this.log('Installing packages for ' + target.name);
    const wrapper = this.wrapName + '.handler';
    const wrapperPy = this.wrapName + '.py';
    const wrapperDir = target.function.handler.substring(0,
      target.function.handler.length - wrapper.length);
    const packagePath = Path.join(wrapperDir, this.libSubDir);
    const requirementsPy = Path.join(packagePath, '_requirements.py');

    return this.wrap(wrapperDir, wrapperPy, this.libSubDir, target.realHandler).bind(this)
      .then(_.partial(this.fileAccessable, Path.join(wrapperDir, 'requirements.txt'))).bind(this)
      .then(_.partial(this.hard_remove, [wrapperPy, packagePath]))
      .then(() => { return Fse.ensureDirAsync(packagePath); })
      .then(() => {
        return Fse.copyAsync(
          Path.join(__dirname, 'requirements.py'), requirementsPy);
      }).bind(this)
      .then(_.partial(this.install, wrapperDir, this.libSubDir))
      //.then(() => { return Fse.removeAsync(requirementsPy); }).bind(this)
      .then(_.partial(this.hard_remove, [requirementsPy])).bind(this)
      .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined));
  };

  /**
   * Replace exceptions from fs.access with IgnorableError
   * @param filename
   * @returns {Promise.<undefined>|*}
   */
  fileAccessable(filename) {
    return Fse.accessAsync(filename, Fse.constants.R_OK)
      .then(BbPromise.resolve,
        (err) => {
          if (process.env.SLS_DEBUG) {
            this.log(err.stack);
          }
          return BbPromise.reject(new IgnorableError(
            'Can\'t access ' + filename));
      });
  }

  /**
   * soft_remove doesn't break promise chain even seeing failures.
   * @param paths
   * @returns {Promise.<TResult>|*}
   */
  soft_remove(paths) {
    const that = this;
    return BbPromise.settle(_.map(paths, (p) => { return Fse.removeAsync(p); }))
      .then((results) => {
        _.forEach(results, (r) => {
          if (r.isRejected() && process.env.SLS_DEBUG) {
            that.log(r.reason());
          }
        });
        return BbPromise.resolve();
      });
  }

  hard_remove(paths) {
    return BbPromise.all(_.map(paths, (p) => { return Fse.removeAsync(p); }));
  }

  /**
   *
   * @param dir
   * @param filename
   * @param libDir
   * @param realHandler
   * @returns {Promise.<undefined>|*}
   */
  wrap(dir, filename, libDir, realHandler) {
    // realHandler: path_to/hello.handler_name
    // file: hello
    // handler: handler_name
    const path = Path.parse(realHandler);
    const file = path.name;
    const handler = path.ext.split('.').pop();
    const content = `
# vim:fileencoding=utf-8
# ${filename}
# This file is generated on the fly by serverless-python-individually plugin.
import os
import sys

root = os.path.abspath(os.path.join(os.path.dirname(__file__)))
sys.path[0:0] = [root, os.path.join(root, \"${libDir}\")]

from ${file} import ${handler} as real_handler

def handler(event, context):
  return real_handler(event, context)

`;
    const wrapperPath = Path.join(dir, filename);
    this.log('Creating ' + wrapperPath);
    return Fse.outputFileAsync(wrapperPath, content);
  };

  /**
   *
   * @param dir
   * @param libDir
   * @returns {Promise.<undefined>|*}
   */
  install(dir, libDir) {
    const cmd = ((dockerized) => {
      if (dockerized) {
        return ['docker', 'run', '-v', process.cwd() + ':/var/task',
          'lambci/lambda:build-python2.7', 'python',
          Path.posix.join(dir, libDir, '_requirements.py'),
          Path.posix.join(dir, 'requirements.txt'),
          Path.posix.join(dir, libDir)];
      } else {
        return ['python',
          Path.posix.join(dir, libDir, '_requirements.py'),
          Path.posix.join(dir, 'requirements.txt'),
          Path.posix.join(dir, libDir)];
      }
    })(this.dockerizedPip);

    this.log('Installing packagings: ' + cmd.join(' '));
    const ret = ChildProcess.spawnSync(cmd[0], cmd.slice(1));
    this.log(ret.stderr.toString());
    this.log(ret.stdout.toString());
    if (ret.error || ret.stderr.length != 0) {
      return BbPromise.reject(ret.error)
    }
    return BbPromise.resolve()
  }

  /**
   * Catch IgnorableError. Replace it with a promise resolved with value. So
   * that the promise chain goes on.
   * @param value
   * @param e
   * @returns {Promise.<undefined>|*}
   */
  catchIgnorableError(value, e) {
    if (e instanceof IgnorableError) {
      // log, then swallow
      if (process.env.SLS_DEBUG) {
        this.log(e.stack);
      } else {
        this.log('IgnorableError: ' + e.message)
      }
      // NOTE: following functions in the promise chain will be executed.
      return BbPromise.resolve(value);
    } else {
      //throw new this.serverless.classes.Error(e.message);
      throw e;
    }
  };

  /**
   *
   * @param target
   * @returns {Promise.<undefined>|*}
   */
  clean(target) {
    this.log('Cleaning packages for ' + target.name);
    const wrapper = this.wrapName + '.handler';
    const wrapperPy = this.wrapName + '.py';
    const wrapperDir = target.function.handler.substring(0,
      target.function.handler.length - wrapper.length);
    const packagePath = Path.join(wrapperDir, this.libSubDir);
    const wrapperPath = Path.join(wrapperDir, wrapperPy);
    this.log('Deleting ' + wrapperPath + ', ' + packagePath);
    return this.soft_remove([wrapperPath, packagePath]);
  };

  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;
    this.log = (msg) => { serverless.cli.log('[pyIndividually] ' + msg); };
    // overwritten by overwriteDefault()
    this.wrapName = 'wrap';
    this.libSubDir = 'lib';
    this.cleanup = true;
    this.dockerizedPip = false;
    this.hooks = {
      'before:deploy:createDeploymentArtifacts': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.overwriteDefault)
        .then(this.selectAll)
        .map(this.work).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'after:deploy:createDeploymentArtifacts': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.needCleanup)
        .then(this.selectAll)
        .map(this.clean).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'before:deploy:function:packageFunction': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.overwriteDefault)
        .then(this.selectOne)
        .then(this.work).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'after:deploy:function:packageFunction': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.needCleanup)
        .then(this.selectOne)
        .then(this.clean, _.partial(this.catchIgnorableError, undefined)),
    };
  };
}

module.exports = PythonIndividually;
<commit_msg>Pick docker image based on runtime<commit_after>/**
 * Created by cfchou on 02/12/2016.
 */
'use strict';

const BbPromise = require('bluebird');
const _ = require('lodash');
const Path = require('path');
const Fse = require('fs-extra');
const ChildProcess = require('child_process');
const process = require('process');

BbPromise.promisifyAll(Fse);
BbPromise.promisifyAll(ChildProcess);

/**
 * IgnorableError, like Error, breaks the promise chain unless we catch it.
 * It's sole purpose is to distinguish with other Errors. So that it can be
 * seletively caught.
 */
function IgnorableError(message) {
  this.message = message;
  this.name = 'IgnorableError';
  Error.captureStackTrace(this, IgnorableError);
}

IgnorableError.prototype = Object.create(Error.prototype);
IgnorableError.prototype.constructor = IgnorableError;

class PythonIndividually {

  isEnabled() {
    if (this.options['pi-disable']) {
      throw new IgnorableError('disabled due to --pi-disable')
    }
  }

  needCleanup() {
    if (!this.cleanup) {
      throw new IgnorableError('Cleanup is disabled');
    }
  }

  overwriteDefault() {
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    if (!pyIndividually) {
      throw new IgnorableError('no custom.pyIndividually found.');
    }

    if (pyIndividually['wrapName']) {
      this.wrapName = pyIndividually['wrapName'];
    }
    this.log('wrapName: ' + this.wrapName);

    if (pyIndividually['libSubDir']) {
      this.libSubDir = pyIndividually['libSubDir'];
    }
    this.log('libSubDir: ' + this.libSubDir);

    const updater = (key, cliOption, yamlOption, origin) => {
      const enable = 'pi-' + key;
      const disable = 'pi-no-' + key;
      if (cliOption[enable] && cliOption[disable]) {
        throw new that.serverless.classes.Error(
          '[pyIndividually] --' + enable + ' --' + disable + ' both presented');
      }
      if (cliOption[enable]) {
        return true;
      } else if (cliOption[disable]) {
        return false;
      } else if (key in yamlOption) {
        return yamlOption[key];
      }
      return origin;
    }

    this.cleanup = updater('cleanup', this.options, pyIndividually, this.cleanup)
    this.log('cleanup: ' + this.cleanup);
    this.dockerizedPip = updater('dockerizedPip', this.options, pyIndividually,
      this.dockerizedPip)
    this.log('dockerizedPip: ' + this.dockerizedPip);
  };

  checkDocker() {
    if (this.dockerizedPip) {
      this.log('docker version');
      const ret = ChildProcess.spawnSync('docker', ['version']);
      if (ret.error) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version: ' + ret.error.message);
      }
      if (ret.stderr.length != 0) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version: ' + ret.stderr.toString());
      }
      const out = ret.stdout.toString();
      this.log(out);
      if (!out.startsWith('Client') || out.search('Server') == -1) {
        throw new this.serverless.classes.Error(
          '[pyIndividually] docker version invalid output: ' + out);
      }
    }
  }

  selectOne() {
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    const target = this.options.function;
    const targetObj = this.options.functionObj;
    const targetKey = this.wrapName + ':' + target;
    const wrapper = this.wrapName + '.handler';

    if (_.has(pyIndividually, targetKey) &&
      _.endsWith(targetObj.handler, wrapper)) {
      return {
        'name': target,
        'function': targetObj,
        'realHandler': pyIndividually[targetKey]
      };
    }
    throw new IgnorableError('custom.pyIndividually is not set up properly');
  }

  selectAll() {
    const functions = this.serverless.service.functions;
    const pyIndividually = this.serverless.service.custom.pyIndividually;
    const prefix = this.wrapName + ':';
    const prefixLen = (this.wrapName + ':').length;
    const wrapper = this.wrapName + '.handler';
    // validation
    const targetKeys = _.keys(pyIndividually).filter((targetKey) => {
      if (!_.startsWith(targetKey, prefix) || targetKey.length <= prefixLen) {
        return false;
      }
      const target = targetKey.substring(prefixLen);
      return _.has(functions, target) &&
        _.endsWith(functions[target].handler, wrapper);
    });

    // selection
    return _.map(targetKeys, (targetKey) => {
      const target = targetKey.substring(prefixLen);
      return {
        'name': target,
        'function': functions[target],
        'realHandler': pyIndividually[targetKey]
      };
    });
  }

  /**
   * Create a wrapper. Install packages.
   * @param target
   * @returns {Promise.<undefined>}
   */
  work(target) {
    this.log('Installing packages for ' + target.name);
    const wrapper = this.wrapName + '.handler';
    const wrapperPy = this.wrapName + '.py';
    const wrapperDir = target.function.handler.substring(0,
      target.function.handler.length - wrapper.length);
    const packagePath = Path.join(wrapperDir, this.libSubDir);
    const requirementsPy = Path.join(packagePath, '_requirements.py');

    return this.wrap(wrapperDir, wrapperPy, this.libSubDir, target.realHandler).bind(this)
      .then(_.partial(this.fileAccessable, Path.join(wrapperDir, 'requirements.txt'))).bind(this)
      .then(_.partial(this.hard_remove, [wrapperPy, packagePath]))
      .then(() => { return Fse.ensureDirAsync(packagePath); })
      .then(() => {
        return Fse.copyAsync(
          Path.join(__dirname, 'requirements.py'), requirementsPy);
      }).bind(this)
      .then(_.partial(this.install, wrapperDir, this.libSubDir))
      //.then(() => { return Fse.removeAsync(requirementsPy); }).bind(this)
      .then(_.partial(this.hard_remove, [requirementsPy])).bind(this)
      .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined));
  };

  /**
   * Replace exceptions from fs.access with IgnorableError
   * @param filename
   * @returns {Promise.<undefined>|*}
   */
  fileAccessable(filename) {
    return Fse.accessAsync(filename, Fse.constants.R_OK)
      .then(BbPromise.resolve,
        (err) => {
          if (process.env.SLS_DEBUG) {
            this.log(err.stack);
          }
          return BbPromise.reject(new IgnorableError(
            'Can\'t access ' + filename));
      });
  }

  /**
   * soft_remove doesn't break promise chain even seeing failures.
   * @param paths
   * @returns {Promise.<TResult>|*}
   */
  soft_remove(paths) {
    const that = this;
    return BbPromise.settle(_.map(paths, (p) => { return Fse.removeAsync(p); }))
      .then((results) => {
        _.forEach(results, (r) => {
          if (r.isRejected() && process.env.SLS_DEBUG) {
            that.log(r.reason());
          }
        });
        return BbPromise.resolve();
      });
  }

  hard_remove(paths) {
    return BbPromise.all(_.map(paths, (p) => { return Fse.removeAsync(p); }));
  }

  /**
   *
   * @param dir
   * @param filename
   * @param libDir
   * @param realHandler
   * @returns {Promise.<undefined>|*}
   */
  wrap(dir, filename, libDir, realHandler) {
    // realHandler: path_to/hello.handler_name
    // file: hello
    // handler: handler_name
    const path = Path.parse(realHandler);
    const file = path.name;
    const handler = path.ext.split('.').pop();
    const content = `
# vim:fileencoding=utf-8
# ${filename}
# This file is generated on the fly by serverless-python-individually plugin.
import os
import sys

root = os.path.abspath(os.path.join(os.path.dirname(__file__)))
sys.path[0:0] = [root, os.path.join(root, \"${libDir}\")]

from ${file} import ${handler} as real_handler

def handler(event, context):
  return real_handler(event, context)

`;
    const wrapperPath = Path.join(dir, filename);
    this.log('Creating ' + wrapperPath);
    return Fse.outputFileAsync(wrapperPath, content);
  };

  /**
   *
   * @param dir
   * @param libDir
   * @returns {Promise.<undefined>|*}
   */
  install(dir, libDir) {
    const cmd = ((dockerized) => {
      if (dockerized) {
        const runtime = this.serverless.service.provider.runtime;
        return ['docker', 'run', '-v', process.cwd() + ':/var/task',
          'lambci/lambda:build-' + runtime, 'python',
          Path.posix.join(dir, libDir, '_requirements.py'),
          Path.posix.join(dir, 'requirements.txt'),
          Path.posix.join(dir, libDir)];
      } else {
        return ['python',
          Path.posix.join(dir, libDir, '_requirements.py'),
          Path.posix.join(dir, 'requirements.txt'),
          Path.posix.join(dir, libDir)];
      }
    })(this.dockerizedPip);

    this.log('Installing packagings: ' + cmd.join(' '));
    const ret = ChildProcess.spawnSync(cmd[0], cmd.slice(1));
    this.log(ret.stderr.toString());
    this.log(ret.stdout.toString());
    if (ret.error || ret.stderr.length != 0) {
      return BbPromise.reject(ret.error)
    }
    return BbPromise.resolve()
  }

  /**
   * Catch IgnorableError. Replace it with a promise resolved with value. So
   * that the promise chain goes on.
   * @param value
   * @param e
   * @returns {Promise.<undefined>|*}
   */
  catchIgnorableError(value, e) {
    if (e instanceof IgnorableError) {
      // log, then swallow
      if (process.env.SLS_DEBUG) {
        this.log(e.stack);
      } else {
        this.log('IgnorableError: ' + e.message)
      }
      // NOTE: following functions in the promise chain will be executed.
      return BbPromise.resolve(value);
    } else {
      //throw new this.serverless.classes.Error(e.message);
      throw e;
    }
  };

  /**
   *
   * @param target
   * @returns {Promise.<undefined>|*}
   */
  clean(target) {
    this.log('Cleaning packages for ' + target.name);
    const wrapper = this.wrapName + '.handler';
    const wrapperPy = this.wrapName + '.py';
    const wrapperDir = target.function.handler.substring(0,
      target.function.handler.length - wrapper.length);
    const packagePath = Path.join(wrapperDir, this.libSubDir);
    const wrapperPath = Path.join(wrapperDir, wrapperPy);
    this.log('Deleting ' + wrapperPath + ', ' + packagePath);
    return this.soft_remove([wrapperPath, packagePath]);
  };

  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;
    this.log = (msg) => { serverless.cli.log('[pyIndividually] ' + msg); };
    // overwritten by overwriteDefault()
    this.wrapName = 'wrap';
    this.libSubDir = 'lib';
    this.cleanup = true;
    this.dockerizedPip = false;
    this.hooks = {
      'before:deploy:createDeploymentArtifacts': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.overwriteDefault)
        .then(this.selectAll)
        .map(this.work).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'after:deploy:createDeploymentArtifacts': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.needCleanup)
        .then(this.selectAll)
        .map(this.clean).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'before:deploy:function:packageFunction': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.overwriteDefault)
        .then(this.selectOne)
        .then(this.work).bind(this)
        .then(BbPromise.resolve, _.partial(this.catchIgnorableError, undefined)),

      'after:deploy:function:packageFunction': () => BbPromise.bind(this)
        .then(this.isEnabled)
        .then(this.needCleanup)
        .then(this.selectOne)
        .then(this.clean, _.partial(this.catchIgnorableError, undefined)),
    };
  };
}

module.exports = PythonIndividually;
<|endoftext|>


<commit_before>var Map = require('./lib/map');

module.exports = {
    pool: function(datasource, options) {
        return {
            create: function(callback) {
                console.log(datasource);
                var resource = new Map(datasource, options);
                resource.initialize(function(err) {
                    if (err) throw err;
                    callback(resource);
                });
            },
            destroy: function(resource) {
                resource.destroy();
            }
        };
    },
    serve: function(resource, options, callback) {
        resource.render(options, callback);
    }
};

<commit_msg>Remove debugging.<commit_after>var Map = require('./lib/map');

module.exports = {
    pool: function(datasource, options) {
        return {
            create: function(callback) {
                var resource = new Map(datasource, options);
                resource.initialize(function(err) {
                    if (err) throw err;
                    callback(resource);
                });
            },
            destroy: function(resource) {
                resource.destroy();
            }
        };
    },
    serve: function(resource, options, callback) {
        resource.render(options, callback);
    }
};

<|endoftext|>


<commit_before>var u = require('util');
var R = require('ramda');
var H = require('highland');

var neoBatch = require('./utils/neo4batch');
var Queries =  require('./core/queries');

var defaultConfig = {
	Neo4J: {
		hostname: 'localhost',
		port: 7474
	},

	batchTimeout: 1000,
	batchSize: 2500,

	// behave like a graph with equivalences
	types: {
		node: {
			node: {}
		},
		arc: {
			arc: {}
		},
		equivalence: {
			equivalence: {}
		}
	}
};

function Graphmalizer(config)
{
	// store configuration with user overrides
	var conf = R.merge(defaultConfig, config || {});

	// setup neo4j client
	var batchCommit = H.wrapCallback(neoBatch(conf.Neo4J));

	// make query (uses config to determine type ~ structure mapping)
	var types = Object.keys(conf.types);

	function prepare(o)
	{
		// default operation is add
		o.operation = o.operation || 'add';

		// default dataset is stdin
		o.dataset = o.dataset || 'stdin';

		// s, t alias for source/target
		o.source = o.source || o.s;
		o.target = o.target || o.t;

		// ensure (empty) data field
		o.data = o.data || {};

		// check if we have defined the type
		if(!conf.types[o.type])
		{
			console.error(u.format('Unknown type "%s", must be one of: %s', o.type, types));
			return []
		}

		// lookup structure (based on type)
		o.structure = Object.keys(conf.types[o.type])[0];

		try
		{
			return [Queries.mkQuery(o)];
		}
		catch(err)
		{
			console.error(err.stack);
			return []
		}
	}

	// stream of input streams
	this.inputs = H();

	// merge all inputs, convert to cypher queries and batch up
	var input = this.inputs
		.merge()
		.flatMap(prepare)
		.batchWithTimeOrCount(conf.batchTimeout, conf.batchSize);

	// commit batches sequentially
	var output = input
		.fork()
		.map(batchCommit) // a -> stream b
		.series()
		.pluck('results');

	// zip into [request-batch, response-batch]
	var rr = input
		.fork()
		.zip(output);

	// unzip, flatten batches
	var requests = rr.fork().pluck(0).sequence();
	var responses = rr.fork().pluck(1).sequence();

	// zip back up and turn it into a dictionary
	this.system = requests
		.zip(responses)
		.map(function(rr){
			return {
				request: rr[0],
				response: rr[1]
			}
		});
}

// subscribe a stream to the graphmalizer
Graphmalizer.prototype.register = function(stream)
{	// ensure valid arguments
	if(stream)
	{
		if(!H.isStream(stream))
			throw new Error("Must pass a (highland) stream");

		// register input stream
		this.inputs.write(stream);
	}

	// return stream of all request-responses
	return this.system.fork();
};

module.exports = Graphmalizer;
<commit_msg>comment on wrong place<commit_after>var u = require('util');
var R = require('ramda');
var H = require('highland');

var neoBatch = require('./utils/neo4batch');
var Queries =  require('./core/queries');

var defaultConfig = {
	Neo4J: {
		hostname: 'localhost',
		port: 7474
	},

	batchTimeout: 1000,
	batchSize: 2500,

	// behave like a graph with equivalences
	types: {
		node: {
			node: {}
		},
		arc: {
			arc: {}
		},
		equivalence: {
			equivalence: {}
		}
	}
};

function Graphmalizer(config)
{
	// store configuration with user overrides
	var conf = R.merge(defaultConfig, config || {});

	// setup neo4j client
	var batchCommit = H.wrapCallback(neoBatch(conf.Neo4J));

	var types = Object.keys(conf.types);

	// make query (uses config to determine type ~ structure mapping)
	function prepare(o)
	{
		// default operation is add
		o.operation = o.operation || 'add';

		// default dataset is stdin
		o.dataset = o.dataset || 'stdin';

		// s, t alias for source/target
		o.source = o.source || o.s;
		o.target = o.target || o.t;

		// ensure (empty) data field
		o.data = o.data || {};

		// check if we have defined the type
		if(!conf.types[o.type])
		{
			console.error(u.format('Unknown type "%s", must be one of: %s', o.type, types));
			return []
		}

		// lookup structure (based on type)
		o.structure = Object.keys(conf.types[o.type])[0];

		try
		{
			return [Queries.mkQuery(o)];
		}
		catch(err)
		{
			console.error(err.stack);
			return []
		}
	}

	// stream of input streams
	this.inputs = H();

	// merge all inputs, convert to cypher queries and batch up
	var input = this.inputs
		.merge()
		.flatMap(prepare)
		.batchWithTimeOrCount(conf.batchTimeout, conf.batchSize);

	// commit batches sequentially
	var output = input
		.fork()
		.map(batchCommit) // a -> stream b
		.series()
		.pluck('results');

	// zip into [request-batch, response-batch]
	var rr = input
		.fork()
		.zip(output);

	// unzip, flatten batches
	var requests = rr.fork().pluck(0).sequence();
	var responses = rr.fork().pluck(1).sequence();

	// zip back up and turn it into a dictionary
	this.system = requests
		.zip(responses)
		.map(function(rr){
			return {
				request: rr[0],
				response: rr[1]
			}
		});
}

// subscribe a stream to the graphmalizer
Graphmalizer.prototype.register = function(stream)
{	// ensure valid arguments
	if(stream)
	{
		if(!H.isStream(stream))
			throw new Error("Must pass a (highland) stream");

		// register input stream
		this.inputs.write(stream);
	}

	// return stream of all request-responses
	return this.system.fork();
};

module.exports = Graphmalizer;
<|endoftext|>


<commit_before>var redis = require('redis');
var extras = require('./extras');
var RedisEvent = require('./RedisEvent');
var shortId = require('shortid');

/**
 * Node.js scheduling system powered by Redis Keyspace Notifications
 * @param options {Object}
 * @constructor
 */
var Redular = function(options){
  var _this = this;

  if(!options){
    options = {};
  }

  if(!options.redis){
    options.redis = {};
  }

  this.options = {
    id: options.id || shortId.generate(),
    autoConfig: options.autoConfig ||  false,
    dataExpiry: options.dataExpiry || 30,
    redis: {
      port: options.redis.port || 6379,
      host: options.redis.host || '127.0.0.1',
      redis: options.redis.options || {}
    }
  };

  this.redisSub = redis.createClient(this.options.redis.port, this.options.redis.host, this.options.redis.options);
  this.redis = redis.createClient(this.options.redis.port, this.options.redis.host, this.options.redis.options);

  if(this.options.autoConfig){
    var config = '';
    this.redis.config("GET", "notify-keyspace-events", function(err, data){
      if(data){
        config = data[1];
      }
      if(config.indexOf('E') == -1){
        config += 'E'
      }
      if(config.indexOf('x') == -1){
        config += 'x'
      }
      _this.redis.config("SET", "notify-keyspace-events", config)
    });
  }

  var expiryListener = new RedisEvent(this.redisSub, 'expired', /redular:(.+):(.+):(.+)/);
  expiryListener.defineHandler(function(key){
    var clientId = key[1];
    var eventName = key[2];
    var eventId = key[3];

    _this.redis.get('redular-data:' + clientId + ':' + eventName + ':' + eventId, function(err, data){
      if(data){
        data = JSON.parse(data);
        _this.redis.expire('redular-data:' + clientId + ':' + eventName + ':' + eventId, _this.options.dataExpiry);
      }
      _this.handleEvent(eventName, data);
    });

  });
};

Redular.prototype = {
  handlers: {}
};

/**
 * Schedules an event to occur some time in the future
 * @param name {String} - The name of the event
 * @param date {Date} - Javascript date object or string accepted by new Date(), must be in the future
 * @param global {Boolean} - Should this event be handled by all handlers
 */
Redular.prototype.scheduleEvent = function(name, date, global, data){
  var now = new Date();
  date = new Date(date);

  if(extras.isBefore(date, now)){
    return;
  }

  var diff = date.getTime() - now.getTime();
  var seconds = Math.floor(diff / 1000);
  var clientId = this.options.id;
  var eventId = shortId.generate();

  if(global){
    clientId = 'global'
  }

  if(data){
    try{
      data = JSON.stringify(data);
    } catch (e) {
      throw e;
    }
    this.redis.set('redular-data:' + clientId + ':' + name + ':' + eventId, data);
  }

  this.redis.set('redular:' + clientId + ':' + name + ':' + eventId, this.options.id);
  this.redis.expire('redular:' + clientId + ':' + name + ':' + eventId, seconds);
};

/**
 * This is called when an event occurs, if no handler exists nothing happens
 * @param name
 */
Redular.prototype.handleEvent = function(name, data){
  if(this.handlers.hasOwnProperty(name)){
    this.handlers[name](data);
  }
};

/**
 * Define a handler for an event name
 * @param name {String} - The event's name
 * @param action {Function} - The function to be called when the event is triggered
 */
Redular.prototype.defineHandler = function(name, action){
  if(!extras.isFunction(action)){
    throw 'InvalidHandlerException'
  }
  if(this.handlers.hasOwnProperty(name)){
    throw 'HandlerAlreadyExistsException';
  }
  this.handlers[name] = action;
};

module.exports = Redular;
<commit_msg>Modified dataExpiry to be set when event is scheduled. to avoid unhandled events data sticking around<commit_after>var redis = require('redis');
var extras = require('./extras');
var RedisEvent = require('./RedisEvent');
var shortId = require('shortid');

/**
 * Node.js scheduling system powered by Redis Keyspace Notifications
 * @param options {Object}
 * @constructor
 */
var Redular = function(options){
  var _this = this;

  if(!options){
    options = {};
  }

  if(!options.redis){
    options.redis = {};
  }

  this.options = {
    id: options.id || shortId.generate(),
    autoConfig: options.autoConfig ||  false,
    dataExpiry: options.dataExpiry || 30,
    redis: {
      port: options.redis.port || 6379,
      host: options.redis.host || '127.0.0.1',
      redis: options.redis.options || {}
    }
  };

  this.redisSub = redis.createClient(this.options.redis.port, this.options.redis.host, this.options.redis.options);
  this.redis = redis.createClient(this.options.redis.port, this.options.redis.host, this.options.redis.options);

  if(this.options.autoConfig){
    var config = '';
    this.redis.config("GET", "notify-keyspace-events", function(err, data){
      if(data){
        config = data[1];
      }
      if(config.indexOf('E') == -1){
        config += 'E'
      }
      if(config.indexOf('x') == -1){
        config += 'x'
      }
      _this.redis.config("SET", "notify-keyspace-events", config)
    });
  }

  var expiryListener = new RedisEvent(this.redisSub, 'expired', /redular:(.+):(.+):(.+)/);
  expiryListener.defineHandler(function(key){
    var clientId = key[1];
    var eventName = key[2];
    var eventId = key[3];

    _this.redis.get('redular-data:' + clientId + ':' + eventName + ':' + eventId, function(err, data){
      if(data){
        data = JSON.parse(data);
      }
      _this.handleEvent(eventName, data);
    });

  });
};

Redular.prototype = {
  handlers: {}
};

/**
 * Schedules an event to occur some time in the future
 * @param name {String} - The name of the event
 * @param date {Date} - Javascript date object or string accepted by new Date(), must be in the future
 * @param global {Boolean} - Should this event be handled by all handlers
 */
Redular.prototype.scheduleEvent = function(name, date, global, data){
  var now = new Date();
  date = new Date(date);

  if(extras.isBefore(date, now)){
    return;
  }

  var diff = date.getTime() - now.getTime();
  var seconds = Math.floor(diff / 1000);
  var clientId = this.options.id;
  var eventId = shortId.generate();

  if(global){
    clientId = 'global'
  }

  if(data){
    try{
      data = JSON.stringify(data);
    } catch (e) {
      throw e;
    }
    this.redis.set('redular-data:' + clientId + ':' + name + ':' + eventId, data);
    this.redis.expire('redular-data:' + clientId + ':' + name + ':' + eventId, seconds + this.options.dataExpiry);
  }

  this.redis.set('redular:' + clientId + ':' + name + ':' + eventId, this.options.id);
  this.redis.expire('redular:' + clientId + ':' + name + ':' + eventId, seconds);
};

/**
 * This is called when an event occurs, if no handler exists nothing happens
 * @param name
 */
Redular.prototype.handleEvent = function(name, data){
  if(this.handlers.hasOwnProperty(name)){
    this.handlers[name](data);
  }
};

/**
 * Define a handler for an event name
 * @param name {String} - The event's name
 * @param action {Function} - The function to be called when the event is triggered
 */
Redular.prototype.defineHandler = function(name, action){
  if(!extras.isFunction(action)){
    throw 'InvalidHandlerException'
  }
  if(this.handlers.hasOwnProperty(name)){
    throw 'HandlerAlreadyExistsException';
  }
  this.handlers[name] = action;
};

module.exports = Redular;
<|endoftext|>


<commit_before>/**
 * @license nouage https://github.com/podefr/nouage
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2015 Olivier Scherrer <pode.fr@gmail.com>
 */
"use strict";

var compareNumbers = require("compare-numbers"),
    simpleLoop = require("simple-loop"),
    toArray = require("to-array"),
    getClosest = require("get-closest"),
    nestedProperty = require("nested-property"),
    getNodes = require("get-nodes"),
    getDataset =  require("get-dataset"),
    observePlus = require("observe-plus");

function setAttribute(node, property, value) {
    if ('ownerSVGElement' in node) {
        node.setAttribute(property, value);
        return true;
    } else if ('ownerDocument' in node) {
        node[property] = value;
        return true;
    } else {
        throw new Error("invalid element type");
    }
}

/**
 * @class
 * This plugin links dom nodes to a model
 */
module.exports = function BindPluginConstructor($model, $bindings) {

    /**
     * The model to watch
     * @private
     */
    var _model = null,

    /**
     * The list of custom bindings
     * @private
     */
    _bindings = {},

    /**
     * The list of itemRenderers
     * each foreach has its itemRenderer
     * @private
     */
    _itemRenderers = {},

    /**
     * The observers handlers
     * @private
     */
    _observers = {},

    /**
     * The observe-plus wrapper
     * @private
     */
    _observer;

    /**
     * Exposed for debugging purpose
     * @private
     */
    this.observers = _observers;

    function _removeObserversForId(id) {
        if (_observers[id]) {
            _observers[id].forEach(function (handler) {
                handler();
            });
            delete _observers[id];
        }
    }

    /**
     * Define the model to watch for
     * @param {Store} model the model to watch for changes
     * @returns {Boolean} true if the model was set
     */
    this.setModel = function setModel(model) {
        _model = model;
        _observer = observePlus.observe(model);
    };

    /**
     * Get the store that is watched for
     * for debugging only
     * @private
     * @returns the Store
     */
    this.getModel = function getModel() {
        return _model;
    };

    /**
     * The item renderer defines a dom node that can be duplicated
     * It is made available for debugging purpose, don't use it
     * @private
     */
    this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {

        /**
         * The node that will be cloned
         * @private
         */
        var _node = null,

            /**
             * The object that contains plugins.name and plugins.apply
             * @private
             */
            _plugins = null,

            /**
             * The _rootNode where to append the created items
             * @private
             */
            _rootNode = null,

            /**
             * The lower boundary
             * @private
             */
            _start = null,

            /**
             * The number of item to display
             * @private
             */
            _nb = null;

        /**
         * Set the duplicated node
         * @private
         */
        this.setRenderer = function setRenderer(node) {
            _node = node;
            return true;
        };

        /**
         * Returns the node that is going to be used for rendering
         * @private
         * @returns the node that is duplicated
         */
        this.getRenderer = function getRenderer() {
            return _node;
        };

        /**
         * Sets the rootNode and gets the node to copy
         * @private
         * @param {HTMLElement|SVGElement} rootNode
         * @returns
         */
        this.setRootNode = function setRootNode(rootNode) {
            var renderer;
            _rootNode = rootNode;
            renderer = _rootNode.querySelector("*");
            this.setRenderer(renderer);
            if (renderer) {
                _rootNode.removeChild(renderer);
            }
        };

        /**
         * Gets the rootNode
         * @private
         * @returns _rootNode
         */
        this.getRootNode = function getRootNode() {
            return _rootNode;
        };

        /**
         * Set the plugins objet that contains the name and the apply function
         * @private
         * @param plugins
         * @returns true
         */
        this.setPlugins = function setPlugins(plugins) {
            _plugins = plugins;
            return true;
        };

        /**
         * Get the plugins object
         * @private
         * @returns the plugins object
         */
        this.getPlugins = function getPlugins() {
            return _plugins;
        };

        /**
         * The nodes created from the items are stored here
         * @private
         */
        this.items = {};

        /**
         * Set the start limit
         * @private
         * @param {Number} start the value to start rendering the items from
         * @returns the value
         */
        this.setStart = function setStart(start) {
            _start = parseInt(start, 10);
            return _start;
        };

        /**
         * Get the start value
         * @private
         * @returns the start value
         */
        this.getStart = function getStart() {
            return _start;
        };

        /**
         * Set the number of item to display
         * @private
         * @param {Number/String} nb the number of item to display or "*" for all
         * @returns the value
         */
        this.setNb = function setNb(nb) {
            _nb = nb == "*" ? nb : parseInt(nb, 10);
            return _nb;
        };

        /**
         * Get the number of item to display
         * @private
         * @returns the value
         */
        this.getNb = function getNb() {
            return _nb;
        };

        /**
         * Adds a new item and adds it in the items list
         * @private
         * @param {Number} id the id of the item
         * @returns
         */
        this.addItem = function addItem(id) {
            var node;

            if (typeof id == "number" && !this.items[id]) {
                node = this.create(id);
                if (node) {
                    _rootNode.appendChild(node);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        };

        /**
         * Get the next item in the item store given an id.
         * @private
         * @param {Number} id the id to start from
         * @returns
         */
        this.getNextItem = function getNextItem(id) {
            var keys = Object.keys(this.items).map(function (string) {
                    return Number(string);
                }),
                closest = getClosest.greaterNumber(id, keys),
                closestId = keys[closest];

            // Only return if different
            if (closestId != id) {
                return this.items[closestId];
            } else {
                return;
            }
        };

        /**
         * Remove an item from the dom and the items list
         * @private
         * @param {Number} id the id of the item to remove
         * @returns
         */
        this.removeItem = function removeItem(id) {
            var item = this.items[id];
            if (item) {
                _rootNode.removeChild(item);
                delete this.items[id];
                _removeObserversForId(id);
                return true;
            } else {
                return false;
            }
        };

        /**
         * create a new node. Actually makes a clone of the initial one
         * and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
         * @private
         * @param id
         * @param pluginName
         * @returns the associated node
         */
        this.create = function create(id) {
            if (id in _model) {
                var newNode = _node.cloneNode(true),
                    nodes = getNodes(newNode);

                toArray(nodes).forEach(function (child) {
                    child.setAttribute("data-" + _plugins.name + "_id", id);
                });

                this.items[id] = newNode;
                _plugins.apply(newNode);
                return newNode;
            }
        };

        /**
         * Renders the dom tree, adds nodes that are in the boundaries
         * and removes the others
         * @private
         * @returns true boundaries are set
         */
        this.render = function render() {
            // If the number of items to render is all (*)
            // Then get the number of items
            var _tmpNb = _nb == "*" ? _model.length : _nb;

            // This will store the items to remove
            var marked = [];

            // Render only if boundaries have been set
            if (_nb !== null && _start !== null) {

                // Loop through the existing items
                simpleLoop(this.items, function (value, idx) {
                    // If an item is out of the boundary
                    idx = Number(idx);

                    if (idx < _start || idx >= (_start + _tmpNb) || !idx in _model) {
                        // Mark it
                        marked.push(idx);
                    }
                }, this);

                // Remove the marked item from the highest id to the lowest
                // Doing this will avoid the id change during removal
                // (removing id 2 will make id 3 becoming 2)
                marked.sort(compareNumbers.desc).forEach(this.removeItem, this);

                // Now that we have removed the old nodes
                // Add the missing one
                for (var i=_start, l=_tmpNb+_start; i<l; i++) {
                    this.addItem(i);
                }
                return true;
            } else {
                return false;
            }
        };

        if ($plugins) {
            this.setPlugins($plugins);
        }
        if ($rootNode) {
            this.setRootNode($rootNode);
        }
    };

    /**
     * Save an itemRenderer according to its id
     * @private
     * @param {String} id the id of the itemRenderer
     * @param {ItemRenderer} itemRenderer an itemRenderer object
     */
    this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
        id = id || "default";
        _itemRenderers[id] = itemRenderer;
    };

    /**
     * Get an itemRenderer
     * @private
     * @param {String} id the name of the itemRenderer
     * @returns the itemRenderer
     */
    this.getItemRenderer = function getItemRenderer(id) {
        return _itemRenderers[id];
    };

    /**
     * Expands the inner dom nodes of a given dom node, filling it with model's values
     * @param {HTMLElement|SVGElement} node the dom node to apply foreach to
     */
    this.foreach = function foreach(node, idItemRenderer, start, nb) {
        var itemRenderer = new this.ItemRenderer(this.plugins, node);

        itemRenderer.setStart(start || 0);
        itemRenderer.setNb(nb || "*");

        itemRenderer.render();

        // Add the newly created item
        _observer.observe("added", itemRenderer.render, itemRenderer);

        // If an item is deleted
        _observer.observe("splice", function (event) {
            event.removed.forEach(function (removedItem, idx) {
                itemRenderer.removeItem(+event.index + idx);
                // Also remove all observers
                _removeObserversForId(+event.index + idx);
            });
        },this);

        this.setItemRenderer(idItemRenderer, itemRenderer);
    };

    /**
     * Update the lower boundary of a foreach
     * @param {String} id the id of the foreach to update
     * @param {Number} start the new value
     * @returns true if the foreach exists
     */
    this.updateStart = function updateStart(id, start) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.setStart(start);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Update the number of item to display in a foreach
     * @param {String} id the id of the foreach to update
     * @param {Number} nb the number of items to display
     * @returns true if the foreach exists
     */
    this.updateNb = function updateNb(id, nb) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.setNb(nb);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Refresh a foreach after having modified its limits
     * @param {String} id the id of the foreach to refresh
     * @returns true if the foreach exists
     */
    this.refresh = function refresh(id) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.render();
            return true;
        } else {
            return false;
        }
    };

    /**
     * Both ways binding between a dom node attributes and the model
     * @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
     * @param {String} name the name of the property to look for in the model's value
     * @returns
     */
    this.bind = function bind(node, property, name) {
        // Name can be unset if the value of a row is plain text
        name = name || "";

        // If the model is an array, the id is the index of the model's item to look for
        var id = node.getAttribute("data-" + this.plugins.name + "_id"),

            prefixedName = id ? id + "." + name : name,

            // Get the model's value
             val = nestedProperty.get(_model, prefixedName),

            // When calling bind like bind:newBinding,param1, param2... we need to get them
            extraParam = toArray(arguments).slice(3);

        // 0 and false are acceptable falsy values
        if (val || val === 0 || val === false) {
            // If the binding hasn't been overriden
            if (!this.execBinding.apply(this,
                [node, property, val]
                    // Extra params are passed to the new binding too
                    .concat(extraParam))) {
                // Execute the default one which is a simple assignation

                setAttribute(node, property, val);
            }
        }

        // Only watch for changes (two way data binding) if the binding
        // has not been redefined
        if (!this.hasBinding(property)) {
            node.addEventListener("change", function () {
                if (nestedProperty.has(_model, prefixedName)) {
                    nestedProperty.set(_model, prefixedName, node[property]);
                }
            }, true);
        }

        // Watch for changes
        this.observers[prefixedName] = this.observers[prefixedName] || [];

        this.observers[prefixedName].push(_observer.observeValue(prefixedName, function (event) {
            var value = event.value;

            if (!this.execBinding.apply(this,
                [node, property, value]
                    // passing extra params too
                    .concat(extraParam))) {

                setAttribute(node, property, value);
            }
        }, this));

    };

    this.getItemIndex = function getElementId(dom) {
        var dataset = getDataset(dom);

        if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
            return +dataset[this.plugins.name + "_id"];
        } else {
            return false;
        }
    };

    /**
     * Prevents the submit and set the model with all form's inputs
     * @param {HTMLFormElement} DOMfrom
     * @returns true if valid form
     */
    this.form = function form(DOMform) {
        if (DOMform && DOMform.nodeName == "FORM") {
            var that = this;
            DOMform.addEventListener("submit", function (event) {
                toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
                event.preventDefault();
            }, true);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Add a new way to handle a binding
     * @param {String} name of the binding
     * @param {Function} binding the function to handle the binding
     * @returns
     */
    this.addBinding = function addBinding(name, binding) {
        if (name && typeof name == "string" && typeof binding == "function") {
            _bindings[name] = binding;
            return true;
        } else {
            return false;
        }
    };

    /**
     * Execute a binding
     * Only used by the plugin
     * @private
     * @param {HTMLElement} node the dom node on which to execute the binding
     * @param {String} name the name of the binding
     * @param {Any type} value the value to pass to the function
     * @returns
     */
    this.execBinding = function execBinding(node, name) {
        if (this.hasBinding(name)) {
            _bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
            return true;
        } else {
            return false;
        }
    };

    /**
     * Check if the binding exists
     * @private
     * @param {String} name the name of the binding
     * @returns
     */
    this.hasBinding = function hasBinding(name) {
        return _bindings.hasOwnProperty(name);
    };

    /**
     * Get a binding
     * For debugging only
     * @private
     * @param {String} name the name of the binding
     * @returns
     */
    this.getBinding = function getBinding(name) {
        return _bindings[name];
    };

    /**
     * Add multiple binding at once
     * @param {Object} list the list of bindings to add
     * @returns
     */
    this.addBindings = function addBindings(list) {
        return simpleLoop(list, function (binding, name) {
            this.addBinding(name, binding);
        }, this);
    };

    // Inits the model
    this.setModel($model);
    // Inits bindings

    if ($bindings) {
        this.addBindings($bindings);
    }
};
<commit_msg>remove useless function now that IE8 is not supported anymore<commit_after>/**
 * @license nouage https://github.com/podefr/nouage
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2015 Olivier Scherrer <pode.fr@gmail.com>
 */
"use strict";

var compareNumbers = require("compare-numbers"),
    simpleLoop = require("simple-loop"),
    toArray = require("to-array"),
    getClosest = require("get-closest"),
    nestedProperty = require("nested-property"),
    getNodes = require("get-nodes"),
    getDataset =  require("get-dataset"),
    observePlus = require("observe-plus");

function setAttribute(node, property, value) {
    if ('ownerSVGElement' in node) {
        node.setAttribute(property, value);
        return true;
    } else if ('ownerDocument' in node) {
        node[property] = value;
        return true;
    } else {
        throw new Error("invalid element type");
    }
}

/**
 * @class
 * This plugin links dom nodes to a model
 */
module.exports = function BindPluginConstructor($model, $bindings) {

    /**
     * The model to watch
     * @private
     */
    var _model = null,

    /**
     * The list of custom bindings
     * @private
     */
    _bindings = {},

    /**
     * The list of itemRenderers
     * each foreach has its itemRenderer
     * @private
     */
    _itemRenderers = {},

    /**
     * The observers handlers
     * @private
     */
    _observers = {},

    /**
     * The observe-plus wrapper
     * @private
     */
    _observer;

    /**
     * Exposed for debugging purpose
     * @private
     */
    this.observers = _observers;

    function _removeObserversForId(id) {
        if (_observers[id]) {
            _observers[id].forEach(function (handler) {
                handler();
            });
            delete _observers[id];
        }
    }

    /**
     * Define the model to watch for
     * @param {Store} model the model to watch for changes
     * @returns {Boolean} true if the model was set
     */
    this.setModel = function setModel(model) {
        _model = model;
        _observer = observePlus.observe(model);
    };

    /**
     * Get the store that is watched for
     * for debugging only
     * @private
     * @returns the Store
     */
    this.getModel = function getModel() {
        return _model;
    };

    /**
     * The item renderer defines a dom node that can be duplicated
     * It is made available for debugging purpose, don't use it
     * @private
     */
    this.ItemRenderer = function ItemRenderer($plugins, $rootNode) {

        /**
         * The node that will be cloned
         * @private
         */
        var _node = null,

            /**
             * The object that contains plugins.name and plugins.apply
             * @private
             */
            _plugins = null,

            /**
             * The _rootNode where to append the created items
             * @private
             */
            _rootNode = null,

            /**
             * The lower boundary
             * @private
             */
            _start = null,

            /**
             * The number of item to display
             * @private
             */
            _nb = null;

        /**
         * Set the duplicated node
         * @private
         */
        this.setRenderer = function setRenderer(node) {
            _node = node;
            return true;
        };

        /**
         * Returns the node that is going to be used for rendering
         * @private
         * @returns the node that is duplicated
         */
        this.getRenderer = function getRenderer() {
            return _node;
        };

        /**
         * Sets the rootNode and gets the node to copy
         * @private
         * @param {HTMLElement|SVGElement} rootNode
         * @returns
         */
        this.setRootNode = function setRootNode(rootNode) {
            var renderer;
            _rootNode = rootNode;
            renderer = _rootNode.querySelector("*");
            this.setRenderer(renderer);
            if (renderer) {
                _rootNode.removeChild(renderer);
            }
        };

        /**
         * Gets the rootNode
         * @private
         * @returns _rootNode
         */
        this.getRootNode = function getRootNode() {
            return _rootNode;
        };

        /**
         * Set the plugins objet that contains the name and the apply function
         * @private
         * @param plugins
         * @returns true
         */
        this.setPlugins = function setPlugins(plugins) {
            _plugins = plugins;
            return true;
        };

        /**
         * Get the plugins object
         * @private
         * @returns the plugins object
         */
        this.getPlugins = function getPlugins() {
            return _plugins;
        };

        /**
         * The nodes created from the items are stored here
         * @private
         */
        this.items = {};

        /**
         * Set the start limit
         * @private
         * @param {Number} start the value to start rendering the items from
         * @returns the value
         */
        this.setStart = function setStart(start) {
            _start = parseInt(start, 10);
            return _start;
        };

        /**
         * Get the start value
         * @private
         * @returns the start value
         */
        this.getStart = function getStart() {
            return _start;
        };

        /**
         * Set the number of item to display
         * @private
         * @param {Number/String} nb the number of item to display or "*" for all
         * @returns the value
         */
        this.setNb = function setNb(nb) {
            _nb = nb == "*" ? nb : parseInt(nb, 10);
            return _nb;
        };

        /**
         * Get the number of item to display
         * @private
         * @returns the value
         */
        this.getNb = function getNb() {
            return _nb;
        };

        /**
         * Adds a new item and adds it in the items list
         * @private
         * @param {Number} id the id of the item
         * @returns
         */
        this.addItem = function addItem(id) {
            var node;

            if (typeof id == "number" && !this.items[id]) {
                node = this.create(id);
                if (node) {
                    _rootNode.appendChild(node);
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        };

        /**
         * Remove an item from the dom and the items list
         * @private
         * @param {Number} id the id of the item to remove
         * @returns
         */
        this.removeItem = function removeItem(id) {
            var item = this.items[id];
            if (item) {
                _rootNode.removeChild(item);
                delete this.items[id];
                _removeObserversForId(id);
                return true;
            } else {
                return false;
            }
        };

        /**
         * create a new node. Actually makes a clone of the initial one
         * and adds pluginname_id to each node, then calls plugins.apply to apply all plugins
         * @private
         * @param id
         * @param pluginName
         * @returns the associated node
         */
        this.create = function create(id) {
            if (id in _model) {
                var newNode = _node.cloneNode(true),
                    nodes = getNodes(newNode);

                toArray(nodes).forEach(function (child) {
                    child.setAttribute("data-" + _plugins.name + "_id", id);
                });

                this.items[id] = newNode;
                _plugins.apply(newNode);
                return newNode;
            }
        };

        /**
         * Renders the dom tree, adds nodes that are in the boundaries
         * and removes the others
         * @private
         * @returns true boundaries are set
         */
        this.render = function render() {
            // If the number of items to render is all (*)
            // Then get the number of items
            var _tmpNb = _nb == "*" ? _model.length : _nb;

            // This will store the items to remove
            var marked = [];

            // Render only if boundaries have been set
            if (_nb !== null && _start !== null) {

                // Loop through the existing items
                simpleLoop(this.items, function (value, idx) {
                    // If an item is out of the boundary
                    idx = Number(idx);

                    if (idx < _start || idx >= (_start + _tmpNb) || !idx in _model) {
                        // Mark it
                        marked.push(idx);
                    }
                }, this);

                // Remove the marked item from the highest id to the lowest
                // Doing this will avoid the id change during removal
                // (removing id 2 will make id 3 becoming 2)
                marked.sort(compareNumbers.desc).forEach(this.removeItem, this);

                // Now that we have removed the old nodes
                // Add the missing one
                for (var i=_start, l=_tmpNb+_start; i<l; i++) {
                    this.addItem(i);
                }
                return true;
            } else {
                return false;
            }
        };

        if ($plugins) {
            this.setPlugins($plugins);
        }
        if ($rootNode) {
            this.setRootNode($rootNode);
        }
    };

    /**
     * Save an itemRenderer according to its id
     * @private
     * @param {String} id the id of the itemRenderer
     * @param {ItemRenderer} itemRenderer an itemRenderer object
     */
    this.setItemRenderer = function setItemRenderer(id, itemRenderer) {
        id = id || "default";
        _itemRenderers[id] = itemRenderer;
    };

    /**
     * Get an itemRenderer
     * @private
     * @param {String} id the name of the itemRenderer
     * @returns the itemRenderer
     */
    this.getItemRenderer = function getItemRenderer(id) {
        return _itemRenderers[id];
    };

    /**
     * Expands the inner dom nodes of a given dom node, filling it with model's values
     * @param {HTMLElement|SVGElement} node the dom node to apply foreach to
     */
    this.foreach = function foreach(node, idItemRenderer, start, nb) {
        var itemRenderer = new this.ItemRenderer(this.plugins, node);

        itemRenderer.setStart(start || 0);
        itemRenderer.setNb(nb || "*");

        itemRenderer.render();

        // Add the newly created item
        _observer.observe("added", itemRenderer.render, itemRenderer);

        // If an item is deleted
        _observer.observe("splice", function (event) {
            event.removed.forEach(function (removedItem, idx) {
                itemRenderer.removeItem(+event.index + idx);
                // Also remove all observers
                _removeObserversForId(+event.index + idx);
            });
        },this);

        this.setItemRenderer(idItemRenderer, itemRenderer);
    };

    /**
     * Update the lower boundary of a foreach
     * @param {String} id the id of the foreach to update
     * @param {Number} start the new value
     * @returns true if the foreach exists
     */
    this.updateStart = function updateStart(id, start) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.setStart(start);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Update the number of item to display in a foreach
     * @param {String} id the id of the foreach to update
     * @param {Number} nb the number of items to display
     * @returns true if the foreach exists
     */
    this.updateNb = function updateNb(id, nb) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.setNb(nb);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Refresh a foreach after having modified its limits
     * @param {String} id the id of the foreach to refresh
     * @returns true if the foreach exists
     */
    this.refresh = function refresh(id) {
        var itemRenderer = this.getItemRenderer(id);
        if (itemRenderer) {
            itemRenderer.render();
            return true;
        } else {
            return false;
        }
    };

    /**
     * Both ways binding between a dom node attributes and the model
     * @param {HTMLElement|SVGElement} node the dom node to apply the plugin to
     * @param {String} name the name of the property to look for in the model's value
     * @returns
     */
    this.bind = function bind(node, property, name) {
        // Name can be unset if the value of a row is plain text
        name = name || "";

        // If the model is an array, the id is the index of the model's item to look for
        var id = node.getAttribute("data-" + this.plugins.name + "_id"),

            prefixedName = id ? id + "." + name : name,

            // Get the model's value
             val = nestedProperty.get(_model, prefixedName),

            // When calling bind like bind:newBinding,param1, param2... we need to get them
            extraParam = toArray(arguments).slice(3);

        // 0 and false are acceptable falsy values
        if (val || val === 0 || val === false) {
            // If the binding hasn't been overriden
            if (!this.execBinding.apply(this,
                [node, property, val]
                    // Extra params are passed to the new binding too
                    .concat(extraParam))) {
                // Execute the default one which is a simple assignation

                setAttribute(node, property, val);
            }
        }

        // Only watch for changes (two way data binding) if the binding
        // has not been redefined
        if (!this.hasBinding(property)) {
            node.addEventListener("change", function () {
                if (nestedProperty.has(_model, prefixedName)) {
                    nestedProperty.set(_model, prefixedName, node[property]);
                }
            }, true);
        }

        // Watch for changes
        this.observers[prefixedName] = this.observers[prefixedName] || [];

        this.observers[prefixedName].push(_observer.observeValue(prefixedName, function (event) {
            var value = event.value;

            if (!this.execBinding.apply(this,
                [node, property, value]
                    // passing extra params too
                    .concat(extraParam))) {

                setAttribute(node, property, value);
            }
        }, this));

    };

    this.getItemIndex = function getElementId(dom) {
        var dataset = getDataset(dom);

        if (dataset && typeof dataset[this.plugins.name + "_id"] != "undefined") {
            return +dataset[this.plugins.name + "_id"];
        } else {
            return false;
        }
    };

    /**
     * Prevents the submit and set the model with all form's inputs
     * @param {HTMLFormElement} DOMfrom
     * @returns true if valid form
     */
    this.form = function form(DOMform) {
        if (DOMform && DOMform.nodeName == "FORM") {
            var that = this;
            DOMform.addEventListener("submit", function (event) {
                toArray(DOMform.querySelectorAll("[name]")).forEach(that.set, that);
                event.preventDefault();
            }, true);
            return true;
        } else {
            return false;
        }
    };

    /**
     * Add a new way to handle a binding
     * @param {String} name of the binding
     * @param {Function} binding the function to handle the binding
     * @returns
     */
    this.addBinding = function addBinding(name, binding) {
        if (name && typeof name == "string" && typeof binding == "function") {
            _bindings[name] = binding;
            return true;
        } else {
            return false;
        }
    };

    /**
     * Execute a binding
     * Only used by the plugin
     * @private
     * @param {HTMLElement} node the dom node on which to execute the binding
     * @param {String} name the name of the binding
     * @param {Any type} value the value to pass to the function
     * @returns
     */
    this.execBinding = function execBinding(node, name) {
        if (this.hasBinding(name)) {
            _bindings[name].apply(node, Array.prototype.slice.call(arguments, 2));
            return true;
        } else {
            return false;
        }
    };

    /**
     * Check if the binding exists
     * @private
     * @param {String} name the name of the binding
     * @returns
     */
    this.hasBinding = function hasBinding(name) {
        return _bindings.hasOwnProperty(name);
    };

    /**
     * Get a binding
     * For debugging only
     * @private
     * @param {String} name the name of the binding
     * @returns
     */
    this.getBinding = function getBinding(name) {
        return _bindings[name];
    };

    /**
     * Add multiple binding at once
     * @param {Object} list the list of bindings to add
     * @returns
     */
    this.addBindings = function addBindings(list) {
        return simpleLoop(list, function (binding, name) {
            this.addBinding(name, binding);
        }, this);
    };

    // Inits the model
    this.setModel($model);
    // Inits bindings

    if ($bindings) {
        this.addBindings($bindings);
    }
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

// See https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/45
require('object.values').shim();

const objectAssign = require('object-assign');

const flatten = arr => arr.reduce((prev, curr) => prev.concat(curr), []);

const isChunkBelongToHtml = (chunk, roots) => {
  for (const root of roots) {
    if (root.hash === chunk.renderedHash) return true;
  }
  for (const parent of chunk.parents) {
    if (isChunkBelongToHtml(parent, roots)) return true;
  }
  return false;
};

const defaultOptions = {
  rel: 'preload',
  include: 'asyncChunks',
  fileBlacklist: [/\.map/]
};

class PreloadPlugin {
  constructor(options) {
    this.options = objectAssign({}, defaultOptions, options);
  }

  apply(compiler) {
    const options = this.options;
    compiler.plugin('compilation', compilation => {
      compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, cb) => {
        let filesToInclude = '';
        let extractedChunks = [];
        // 'asyncChunks' are chunks intended for lazy/async loading usually generated as
        // part of code-splitting with import() or require.ensure(). By default, asyncChunks
        // get wired up using link rel=preload when using this plugin. This behaviour can be
        // configured to preload all types of chunks or just prefetch chunks as needed.
        if (options.include === undefined || options.include === 'asyncChunks') {
          try {
            extractedChunks = compilation.chunks.filter(chunk => !chunk.isInitial());
          } catch (e) {
            extractedChunks = compilation.chunks;
          }
        } else if (options.include === 'all') {
            // Async chunks, vendor chunks, normal chunks.
          extractedChunks = compilation.chunks;
        } else if (Array.isArray(options.include)) {
          // Keep only user specified chunks
          extractedChunks = compilation
              .chunks
              .filter((chunk) => {
                const chunkName = chunk.name;
                // Works only for named chunks
                if (!chunkName) {
                  return false;
                }
                return options.include.indexOf(chunkName) > -1;
              });
        }

        const publicPath = compilation.outputOptions.publicPath || '';

        // Only handle the chunk import by the htmlWebpackPlugin
        extractedChunks = extractedChunks.filter(chunk => isChunkBelongToHtml(chunk, Object.values(htmlPluginData.assets.chunks)));

        flatten(extractedChunks.map(chunk => chunk.files)).filter(entry => {
          return this.options.fileBlacklist.every(regex => regex.test(entry) === false);
        }).forEach(entry => {
          entry = `${publicPath}${entry}`;
          if (options.rel === 'preload') {
            // If `as` value is not provided in option, dynamically determine the correct
            // value depends on suffix of filename. Otherwise use the given `as` value.
            let asValue;
            if (!options.as) {
              if (entry.match(/\.css$/)) asValue = 'style';
              else if (entry.match(/\.woff2$/)) asValue = 'font';
              else asValue = 'script';
            } else if (typeof options.as === 'function') {
              asValue = options.as(entry);
            } else {
              asValue = options.as;
            }
            const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '';
            filesToInclude+= `<link rel="${options.rel}" as="${asValue}" ${crossOrigin}href="${entry}">\n`;
          } else {
            // If preload isn't specified, the only other valid entry is prefetch here
            // You could specify preconnect but as we're dealing with direct paths to resources
            // instead of origins that would make less sense.
            filesToInclude+= `<link rel="${options.rel}" href="${entry}">\n`;
          }
        });
        if (htmlPluginData.html.indexOf('') !== -1) {
          // If a valid closing  is found, update it to include preload/prefetch tags
          htmlPluginData.html = htmlPluginData.html.replace('', filesToInclude + '');
        } else {
          // Otherwise assume at least a  is present and update it to include a new
          htmlPluginData.html = htmlPluginData.html.replace('', '' + filesToInclude + '');
        }
        cb(null, htmlPluginData);
      });
    });
  }
}

module.exports = PreloadPlugin;
<commit_msg>Fix circular recursion?<commit_after>/**
 * @license
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

// See https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/45
require('object.values').shim();

const objectAssign = require('object-assign');

const flatten = arr => arr.reduce((prev, curr) => prev.concat(curr), []);

const doesChunkBelongToHTML = (chunk, roots, visitedChunks) => {
  // Prevent circular recursion.
  // See https://github.com/GoogleChromeLabs/preload-webpack-plugin/issues/49
  if (visitedChunks[chunk.renderedHash]) {
    return false;
  }
  visitedChunks[chunk.renderedHash] = true;

  for (const root of roots) {
    if (root.hash === chunk.renderedHash) {
      return true;
    }
  }

  for (const parent of chunk.parents) {
    if (doesChunkBelongToHTML(parent, roots, visitedChunks)) {
      return true;
    }
  }

  return false;
};

const defaultOptions = {
  rel: 'preload',
  include: 'asyncChunks',
  fileBlacklist: [/\.map/]
};

class PreloadPlugin {
  constructor(options) {
    this.options = objectAssign({}, defaultOptions, options);
  }

  apply(compiler) {
    const options = this.options;
    compiler.plugin('compilation', compilation => {
      compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, cb) => {
        let filesToInclude = '';
        let extractedChunks = [];
        // 'asyncChunks' are chunks intended for lazy/async loading usually generated as
        // part of code-splitting with import() or require.ensure(). By default, asyncChunks
        // get wired up using link rel=preload when using this plugin. This behaviour can be
        // configured to preload all types of chunks or just prefetch chunks as needed.
        if (options.include === undefined || options.include === 'asyncChunks') {
          try {
            extractedChunks = compilation.chunks.filter(chunk => !chunk.isInitial());
          } catch (e) {
            extractedChunks = compilation.chunks;
          }
        } else if (options.include === 'all') {
            // Async chunks, vendor chunks, normal chunks.
          extractedChunks = compilation.chunks;
        } else if (Array.isArray(options.include)) {
          // Keep only user specified chunks
          extractedChunks = compilation
              .chunks
              .filter((chunk) => {
                const chunkName = chunk.name;
                // Works only for named chunks
                if (!chunkName) {
                  return false;
                }
                return options.include.indexOf(chunkName) > -1;
              });
        }

        const publicPath = compilation.outputOptions.publicPath || '';

        // Only handle the chunk import by the htmlWebpackPlugin
        extractedChunks = extractedChunks.filter(chunk => doesChunkBelongToHTML(
          chunk, Object.values(htmlPluginData.assets.chunks), {}));

        flatten(extractedChunks.map(chunk => chunk.files)).filter(entry => {
          return this.options.fileBlacklist.every(regex => regex.test(entry) === false);
        }).forEach(entry => {
          entry = `${publicPath}${entry}`;
          if (options.rel === 'preload') {
            // If `as` value is not provided in option, dynamically determine the correct
            // value depends on suffix of filename. Otherwise use the given `as` value.
            let asValue;
            if (!options.as) {
              if (entry.match(/\.css$/)) asValue = 'style';
              else if (entry.match(/\.woff2$/)) asValue = 'font';
              else asValue = 'script';
            } else if (typeof options.as === 'function') {
              asValue = options.as(entry);
            } else {
              asValue = options.as;
            }
            const crossOrigin = asValue === 'font' ? 'crossorigin="crossorigin" ' : '';
            filesToInclude+= `<link rel="${options.rel}" as="${asValue}" ${crossOrigin}href="${entry}">\n`;
          } else {
            // If preload isn't specified, the only other valid entry is prefetch here
            // You could specify preconnect but as we're dealing with direct paths to resources
            // instead of origins that would make less sense.
            filesToInclude+= `<link rel="${options.rel}" href="${entry}">\n`;
          }
        });
        if (htmlPluginData.html.indexOf('') !== -1) {
          // If a valid closing  is found, update it to include preload/prefetch tags
          htmlPluginData.html = htmlPluginData.html.replace('', filesToInclude + '');
        } else {
          // Otherwise assume at least a  is present and update it to include a new
          htmlPluginData.html = htmlPluginData.html.replace('', '' + filesToInclude + '');
        }
        cb(null, htmlPluginData);
      });
    });
  }
}

module.exports = PreloadPlugin;
<|endoftext|>


<commit_before>'use strict';

var ctx;
var cvs;

function Car () {
    this.turn = 0;
    this.speed = 100;

    return this;
}

Car.prototype.drawControls = function (){
    function drawSteeringWheel(turn) {
        var px = 300;
        var py = 400;
        var r = 50;

        ctx.save();
        ctx.lineWidth = 5;

        ctx.translate(px, py);
        ctx.rotate(turn);

        circle(ctx, 0, 0, r);
        line (ctx, 0, 0, 0, -r);

        ctx.restore();
    }

    function drawSpeedoMeter(speed) {
        var px = 300;
        var py = 200;
        var r = 150;

        var maxSpeedometerSpeed = 200;

        ctx.save();
            ctx.translate(px, py);
            halfCircle (ctx, 0, 0, r);
            line (ctx, -r, 0, r, 0);

            function speedToArc(speed) {
                return (speed / maxSpeedometerSpeed) * 3.14  - 3.14;
            }

            // measurements
            for (var i = 0; i < maxSpeedometerSpeed; i += 10) {
                ctx.save();
                ctx.rotate(speedToArc(i));
                line (ctx, r, 0, r-10, 0);
                ctx.restore();
            }

            // dial
            ctx.save();
                ctx.lineWidth = 8;
                ctx.rotate(speedToArc(speed));
                line (ctx, 0, 0, r-10, 0);
            ctx.restore();

        ctx.restore();
    }

    drawSpeedoMeter(this.speed);
    drawSteeringWheel(this.turn);
}

// Actual game state
// Global because fuck it

var car = new Car();
var input = {
    turningLeft: false,
    turningRight: false
}
;
var getScore;
function drawScore() {
    var $scorebox = document.getElementById('scorebox');
    $scorebox.innerText = getScore() + 'm';
}

function keyDown(e) {
    switch (e.keyCode) {
        case 37: // left arrow
            input.turningLeft = true;
        break;
        case 38: // up arrow
        break;
        case 39: // right arrow
            input.turningRight = true;
        break;
        case 40: // down arrow
        break;
    }

    return false;
}

function keyUp(e) {
    switch (e.keyCode) {
        case 37: // left arrow
            input.turningLeft = false;
        break;
        case 38: // up arrow
        break;
        case 39: // right arrow
            input.turningRight = false;
        break;
        case 40: // down arrow
        break;
    }

    return false;
}

function update() {
    if (input.turningLeft) {
        car.turn -= 0.1;
    }
    if (input.turningRight) {
        car.turn += 0.1;
    }

    ctx.fillStyle = "#5C5C5C";
    ctx.fillRect(0, 0, cvs.width, cvs.height);
    car.drawControls();
    drawScore();
}

function load() {
    ctx = ctxInit('other-canvas');
    cvs = document.getElementById('other-canvas');

    window.addEventListener("keydown", keyDown, true);
    window.addEventListener("keyup", keyUp, true);

    var updateFn = setInterval(update, 32); // 30fps

    var otherCVS = document.getElementById('driving-canvas');
    getScore = run(ctxInit('driving-canvas'), otherCVS.width, otherCVS.height, car);
}

<commit_msg>Made the speedometer filled<commit_after>'use strict';

var ctx;
var cvs;

function Car () {
    this.turn = 0;
    this.speed = 100;

    return this;
}

Car.prototype.drawControls = function (){
    function drawSteeringWheel(turn) {
        var px = 300;
        var py = 400;
        var r = 50;

        ctx.save();
        ctx.lineWidth = 5;

        ctx.translate(px, py);
        ctx.rotate(turn);

        circle(ctx, 0, 0, r);
        line (ctx, 0, 0, 0, -r);

        ctx.restore();
    }

    function drawSpeedoMeter(speed) {
        var px = 300;
        var py = 200;
        var r = 150;

        var maxSpeedometerSpeed = 200;

        ctx.save();
            ctx.translate(px, py);
            ctx.fillStyle = "black";
            halfCircle (ctx, 0, 0, r);
            ctx.fill();

            function speedToArc(speed) {
                return (speed / maxSpeedometerSpeed) * 3.14  - 3.14;
            }

            ctx.strokeStyle = "white";

            // measurements
            for (var i = 0; i < maxSpeedometerSpeed; i += 10) {
                ctx.save();
                ctx.rotate(speedToArc(i));
                line (ctx, r, 0, r-10, 0);
                ctx.restore();
            }

            // dial
            ctx.save();
                ctx.lineWidth = 8;
                ctx.rotate(speedToArc(speed));
                line (ctx, 0, 0, r-15, 0);
            ctx.restore();

        ctx.restore();
    }

    drawSpeedoMeter(this.speed);
    drawSteeringWheel(this.turn);
}

// Actual game state
// Global because fuck it

var car = new Car();
var input = {
    turningLeft: false,
    turningRight: false
}
;
var getScore;
function drawScore() {
    var $scorebox = document.getElementById('scorebox');
    $scorebox.innerText = getScore() + 'm';
}

function keyDown(e) {
    switch (e.keyCode) {
        case 37: // left arrow
            input.turningLeft = true;
        break;
        case 38: // up arrow
        break;
        case 39: // right arrow
            input.turningRight = true;
        break;
        case 40: // down arrow
        break;
    }

    return false;
}

function keyUp(e) {
    switch (e.keyCode) {
        case 37: // left arrow
            input.turningLeft = false;
        break;
        case 38: // up arrow
        break;
        case 39: // right arrow
            input.turningRight = false;
        break;
        case 40: // down arrow
        break;
    }

    return false;
}

function update() {
    if (input.turningLeft) {
        car.turn -= 0.1;
    }
    if (input.turningRight) {
        car.turn += 0.1;
    }

    ctx.fillStyle = "#5C5C5C";
    ctx.fillRect(0, 0, cvs.width, cvs.height);
    car.drawControls();
    drawScore();
}

function load() {
    ctx = ctxInit('other-canvas');
    cvs = document.getElementById('other-canvas');

    window.addEventListener("keydown", keyDown, true);
    window.addEventListener("keyup", keyUp, true);

    var updateFn = setInterval(update, 32); // 30fps

    var otherCVS = document.getElementById('driving-canvas');
    getScore = run(ctxInit('driving-canvas'), otherCVS.width, otherCVS.height, car);
}

<|endoftext|>


<commit_before>/*
 *  index.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-03-27
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

exports.Transport = require('./ExpressTransport').ExpressTransport;
<commit_msg>export the transporter<commit_after>/*
 *  index.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-03-27
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

exports.Transport = require('./ExpressTransport').ExpressTransport;

const transporter = require('./transporter');
exports.make = transporter.make;

<|endoftext|>