<commit_before>'use strict';

var debug = require('debug');
var View = require('vinyl-view');
var utils = require('../utils');

/**
 * Plugin for adding data and context related methods to
 * an instance of `app` or `collection`.
 */

module.exports = function(app) {
  var opts = utils.extend({namespace: true}, app.options);

  /**
   * Custom debug methods
   */

  debug.context = debug('base:templates:context');
  debug.helper = function(key) {
    return debug('base:templates:helper:', key);
  };

  /**
   * Set, get and load data to be passed to templates as
   * context at render-time.
   *
   * ```js
   * app.data('a', 'b');
   * app.data({c: 'd'});
   * console.log(app.cache.data);
   * //=> {a: 'b', c: 'd'}
   * ```
   *
   * @name .data
   * @param {String|Object} `key` Pass a key-value pair or an object to set.
   * @param {any} `val` Any value when a key-value pair is passed. This can also be options if a glob pattern is passed as the first value.
   * @return {Object} Returns an instance of `Templates` for chaining.
   * @api public
   */

  app.use(utils.baseData(opts));

  /**
   * Register a default data loader
   */

  app.dataLoader('json', function(str) {
    return JSON.parse(str);
  });

  /**
   * Build the context for the given `view` and `locals`.
   *
   * @name .context
   * @param  {Object} `view` The view being rendered
   * @param  {Object} `locals`
   * @return {Object} The object to be passed to engines/views as context.
   * @api public
   */

  app.define('context', function(view, locals) {
    // backwards support for `mergeContext`
    var fn = this.options.context || this.options.mergeContext;
    if (typeof fn === 'function') {
      return fn.apply(this, arguments);
    }
    if (typeof view.context !== 'function') {
      view.context = View.context;
    }
    return utils.merge({}, this.cache.data, view.context(locals));
  });

  /**
   * Bind context to helpers.
   */

  app.define('bindHelpers', function(view, context, isAsync) {
    debug.context('binding helpers for %s <%s>', view.options.inflection, view.basename);
    var optsHelpers = utils.isObject(this.options.helpers) ? this.options.helpers : {};

    // merge cached _synchronous_ helpers with helpers defined on `app.options`
    var helpers = utils.extend({}, optsHelpers, this._.helpers.sync);

    // add helpers defined on the context
    if (utils.isObject(context.helpers)) {
      helpers = utils.extend({}, helpers, context.helpers);
    }

    // if any _async_ helpers are defined, merge those onto the context LAST
    if (isAsync) {
      helpers = utils.extend({}, helpers, this._.helpers.async);
    }

    // create the "helper context" to be exposed as `this` inside helper functions
    var thisArg = new Context(this, view, context, helpers);

    // bind the context to helpers.
    context.helpers = utils.bindAll(helpers, thisArg, {
      bindFn: function(thisArg, key, parent, options) {
        thisArg.debug = debug.helper(key);
        thisArg.helper.name = key;
        setHelperOptions(thisArg, key);
        thisArg._parent = parent;
        thisArg = utils.extend({}, thisArg, parent);
        return thisArg;
      }
    });
  });

  /**
   * Update context in a helper so that `this.helper.options` is
   * the options for that specific helper.
   *
   * @param {Object} `context`
   * @param {String} `key`
   * @api public
   */

  function setHelperOptions(context, key) {
    var optsHelper = context.options.helper || {};
    if (optsHelper.hasOwnProperty(key)) {
      context.helper.options = optsHelper[key];
    }
  }

  /**
   * Create a new context object to expose inside helpers.
   *
   * ```js
   * app.helper('lowercase', function(str) {
   *   // the 'this' object is the _helper_ context
   *   console.log(this);
   *   // 'this.app' => the application instance, e.g. templates, assemble, verb etc.
   *   // 'this.view' => the current view being rendered
   *   // 'this.helper' => helper name and options
   *   // 'this.context' => view context (as opposed to _helper_ context)
   *   // 'this.options' => options created for the specified helper being called
   * });
   * ```
   * @param {Object} `app` The application instance
   * @param {Object} `view` The view being rendered
   * @param {Object} `context` The view's context
   * @param {Object} `options`
   */

  function Context(app, view, context, helpers) {
    this.helper = {};
    this.helper.options = createHelperOptions(app, view, helpers);

    this.context = context;
    utils.define(this.context, 'view', view);
    this.options = utils.merge({}, app.options, view.options, this.helper.options);

    // make `this.options.handled` non-enumberable
    utils.define(this.options, 'handled', this.options.handled);

    decorate(this.context);
    decorate(this.options);
    decorate(this);

    this.view = view;
    this.app = app;
    this.ctx = function() {
      return helperContext.apply(this, arguments);
    };
  }

  /**
   * Expose `this.ctx()` in helpers for creating a custom context object
   * from a `view` being rendered, `locals` and helper `options` (which
   * can optionally have a handlebars `options.hash` property)
   *
   * The context object is created from:
   *
   * - `this.context`: the context for the **current view being rendered**
   * - `this.view.locals`: the `locals` object for current view being rendered
   * - `this.view.data`: front-matter from current view being rendered
   * - `view.locals`: locals defined on the view being injected
   * - `view.data`: front-matter on the view being injected
   * - helper `locals`: locals passed to the helper
   * - helper `options`: options passed to the helper
   * - helper `options.hash` if helper is registered as a handlebars helper
   *
   * Also note that `view` is the view being injected, whereas `this.view`
   * is the `view` being rendered.
   *
   * ```js
   * // handlebars helper
   * app.helper('foo', function(name, locals, options) {
   *   var view = this.app.find(name);
   *   var ctx = this.ctx(view, locals, options);
   *   return options.fn(ctx);
   * });
   *
   * // async helper
   * app.helper('foo', function(name, locals, options, cb) {
   *   var view = this.app.find(name);
   *   var ctx = this.ctx(view, locals, options);
   *   view.render(ctx, function(err, res) {
   *     if (err) return cb(err);
   *     cb(null, res.content);
   *   });
   * });
   * ```
   * @name .this.ctx
   * @param {Object} `view` the view being injected
   * @param {Object} `locals` Helper locals
   * @param {Object} `options` Helper options
   * @return {Object}
   * @public
   */

  function helperContext(view, locals, options) {
    var fn = this.options.helperContext;
    var context = {};

    if (!utils.isObject(view) || !view.isView) {
      locals = view;
      options = locals;
      view = this.view;
    }

    options = options || {};
    locals = locals || {};
    if (typeof fn === 'function') {
      return fn.call(this, view, locals, options);
    }

    // merge "view" front-matter with context
    context = utils.merge({}, context, this.view.locals, this.view.data);

    // merge in partial locals and front-matter
    if (view !== this.view) {
      context = utils.merge({}, context, view.locals, view.data);
    }

    // merge in helper locals and options.hash
    context = utils.merge({}, context, locals, options.hash);
    return context;
  }

  /**
   * Decorate the given object with `merge`, `set` and `get` methods
   */

  function decorate(obj) {
    utils.define(obj, 'merge', function() {
      var args = [].concat.apply([], [].slice.call(arguments));
      var len = args.length;
      var idx = -1;

      while (++idx < len) {
        var val = args[idx];
        if (!utils.isObject(val)) continue;
        if (val.hasOwnProperty('hash')) {
          // shallow clone and delete the `data` object
          val = utils.merge({}, val, val.hash);
          delete val.data;
        }
        utils.merge(obj, val);
      }
      // ensure methods aren't overwritten
      decorate(obj);
      if (obj.hasOwnProperty('app') && obj.hasOwnProperty('options')) {
        decorate(obj.options);
      }
      return obj;
    });

    utils.define(obj, 'get', function(prop) {
      return utils.get(obj, prop);
    });

    utils.define(obj, 'set', function(key, val) {
      return utils.set(obj, key, val);
    });
  }

  /**
   * Support helper options defined on `app.options.helper`. For example,
   * to define options for helper `foo`:
   *
   * ```js
   * app.option('helper.foo', {doStuff: true});
   * ```
   * @param {Object} `app`
   * @param {Object} `helpers` Currently defined helpers, to match up options
   * @return {Object} Returns helper options
   */

  function createHelperOptions(app, view, helpers) {
    var options = utils.merge({}, app.options, view.options);
    var helperOptions = {};

    if (options.hasOwnProperty('helper')) {
      var opts = options.helper;
      if (!utils.isObject(opts)) {
        return helperOptions;
      }

      for (var key in opts) {
        if (opts.hasOwnProperty(key) && helpers.hasOwnProperty(key)) {
          helperOptions[key] = opts[key];
        }
      }
    }
    return helperOptions;
  }

  /**
   * Merge "partials" view types. This is necessary for template
   * engines have no support for partials or only support one
   * type of partials.
   *
   * @name .mergePartials
   * @param {Object} `options` Optionally pass an array of `viewTypes` to include on `options.viewTypes`
   * @return {Object} Merged partials
   * @api public
   */

  function mergePartials(options) {
    var opts = utils.merge({}, this.options, options);
    var names = opts.mergeTypes || this.viewTypes.partial;
    var partials = {};
    var self = this;

    names.forEach(function(name) {
      var collection = self.views[name];
      for (var key in collection) {
        var view = collection[key];

        // handle `onMerge` middleware
        self.handleOnce('onMerge', view, function(err, res) {
          if (err) throw err;
          view = res;
        });

        if (view.options.nomerge) continue;
        if (opts.mergePartials !== false) {
          name = 'partials';
        }

        // convert the partial to:
        //=> {'foo.hbs': 'some content...'};
        partials[name] = partials[name] || {};
        partials[name][key] = view.content;
      }
    });
    return partials;
  };

  /**
   * Merge "partials" view types. This is necessary for template engines
   * have no support for partials or only support one type of partials.
   *
   * @name .mergePartialsAsync
   * @param {Object} `options` Optionally pass an array of `viewTypes` to include on `options.viewTypes`
   * @param {Function} `callback` Function that exposes `err` and `partials` parameters
   * @api public
   */

  mergePartials.async = function(options, done) {
    if (typeof options === 'function') {
      done = options;
      options = {};
    }

    var opts = utils.merge({}, this.options, options);
    var names = opts.mergeTypes || this.viewTypes.partial;
    var partials = {};
    var self = this;

    utils.each(names, function(name, cb) {
      var collection = self.views[name];
      var keys = Object.keys(collection);

      utils.each(keys, function(key, next) {
        var view = collection[key];

        // handle `onMerge` middleware
        self.handleOnce('onMerge', view, function(err, file) {
          if (err) return next(err);

          if (file.options.nomerge) {
            return next();
          }

          if (opts.mergePartials !== false) {
            name = 'partials';
          }

          // convert the partial to:
          //=> {'foo.hbs': 'some content...'};
          partials[name] = partials[name] || {};
          partials[name][key] = file.content;
          next();
        });
      }, cb);
    }, function(err) {
      if (err) return done(err);
      done(null, partials);
    });
  };

  /**
   * Expose `mergePartials` functions as methods
   */

  app.define('mergePartials', mergePartials);
  app.define('mergePartialsAsync', mergePartials.async);
};
<commit_msg>fix args passed to `view.context()`<commit_after>'use strict';

var debug = require('debug');
var View = require('vinyl-view');
var utils = require('../utils');

/**
 * Plugin for adding data and context related methods to
 * an instance of `app` or `collection`.
 */

module.exports = function(app) {
  var opts = utils.extend({namespace: true}, app.options);

  /**
   * Custom debug methods
   */

  debug.context = debug('base:templates:context');
  debug.helper = function(key) {
    return debug('base:templates:helper:', key);
  };

  /**
   * Set, get and load data to be passed to templates as
   * context at render-time.
   *
   * ```js
   * app.data('a', 'b');
   * app.data({c: 'd'});
   * console.log(app.cache.data);
   * //=> {a: 'b', c: 'd'}
   * ```
   *
   * @name .data
   * @param {String|Object} `key` Pass a key-value pair or an object to set.
   * @param {any} `val` Any value when a key-value pair is passed. This can also be options if a glob pattern is passed as the first value.
   * @return {Object} Returns an instance of `Templates` for chaining.
   * @api public
   */

  app.use(utils.baseData(opts));

  /**
   * Register a default data loader
   */

  app.dataLoader('json', function(str) {
    return JSON.parse(str);
  });

  /**
   * Build the context for the given `view` and `locals`.
   *
   * @name .context
   * @param  {Object} `view` The view being rendered
   * @param  {Object} `locals`
   * @return {Object} The object to be passed to engines/views as context.
   * @api public
   */

  app.define('context', function(view, locals) {
    // backwards support for `mergeContext`
    var fn = this.options.context || this.options.mergeContext;
    if (typeof fn === 'function') {
      return fn.apply(this, arguments);
    }
    if (typeof view.context !== 'function') {
      view.context = View.context;
    }
    return view.context(this.cache.data, locals);
  });

  /**
   * Bind context to helpers.
   */

  app.define('bindHelpers', function(view, context, isAsync) {
    debug.context('binding helpers for %s <%s>', view.options.inflection, view.basename);
    var optsHelpers = utils.isObject(this.options.helpers) ? this.options.helpers : {};

    // merge cached _synchronous_ helpers with helpers defined on `app.options`
    var helpers = utils.extend({}, optsHelpers, this._.helpers.sync);

    // add helpers defined on the context
    if (utils.isObject(context.helpers)) {
      helpers = utils.extend({}, helpers, context.helpers);
    }

    // if any _async_ helpers are defined, merge those onto the context LAST
    if (isAsync) {
      helpers = utils.extend({}, helpers, this._.helpers.async);
    }

    // create the "helper context" to be exposed as `this` inside helper functions
    var thisArg = new Context(this, view, context, helpers);

    // bind the context to helpers.
    context.helpers = utils.bindAll(helpers, thisArg, {
      bindFn: function(thisArg, key, parent, options) {
        thisArg.debug = debug.helper(key);
        thisArg.helper.name = key;
        setHelperOptions(thisArg, key);
        thisArg._parent = parent;
        thisArg = utils.extend({}, thisArg, parent);
        return thisArg;
      }
    });
  });

  /**
   * Update context in a helper so that `this.helper.options` is
   * the options for that specific helper.
   *
   * @param {Object} `context`
   * @param {String} `key`
   * @api public
   */

  function setHelperOptions(context, key) {
    var optsHelper = context.options.helper || {};
    if (optsHelper.hasOwnProperty(key)) {
      context.helper.options = optsHelper[key];
    }
  }

  /**
   * Create a new context object to expose inside helpers.
   *
   * ```js
   * app.helper('lowercase', function(str) {
   *   // the 'this' object is the _helper_ context
   *   console.log(this);
   *   // 'this.app' => the application instance, e.g. templates, assemble, verb etc.
   *   // 'this.view' => the current view being rendered
   *   // 'this.helper' => helper name and options
   *   // 'this.context' => view context (as opposed to _helper_ context)
   *   // 'this.options' => options created for the specified helper being called
   * });
   * ```
   * @param {Object} `app` The application instance
   * @param {Object} `view` The view being rendered
   * @param {Object} `context` The view's context
   * @param {Object} `options`
   */

  function Context(app, view, context, helpers) {
    this.helper = {};
    this.helper.options = createHelperOptions(app, view, helpers);

    this.context = context;
    utils.define(this.context, 'view', view);
    this.options = utils.merge({}, app.options, view.options, this.helper.options);

    // make `this.options.handled` non-enumberable
    utils.define(this.options, 'handled', this.options.handled);

    decorate(this.context);
    decorate(this.options);
    decorate(this);

    this.view = view;
    this.app = app;
    this.ctx = function() {
      return helperContext.apply(this, arguments);
    };
  }

  /**
   * Expose `this.ctx()` in helpers for creating a custom context object
   * from a `view` being rendered, `locals` and helper `options` (which
   * can optionally have a handlebars `options.hash` property)
   *
   * The context object is created from:
   *
   * - `this.context`: the context for the **current view being rendered**
   * - `this.view.locals`: the `locals` object for current view being rendered
   * - `this.view.data`: front-matter from current view being rendered
   * - `view.locals`: locals defined on the view being injected
   * - `view.data`: front-matter on the view being injected
   * - helper `locals`: locals passed to the helper
   * - helper `options`: options passed to the helper
   * - helper `options.hash` if helper is registered as a handlebars helper
   *
   * Also note that `view` is the view being injected, whereas `this.view`
   * is the `view` being rendered.
   *
   * ```js
   * // handlebars helper
   * app.helper('foo', function(name, locals, options) {
   *   var view = this.app.find(name);
   *   var ctx = this.ctx(view, locals, options);
   *   return options.fn(ctx);
   * });
   *
   * // async helper
   * app.helper('foo', function(name, locals, options, cb) {
   *   var view = this.app.find(name);
   *   var ctx = this.ctx(view, locals, options);
   *   view.render(ctx, function(err, res) {
   *     if (err) return cb(err);
   *     cb(null, res.content);
   *   });
   * });
   * ```
   * @name .this.ctx
   * @param {Object} `view` the view being injected
   * @param {Object} `locals` Helper locals
   * @param {Object} `options` Helper options
   * @return {Object}
   * @public
   */

  function helperContext(view, locals, options) {
    var fn = this.options.helperContext;
    var context = {};

    if (!utils.isObject(view) || !view.isView) {
      locals = view;
      options = locals;
      view = this.view;
    }

    options = options || {};
    locals = locals || {};
    if (typeof fn === 'function') {
      return fn.call(this, view, locals, options);
    }

    // merge "view" front-matter with context
    context = utils.merge({}, context, this.view.locals, this.view.data);

    // merge in partial locals and front-matter
    if (view !== this.view) {
      context = utils.merge({}, context, view.locals, view.data);
    }

    // merge in helper locals and options.hash
    context = utils.merge({}, context, locals, options.hash);
    return context;
  }

  /**
   * Decorate the given object with `merge`, `set` and `get` methods
   */

  function decorate(obj) {
    utils.define(obj, 'merge', function() {
      var args = [].concat.apply([], [].slice.call(arguments));
      var len = args.length;
      var idx = -1;

      while (++idx < len) {
        var val = args[idx];
        if (!utils.isObject(val)) continue;
        if (val.hasOwnProperty('hash')) {
          // shallow clone and delete the `data` object
          val = utils.merge({}, val, val.hash);
          delete val.data;
        }
        utils.merge(obj, val);
      }
      // ensure methods aren't overwritten
      decorate(obj);
      if (obj.hasOwnProperty('app') && obj.hasOwnProperty('options')) {
        decorate(obj.options);
      }
      return obj;
    });

    utils.define(obj, 'get', function(prop) {
      return utils.get(obj, prop);
    });

    utils.define(obj, 'set', function(key, val) {
      return utils.set(obj, key, val);
    });
  }

  /**
   * Support helper options defined on `app.options.helper`. For example,
   * to define options for helper `foo`:
   *
   * ```js
   * app.option('helper.foo', {doStuff: true});
   * ```
   * @param {Object} `app`
   * @param {Object} `helpers` Currently defined helpers, to match up options
   * @return {Object} Returns helper options
   */

  function createHelperOptions(app, view, helpers) {
    var options = utils.merge({}, app.options, view.options);
    var helperOptions = {};

    if (options.hasOwnProperty('helper')) {
      var opts = options.helper;
      if (!utils.isObject(opts)) {
        return helperOptions;
      }

      for (var key in opts) {
        if (opts.hasOwnProperty(key) && helpers.hasOwnProperty(key)) {
          helperOptions[key] = opts[key];
        }
      }
    }
    return helperOptions;
  }

  /**
   * Merge "partials" view types. This is necessary for template
   * engines have no support for partials or only support one
   * type of partials.
   *
   * @name .mergePartials
   * @param {Object} `options` Optionally pass an array of `viewTypes` to include on `options.viewTypes`
   * @return {Object} Merged partials
   * @api public
   */

  function mergePartials(options) {
    var opts = utils.merge({}, this.options, options);
    var names = opts.mergeTypes || this.viewTypes.partial;
    var partials = {};
    var self = this;

    names.forEach(function(name) {
      var collection = self.views[name];
      for (var key in collection) {
        var view = collection[key];

        // handle `onMerge` middleware
        self.handleOnce('onMerge', view, function(err, res) {
          if (err) throw err;
          view = res;
        });

        if (view.options.nomerge) continue;
        if (opts.mergePartials !== false) {
          name = 'partials';
        }

        // convert the partial to:
        //=> {'foo.hbs': 'some content...'};
        partials[name] = partials[name] || {};
        partials[name][key] = view.content;
      }
    });
    return partials;
  };

  /**
   * Merge "partials" view types. This is necessary for template engines
   * have no support for partials or only support one type of partials.
   *
   * @name .mergePartialsAsync
   * @param {Object} `options` Optionally pass an array of `viewTypes` to include on `options.viewTypes`
   * @param {Function} `callback` Function that exposes `err` and `partials` parameters
   * @api public
   */

  mergePartials.async = function(options, done) {
    if (typeof options === 'function') {
      done = options;
      options = {};
    }

    var opts = utils.merge({}, this.options, options);
    var names = opts.mergeTypes || this.viewTypes.partial;
    var partials = {};
    var self = this;

    utils.each(names, function(name, cb) {
      var collection = self.views[name];
      var keys = Object.keys(collection);

      utils.each(keys, function(key, next) {
        var view = collection[key];

        // handle `onMerge` middleware
        self.handleOnce('onMerge', view, function(err, file) {
          if (err) return next(err);

          if (file.options.nomerge) {
            return next();
          }

          if (opts.mergePartials !== false) {
            name = 'partials';
          }

          // convert the partial to:
          //=> {'foo.hbs': 'some content...'};
          partials[name] = partials[name] || {};
          partials[name][key] = file.content;
          next();
        });
      }, cb);
    }, function(err) {
      if (err) return done(err);
      done(null, partials);
    });
  };

  /**
   * Expose `mergePartials` functions as methods
   */

  app.define('mergePartials', mergePartials);
  app.define('mergePartialsAsync', mergePartials.async);
};
<|endoftext|>


<commit_before>module.exports.safe = true;
module.exports.manual_interpolation = true;

/*
 Purpose:
 Executes the specified actions for each value in the specified array(s), assigning the array value to the specified key(s).
 If parallel=true, then the actions will be processed concurrently with each iteration receiving a separate snapshot of the session.

 Example:
- foreach:
    key: [ item, ad ]
    in: [ items, ads ]
    parallel: true
    actions:
      - goto: token_exchange

 */

var _ = require("lodash");
var Promise = require("bluebird");

module.exports.normalize = function (action, normalize_action) {
	if (_.has(action, "foreach")) {
		// Normalize nested actions
		action.foreach.key = _.castArray(action.foreach.key);
		action.foreach.in = _.castArray(action.foreach.in);
		action.foreach.actions = normalize_action(action.foreach.actions);
		return action;
	}
}

module.exports.process = function (context) {
	var func = (this.foreach.parallel ? Promise.map : Promise.mapSeries);
	this.foreach.in = this.foreach.in.map(a => _.isArray(a) ? a : context.session[a]);
	return func(_.zip.apply(null, this.foreach.in), (parts) => {
		var new_context = context.clone();
		var new_part = _.zipObject(this.foreach.key, parts);
		exports.logger.debug("Forking with " + JSON.stringify(new_part));
		_.assign(new_context.session, new_part);
		return new_context.process_action(this.foreach.actions).catch(err => {
			if(_.has(err, "goto")) return err.context.handle_directive(err);
			throw err;
		});;
	});
}
<commit_msg>Fix for shared-state with foreach.in being overwritten<commit_after>module.exports.safe = true;
module.exports.manual_interpolation = true;

/*
 Purpose:
 Executes the specified actions for each value in the specified array(s), assigning the array value to the specified key(s).
 If parallel=true, then the actions will be processed concurrently with each iteration receiving a separate snapshot of the session.

 Example:
- foreach:
    key: [ item, ad ]
    in: [ items, ads ]
    parallel: true
    actions:
      - goto: token_exchange

 */

var _ = require("lodash");
var Promise = require("bluebird");

module.exports.normalize = function (action, normalize_action) {
	if (_.has(action, "foreach")) {
		// Normalize nested actions
		action.foreach.key = _.castArray(action.foreach.key);
		action.foreach.in = _.castArray(action.foreach.in);
		action.foreach.actions = normalize_action(action.foreach.actions);
		return action;
	}
}

module.exports.process = function (context) {
	var mapper = (this.foreach.parallel ? Promise.map : Promise.mapSeries);
	var mapped_in = this.foreach.in.map(a => _.isArray(a) ? a : context.session[a]);
	return mapper(_.zip.apply(null, mapped_in), (parts) => {
		var new_context = context.clone();
		var new_part = _.zipObject(this.foreach.key, parts);
		exports.logger.debug("Forking with " + JSON.stringify(new_part));
		_.assign(new_context.session, new_part);
		return new_context.process_action(this.foreach.actions).catch(err => {
			if(_.has(err, "goto")) return err.context.handle_directive(err);
			throw err;
		});;
	});
}
<|endoftext|>


<commit_before>/**
 * Process Manager
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This file abstract out multiprocess logic involved in several tasks.
 *
 * Child processes can be queried.
 *
 * @license MIT
 */

'use strict';

const path = require('path');
const child_process = require('child_process');
const Streams = require('./streams');
const ROOT_DIR = path.resolve(__dirname, '..');

class SubprocessStream extends Streams.ObjectReadWriteStream {
	/**
	 * @param {ChildProcess} process
	 * @param {number} taskId
	 */
	constructor(process, taskId) {
		super();
		this.process = process;
		this.taskId = taskId;
		this.process.send(`${taskId}\nNEW`);
	}
	_write(/** @type {string} */ message) {
		if (!process.connected) return;
		this.process.send(`${this.taskId}\nWRITE\n${message}`);
		// responses are handled in ProcessWrapper
	}
	_destroy() {
		if (!process.connected) return;
		this.process.send(`${this.taskId}\nDESTROY`);
	}
}

/**
 * @typedef {Object} ProcessWrapper
 * @property {number} load
 * @property {() => Promise<void>} release
 */

/**
 * Wraps the process object in the PARENT process
 */
class QueryProcessWrapper {
	constructor(/** @type {string} */ file) {
		this.process = child_process.fork(file, [], {cwd: ROOT_DIR});
		this.taskId = 0;
		/** @type {Map<number, (resp: string) => void>} */
		this.pendingTasks = new Map();
		/** @type {Promise<void>?} */
		this.pendingRelease = null;
		/** @type {(() => void)?} */
		this.resolveRelease = null;

		this.process.on('message', /** @param {string} message */ message => {
			const nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = parseInt(message.slice(0, nlLoc));
			const resolve = this.pendingTasks.get(taskId);
			if (!resolve) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`);
			this.pendingTasks.delete(taskId);
			resolve(JSON.parse(message.slice(nlLoc + 1)));

			if (this.resolveRelease && !this.load) this.destroy();
		});
		this.process.on('disconnect', () => {
			this.destroy();
		});
	}

	get load() {
		return this.pendingTasks.size;
	}

	/**
	 * @param {any} input
	 * @return {Promise<any>}
	 */
	query(input) {
		this.taskId++;
		const taskId = this.taskId;
		this.process.send(`${taskId}\n${JSON.stringify(input)}`);
		return new Promise(resolve => {
			this.pendingTasks.set(taskId, resolve);
		});
	}

	/**
	 * @return {Promise<void>}
	 */
	release() {
		if (this.pendingRelease) return this.pendingRelease;
		if (!this.load) {
			this.destroy();
		} else {
			this.pendingRelease = new Promise(resolve => {
				this.resolveRelease = resolve;
			});
		}
		return /** @type {Promise<void>} */ (this.pendingRelease);
	}

	destroy() {
		if (this.pendingRelease && !this.resolveRelease) {
			// already destroyed
			return;
		}
		this.process.disconnect();
		for (const resolver of this.pendingTasks.values()) {
			// maybe we should track reject functions too...
			resolver('');
		}
		this.pendingTasks.clear();
		if (this.resolveRelease) {
			this.resolveRelease();
			this.resolveRelease = null;
		} else if (!this.pendingRelease) {
			this.pendingRelease = Promise.resolve();
		}
	}
}

/**
 * Wraps the process object in the PARENT process
 */
class StreamProcessWrapper {
	constructor(/** @type {string} */ file) {
		this.process = child_process.fork(file, [], {cwd: ROOT_DIR});
		this.taskId = 0;
		/** @type {Map<number, SubprocessStream>} */
		this.activeStreams = new Map();
		/** @type {Promise<void>?} */
		this.pendingRelease = null;
		/** @type {(() => void)?} */
		this.resolveRelease = null;

		this.process.on('message', /** @param {string} message */ message => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = parseInt(message.slice(0, nlLoc));
			const stream = this.activeStreams.get(taskId);
			if (!stream) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`);

			message = message.slice(nlLoc + 1);
			nlLoc = message.indexOf('\n');
			if (nlLoc < 0) nlLoc = message.length;
			const messageType = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (messageType === 'END') {
				stream.end();
				this.deleteStream(taskId);
			} else if (messageType === 'PUSH') {
				stream.push(message);
			} else {
				throw new Error(`Unrecognized messageType ${messageType}`);
			}
		});
	}

	deleteStream(/** @type {number} */ taskId) {
		this.activeStreams.delete(taskId);
		// try to release
		if (this.resolveRelease && !this.load) this.destroy();
	}

	get load() {
		return this.activeStreams.size;
	}

	/**
	 * @return {SubprocessStream}
	 */
	createStream() {
		this.taskId++;
		const taskId = this.taskId;
		const stream = new SubprocessStream(this.process, taskId);
		this.activeStreams.set(taskId, stream);
		return stream;
	}

	/**
	 * @return {Promise<void>}
	 */
	release() {
		if (this.pendingRelease) return this.pendingRelease;
		if (!this.load) {
			this.destroy();
		} else {
			this.pendingRelease = new Promise(resolve => {
				this.resolveRelease = resolve;
			});
		}
		return /** @type {Promise<void>} */ (this.pendingRelease);
	}

	destroy() {
		if (this.pendingRelease && !this.resolveRelease) {
			// already destroyed
			return;
		}
		this.process.disconnect();
		for (const stream of this.activeStreams.values()) {
			stream.destroy();
		}
		this.activeStreams.clear();
		if (this.resolveRelease) {
			this.resolveRelease();
			this.resolveRelease = null;
		} else if (!this.pendingRelease) {
			this.pendingRelease = Promise.resolve();
		}
	}
}

/**
 * A ProcessManager wraps a query function: A function that takes a
 * string and returns a string or Promise<string>.
 */
class ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 */
	constructor(module) {
		/** @type {ProcessWrapper[]} */
		this.processes = [];
		/** @type {ProcessWrapper[]} */
		this.releasingProcesses = [];
		this.module = module;
		this.filename = module.filename;
		this.basename = path.basename(module.filename);

		this.isParentProcess = (process.mainModule !== module || !process.send);

		this.listen();
	}
	acquire() {
		if (!this.processes.length) {
			return null;
		}
		let lowestLoad = this.processes[0];
		for (const process of this.processes) {
			if (process.load < lowestLoad.load) {
				lowestLoad = process;
			}
		}
		return lowestLoad;
	}
	unspawn() {
		for (const process of this.processes) {
			process.release().then(() => {
				const index = this.releasingProcesses.indexOf(process);
				if (index >= 0) {
					this.releasingProcesses.splice(index, 1);
				}
			});
		}
		this.releasingProcesses = this.releasingProcesses.concat(this.processes);
		this.processes = [];
	}
	spawn(count = 1) {
		if (!this.isParentProcess) return;
		if (PMLib.disabled) return;
		while (this.processes.length < count) {
			this.processes.push(this.createProcess());
		}
	}
	respawn(/** @type {number?} */ count = null) {
		if (count === null) count = this.processes.length;
		this.unspawn();
		this.spawn(count);
	}
	/**
	 * @return {ProcessWrapper}
	 */
	createProcess() {
		throw new Error(`implemented by subclass`);
	}
	listen() {
		throw new Error(`implemented by subclass`);
	}
	destroy() {
		const index = PMLib.processManagers.indexOf(this);
		if (index) PMLib.processManagers.splice(index, 1);
		this.unspawn();
	}
}

class QueryProcessManager extends ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 * @param {(input: any) => any} query
	 */
	constructor(module, query) {
		super(module);
		this._query = query;

		PMLib.processManagers.push(this);
	}
	/**
	 * @param {any} input
	 */
	query(input) {
		const process = /** @type {QueryProcessWrapper} */ (this.acquire());
		if (!process) return Promise.resolve(this._query(input));
		return process.query(input);
	}
	createProcess() {
		return new QueryProcessWrapper(this.filename);
	}
	listen() {
		if (this.isParentProcess) return;
		// child process
		process.on('message', async (/** @type {string} */ message) => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (taskId.startsWith('EVAL')) {
				// @ts-ignore guaranteed to be defined here
				process.send(`${taskId}\n` + eval(message));
				return;
			}

			const response = await this._query(JSON.parse(message));
			// @ts-ignore guaranteed to be defined here
			process.send(`${taskId}\n${JSON.stringify(response)}`);
		});
		process.on('disconnect', () => {
			process.exit();
		});
	}
}
class StreamProcessManager extends ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 * @param {() => ObjectReadWriteStream} createStream
	 */
	constructor(module, createStream) {
		super(module);
		/** @type {Map<string, ObjectReadWriteStream>} taskid: stream used only in child process */
		this.activeStreams = new Map();
		this._createStream = createStream;

		PMLib.processManagers.push(this);
	}
	createStream() {
		const process = /** @type {StreamProcessWrapper} */ (this.acquire());
		if (!process) return this._createStream();
		return process.createStream();
	}
	createProcess() {
		return new StreamProcessWrapper(this.filename);
	}
	/**
	 * @param {string} taskId
	 * @param {ObjectReadStream} stream
	 */
	async pipeStream(taskId, stream) {
		let value, done;
		while (({value, done} = await stream.next(), !done)) {
			// @ts-ignore Guaranteed to be a child process
			process.send(`${taskId}\nPUSH\n${value}`);
		}
		// @ts-ignore Guaranteed to be a child process
		process.send(`${taskId}\nEND`);
		this.activeStreams.delete(taskId);
	}
	listen() {
		if (this.isParentProcess) return;
		// child process
		process.on('message', async (/** @type {string} */ message) => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid request ${message}`);
			const taskId = message.slice(0, nlLoc);
			const stream = this.activeStreams.get(taskId);

			message = message.slice(nlLoc + 1);
			nlLoc = message.indexOf('\n');
			if (nlLoc < 0) nlLoc = message.length;
			const messageType = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (taskId.startsWith('EVAL')) {
				// @ts-ignore guaranteed to be a child process
				process.send(`${taskId}\n` + eval(message));
				return;
			}

			if (messageType === 'NEW') {
				if (stream) throw new Error(`NEW: taskId ${taskId} already exists`);
				const newStream = this._createStream();
				this.activeStreams.set(taskId, newStream);
				this.pipeStream(taskId, newStream);
			} else if (messageType === 'DESTROY') {
				if (!stream) throw new Error(`DESTROY: Invalid taskId ${taskId}`);
				stream.destroy();
				this.activeStreams.delete(taskId);
			} else if (messageType === 'WRITE') {
				if (!stream) throw new Error(`WRITE: Invalid taskId ${taskId}`);
				stream.write(message);
			} else {
				throw new Error(`Unrecognized messageType ${messageType}`);
			}
		});
		process.on('disconnect', () => {
			process.exit();
		});
	}
}

const PMLib = {
	QueryProcessWrapper,
	StreamProcessWrapper,
	ProcessManager,
	QueryProcessManager,
	StreamProcessManager,

	/** @type {ProcessManager[]} */
	processManagers: [],
	disabled: false,
};
// @ts-ignore Typescript bug
module.exports = PMLib;
<commit_msg>Fix bug in previous commit<commit_after>/**
 * Process Manager
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This file abstract out multiprocess logic involved in several tasks.
 *
 * Child processes can be queried.
 *
 * @license MIT
 */

'use strict';

const path = require('path');
const child_process = require('child_process');
const Streams = require('./streams');
const ROOT_DIR = path.resolve(__dirname, '..');

class SubprocessStream extends Streams.ObjectReadWriteStream {
	/**
	 * @param {ChildProcess} process
	 * @param {number} taskId
	 */
	constructor(process, taskId) {
		super();
		this.process = process;
		this.taskId = taskId;
		this.process.send(`${taskId}\nNEW`);
	}
	_write(/** @type {string} */ message) {
		if (!this.process.connected) return;
		this.process.send(`${this.taskId}\nWRITE\n${message}`);
		// responses are handled in ProcessWrapper
	}
	_destroy() {
		if (!this.process.connected) return;
		this.process.send(`${this.taskId}\nDESTROY`);
	}
}

/**
 * @typedef {Object} ProcessWrapper
 * @property {number} load
 * @property {() => Promise<void>} release
 */

/**
 * Wraps the process object in the PARENT process
 */
class QueryProcessWrapper {
	constructor(/** @type {string} */ file) {
		this.process = child_process.fork(file, [], {cwd: ROOT_DIR});
		this.taskId = 0;
		/** @type {Map<number, (resp: string) => void>} */
		this.pendingTasks = new Map();
		/** @type {Promise<void>?} */
		this.pendingRelease = null;
		/** @type {(() => void)?} */
		this.resolveRelease = null;

		this.process.on('message', /** @param {string} message */ message => {
			const nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = parseInt(message.slice(0, nlLoc));
			const resolve = this.pendingTasks.get(taskId);
			if (!resolve) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`);
			this.pendingTasks.delete(taskId);
			resolve(JSON.parse(message.slice(nlLoc + 1)));

			if (this.resolveRelease && !this.load) this.destroy();
		});
		this.process.on('disconnect', () => {
			this.destroy();
		});
	}

	get load() {
		return this.pendingTasks.size;
	}

	/**
	 * @param {any} input
	 * @return {Promise<any>}
	 */
	query(input) {
		this.taskId++;
		const taskId = this.taskId;
		this.process.send(`${taskId}\n${JSON.stringify(input)}`);
		return new Promise(resolve => {
			this.pendingTasks.set(taskId, resolve);
		});
	}

	/**
	 * @return {Promise<void>}
	 */
	release() {
		if (this.pendingRelease) return this.pendingRelease;
		if (!this.load) {
			this.destroy();
		} else {
			this.pendingRelease = new Promise(resolve => {
				this.resolveRelease = resolve;
			});
		}
		return /** @type {Promise<void>} */ (this.pendingRelease);
	}

	destroy() {
		if (this.pendingRelease && !this.resolveRelease) {
			// already destroyed
			return;
		}
		this.process.disconnect();
		for (const resolver of this.pendingTasks.values()) {
			// maybe we should track reject functions too...
			resolver('');
		}
		this.pendingTasks.clear();
		if (this.resolveRelease) {
			this.resolveRelease();
			this.resolveRelease = null;
		} else if (!this.pendingRelease) {
			this.pendingRelease = Promise.resolve();
		}
	}
}

/**
 * Wraps the process object in the PARENT process
 */
class StreamProcessWrapper {
	constructor(/** @type {string} */ file) {
		this.process = child_process.fork(file, [], {cwd: ROOT_DIR});
		this.taskId = 0;
		/** @type {Map<number, SubprocessStream>} */
		this.activeStreams = new Map();
		/** @type {Promise<void>?} */
		this.pendingRelease = null;
		/** @type {(() => void)?} */
		this.resolveRelease = null;

		this.process.on('message', /** @param {string} message */ message => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = parseInt(message.slice(0, nlLoc));
			const stream = this.activeStreams.get(taskId);
			if (!stream) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`);

			message = message.slice(nlLoc + 1);
			nlLoc = message.indexOf('\n');
			if (nlLoc < 0) nlLoc = message.length;
			const messageType = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (messageType === 'END') {
				stream.end();
				this.deleteStream(taskId);
			} else if (messageType === 'PUSH') {
				stream.push(message);
			} else {
				throw new Error(`Unrecognized messageType ${messageType}`);
			}
		});
	}

	deleteStream(/** @type {number} */ taskId) {
		this.activeStreams.delete(taskId);
		// try to release
		if (this.resolveRelease && !this.load) this.destroy();
	}

	get load() {
		return this.activeStreams.size;
	}

	/**
	 * @return {SubprocessStream}
	 */
	createStream() {
		this.taskId++;
		const taskId = this.taskId;
		const stream = new SubprocessStream(this.process, taskId);
		this.activeStreams.set(taskId, stream);
		return stream;
	}

	/**
	 * @return {Promise<void>}
	 */
	release() {
		if (this.pendingRelease) return this.pendingRelease;
		if (!this.load) {
			this.destroy();
		} else {
			this.pendingRelease = new Promise(resolve => {
				this.resolveRelease = resolve;
			});
		}
		return /** @type {Promise<void>} */ (this.pendingRelease);
	}

	destroy() {
		if (this.pendingRelease && !this.resolveRelease) {
			// already destroyed
			return;
		}
		this.process.disconnect();
		for (const stream of this.activeStreams.values()) {
			stream.destroy();
		}
		this.activeStreams.clear();
		if (this.resolveRelease) {
			this.resolveRelease();
			this.resolveRelease = null;
		} else if (!this.pendingRelease) {
			this.pendingRelease = Promise.resolve();
		}
	}
}

/**
 * A ProcessManager wraps a query function: A function that takes a
 * string and returns a string or Promise<string>.
 */
class ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 */
	constructor(module) {
		/** @type {ProcessWrapper[]} */
		this.processes = [];
		/** @type {ProcessWrapper[]} */
		this.releasingProcesses = [];
		this.module = module;
		this.filename = module.filename;
		this.basename = path.basename(module.filename);

		this.isParentProcess = (process.mainModule !== module || !process.send);

		this.listen();
	}
	acquire() {
		if (!this.processes.length) {
			return null;
		}
		let lowestLoad = this.processes[0];
		for (const process of this.processes) {
			if (process.load < lowestLoad.load) {
				lowestLoad = process;
			}
		}
		return lowestLoad;
	}
	unspawn() {
		for (const process of this.processes) {
			process.release().then(() => {
				const index = this.releasingProcesses.indexOf(process);
				if (index >= 0) {
					this.releasingProcesses.splice(index, 1);
				}
			});
		}
		this.releasingProcesses = this.releasingProcesses.concat(this.processes);
		this.processes = [];
	}
	spawn(count = 1) {
		if (!this.isParentProcess) return;
		if (PMLib.disabled) return;
		while (this.processes.length < count) {
			this.processes.push(this.createProcess());
		}
	}
	respawn(/** @type {number?} */ count = null) {
		if (count === null) count = this.processes.length;
		this.unspawn();
		this.spawn(count);
	}
	/**
	 * @return {ProcessWrapper}
	 */
	createProcess() {
		throw new Error(`implemented by subclass`);
	}
	listen() {
		throw new Error(`implemented by subclass`);
	}
	destroy() {
		const index = PMLib.processManagers.indexOf(this);
		if (index) PMLib.processManagers.splice(index, 1);
		this.unspawn();
	}
}

class QueryProcessManager extends ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 * @param {(input: any) => any} query
	 */
	constructor(module, query) {
		super(module);
		this._query = query;

		PMLib.processManagers.push(this);
	}
	/**
	 * @param {any} input
	 */
	query(input) {
		const process = /** @type {QueryProcessWrapper} */ (this.acquire());
		if (!process) return Promise.resolve(this._query(input));
		return process.query(input);
	}
	createProcess() {
		return new QueryProcessWrapper(this.filename);
	}
	listen() {
		if (this.isParentProcess) return;
		// child process
		process.on('message', async (/** @type {string} */ message) => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid response ${message}`);
			const taskId = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (taskId.startsWith('EVAL')) {
				// @ts-ignore guaranteed to be defined here
				process.send(`${taskId}\n` + eval(message));
				return;
			}

			const response = await this._query(JSON.parse(message));
			// @ts-ignore guaranteed to be defined here
			process.send(`${taskId}\n${JSON.stringify(response)}`);
		});
		process.on('disconnect', () => {
			process.exit();
		});
	}
}
class StreamProcessManager extends ProcessManager {
	/**
	 * @param {NodeJS.Module} module
	 * @param {() => ObjectReadWriteStream} createStream
	 */
	constructor(module, createStream) {
		super(module);
		/** @type {Map<string, ObjectReadWriteStream>} taskid: stream used only in child process */
		this.activeStreams = new Map();
		this._createStream = createStream;

		PMLib.processManagers.push(this);
	}
	createStream() {
		const process = /** @type {StreamProcessWrapper} */ (this.acquire());
		if (!process) return this._createStream();
		return process.createStream();
	}
	createProcess() {
		return new StreamProcessWrapper(this.filename);
	}
	/**
	 * @param {string} taskId
	 * @param {ObjectReadStream} stream
	 */
	async pipeStream(taskId, stream) {
		let value, done;
		while (({value, done} = await stream.next(), !done)) {
			// @ts-ignore Guaranteed to be a child process
			process.send(`${taskId}\nPUSH\n${value}`);
		}
		// @ts-ignore Guaranteed to be a child process
		process.send(`${taskId}\nEND`);
		this.activeStreams.delete(taskId);
	}
	listen() {
		if (this.isParentProcess) return;
		// child process
		process.on('message', async (/** @type {string} */ message) => {
			let nlLoc = message.indexOf('\n');
			if (nlLoc <= 0) throw new Error(`Invalid request ${message}`);
			const taskId = message.slice(0, nlLoc);
			const stream = this.activeStreams.get(taskId);

			message = message.slice(nlLoc + 1);
			nlLoc = message.indexOf('\n');
			if (nlLoc < 0) nlLoc = message.length;
			const messageType = message.slice(0, nlLoc);
			message = message.slice(nlLoc + 1);

			if (taskId.startsWith('EVAL')) {
				// @ts-ignore guaranteed to be a child process
				process.send(`${taskId}\n` + eval(message));
				return;
			}

			if (messageType === 'NEW') {
				if (stream) throw new Error(`NEW: taskId ${taskId} already exists`);
				const newStream = this._createStream();
				this.activeStreams.set(taskId, newStream);
				this.pipeStream(taskId, newStream);
			} else if (messageType === 'DESTROY') {
				if (!stream) throw new Error(`DESTROY: Invalid taskId ${taskId}`);
				stream.destroy();
				this.activeStreams.delete(taskId);
			} else if (messageType === 'WRITE') {
				if (!stream) throw new Error(`WRITE: Invalid taskId ${taskId}`);
				stream.write(message);
			} else {
				throw new Error(`Unrecognized messageType ${messageType}`);
			}
		});
		process.on('disconnect', () => {
			process.exit();
		});
	}
}

const PMLib = {
	QueryProcessWrapper,
	StreamProcessWrapper,
	ProcessManager,
	QueryProcessManager,
	StreamProcessManager,

	/** @type {ProcessManager[]} */
	processManagers: [],
	disabled: false,
};
// @ts-ignore Typescript bug
module.exports = PMLib;
<|endoftext|>


<commit_before>
var getTime = require('../medtronic-clock');

function bgTargetsLookup (inputs) {
  return bound_target_range(lookup(inputs));
}

function lookup (inputs) {
    var bgtargets_data = inputs.targets;
    var temptargets_data = inputs.temptargets;
    var now = new Date();

    //bgtargets_data.targets.sort(function (a, b) { return a.offset > b.offset });

    var bgTargets = bgtargets_data.targets[bgtargets_data.targets.length - 1];

    for (var i = 0; i < bgtargets_data.targets.length - 1; i++) {
        if ((now >= getTime(bgtargets_data.targets[i].offset)) && (now < getTime(bgtargets_data.targets[i + 1].offset))) {
            bgTargets = bgtargets_data.targets[i];
            break;
        }
    }

    //console.error(temptargets_data);
    //console.error(now);
    for (var i = 0; i < temptargets_data.length; i++) {
        start = new Date(temptargets_data[i].created_at);
        //console.error(start);
        expires = new Date(start.getTime() + temptargets_data[i].duration * 60 * 1000);
        //console.error(expires);
        if (now >= new Date(temptargets_data[i].created_at) && now < expires ) {
            //console.error(temptargets_data[i]);
            bgTargets.high = temptargets_data[i].targetTop;
            bgTargets.low = temptargets_data[i].targetBottom;
            //break;
        }
    }
    //console.error(bgTargets);

    return bgTargets;
}

function bound_target_range (target) {
    // hard-code lower bounds for min_bg and max_bg in case pump is set too low, or units are wrong
    target.max_bg = Math.max(80, target.high);
    target.min_bg = Math.max(80, target.low);
    // hard-code upper bound for min_bg in case pump is set too high
    target.min_bg = Math.min(200, target.min_bg);
    target.max_bg = Math.min(200, target.min_bg);
    return target
}

bgTargetsLookup.bgTargetsLookup = bgTargetsLookup;
bgTargetsLookup.lookup = lookup;
bgTargetsLookup.bound_target_range = bound_target_range;
exports = module.exports = bgTargetsLookup;

<commit_msg>use only most recently set temptarget<commit_after>
var getTime = require('../medtronic-clock');

function bgTargetsLookup (inputs) {
  return bound_target_range(lookup(inputs));
}

function lookup (inputs) {
    var bgtargets_data = inputs.targets;
    var temptargets_data = inputs.temptargets;
    var now = new Date();

    //bgtargets_data.targets.sort(function (a, b) { return a.offset > b.offset });

    var bgTargets = bgtargets_data.targets[bgtargets_data.targets.length - 1];

    for (var i = 0; i < bgtargets_data.targets.length - 1; i++) {
        if ((now >= getTime(bgtargets_data.targets[i].offset)) && (now < getTime(bgtargets_data.targets[i + 1].offset))) {
            bgTargets = bgtargets_data.targets[i];
            break;
        }
    }

    //console.error(temptargets_data);
    //console.error(now);
    for (var i = 0; i < temptargets_data.length; i++) {
        start = new Date(temptargets_data[i].created_at);
        //console.error(start);
        expires = new Date(start.getTime() + temptargets_data[i].duration * 60 * 1000);
        //console.error(expires);
        if (now >= new Date(temptargets_data[i].created_at) && now < expires ) {
            //console.error(temptargets_data[i]);
            bgTargets.high = temptargets_data[i].targetTop;
            bgTargets.low = temptargets_data[i].targetBottom;
            break;
        }
    }
    //console.error(bgTargets);

    return bgTargets;
}

function bound_target_range (target) {
    // hard-code lower bounds for min_bg and max_bg in case pump is set too low, or units are wrong
    target.max_bg = Math.max(80, target.high);
    target.min_bg = Math.max(80, target.low);
    // hard-code upper bound for min_bg in case pump is set too high
    target.min_bg = Math.min(200, target.min_bg);
    target.max_bg = Math.min(200, target.max_bg);
    return target
}

bgTargetsLookup.bgTargetsLookup = bgTargetsLookup;
bgTargetsLookup.lookup = lookup;
bgTargetsLookup.bound_target_range = bound_target_range;
exports = module.exports = bgTargetsLookup;

<|endoftext|>


<commit_before>var BufferResponse = require('express-buffer-response');
var onHeaders = require('on-headers');
var pathRegexp = require('path-to-regexp');
var URL = require('url');
//var CacheDebounce = require('cache-debounce');
var debug = require('debug')('raja:express');

module.exports = ExpressProxy;

function ExpressProxy(raja, opts) {
	if (!(this instanceof ExpressProxy)) return new ExpressProxy(raja, opts);
	this.raja = raja;
	this.opts = opts || {};
	this.middleware = this.middleware.bind(this);
}

//var GetMw = CacheDebounce(function(url, baseUrl, req, res, next, cb) {
var GetMw = function(url, baseUrl, req, res, next, cb) {
	var raja = this.raja;
	req.headers['x-raja'] = raja.opts.namespace;
	raja.retrieve(url, res, function(err, resource) {
		if (err) raja.error(err); // fall through
		res.set('X-Raja', raja.opts.namespace);
		if (resource && resource.valid) {
			debug('found valid', resource.key, resource.headers);
			for (var name in resource.headers) {
				if (name in {'Content-Type':1, 'ETag':1, 'Vary':1, 'Content-Encoding':1, 'X-Grant': 1, 'X-Raja':1}) {
					res.set(name, resource.headers[name]);
				}
			}
			var ETag = resource.headers.ETag;
			if (ETag && req.get('If-None-Match') == ETag) {
				res.sendStatus(304);
			} else {
				if (resource.headers['X-Grant']) {
					res.set('Cache-Control', 'max-age=0,must-revalidate,private,no-cache');
					res.set('Pragma', 'no-cache');
				}
				if (resource.mtime) {
					res.set('Last-Modified', resource.mtime.toUTCString());
				}
				res.send(resource.data);
			}
			cb();
		} else {
			if (!resource) {
				debug('no resource found', url);
				resource = raja.create({url: url, builder: 'express'}, res);
			} else {
				debug('found invalid resource', resource.key);
			}
			req.resource = resource;
			onHeaders(res, function() {
				var lastMod = res.get('Last-Modified');
				if (lastMod) {
					lastMod = new Date(lastMod);
					if (isNaN(lastMod.getTime())) lastMod = null;
				}
				if (!lastMod) {
					lastMod = new Date();
				}
				if (resource.headers['X-Grant']) {
					res.set('Cache-Control', 'max-age=0,must-revalidate,private,no-cache');
					res.set('Pragma', 'no-cache');
				}
				res.set('Last-Modified', lastMod.toUTCString());
				resource.mtime = lastMod;
			});

			BufferResponse(res, function(err, bufl) {
				delete req.resource;
				if (err) return cb(err);
				// not sure we must ignore codes above 300 but what to do with them ?
				if (res.statusCode >= 200 && res.statusCode < 300) {
					resource.headers = raja.headers(res);
					debug('cache response', res.statusCode, url, resource.headers);
					if (url != baseUrl && isResJson(res)) {
						debug("url with query depends on baseUrl", url, baseUrl);
						resource.depend({url: baseUrl, headers: resource.headers, builder: 'express'});
					}
					resource.code = res.statusCode;
					resource.data = bufl.slice();
					resource.save();
				}
				cb();
			}.bind(this));
			next();
		}
	}.bind(this));
};
//}, function(url) {
//	return url;
//});

ExpressProxy.prototype.middleware = function(req, res, next) {
	if (this.opts.disable) return next();
	var method = req.method.toLowerCase();
	var baseUrl = req.protocol + "://" + req.get('Host');
	var url = baseUrl + req.url;
	baseUrl += req.path;

	var raja = this.raja;
	if (method == "get") {
		GetMw.call(this, url, baseUrl, req, res, next, function(err) {
			if (err) console.error(err);
		});
	} else if (method == "purge") {
		req.headers['x-raja'] = raja.opts.namespace;
		raja.retrieve(url, req, function(err, resource) {
			if (err) return next(err);
			if (!resource) return next(404);
			function finish(err) {
				if (err) console.error(err);
				invalidateAndSend(raja, {
					key: raja.reqkey(url, req),
					method: method,
					mtime: new Date()
				});
				res.sendStatus(200);
			}
			if (resource.page && raja.opts.dom) raja.opts.dom.pool.release(resource.page, finish);
			else finish();
		});
	} else {
		// must be done before because req.params changes
		var coll = collectionInfo(req.route.path, url, req.params);
		BufferResponse(res, function(err, bufl) {
			if (err) return raja.error(err);
			if (res.statusCode < 200 || res.statusCode >= 400) return;
			var now = new Date(); // TODO inspect HTTP response headers like Date, Last-Modified
			// data is response buffer or request body, in that order
			var data = bufl;
			if (method != "delete" && isResJson(res) && bufl && bufl.length) {
				data = JSON.parse(bufl.toString());
			} else if (isReqJson(req) && req.body) {
				data = req.body;
			}

			var headers = raja.headers(res);
			headers['X-Raja'] = raja.opts.namespace;
			if (method == "put" && !(coll && coll.url == url)) {
				// send the message about the element if the url concerns an element
				// and the method is PUT.
				invalidateAndSend(raja, {
					url: url,
					method: method,
					mtime: now,
					data: data
				}, headers);
			}
			if (coll) {
				if (method == "post" && res.get('Location')) {
					// obtain coll.value from matching loc against req.route.path
					coll.value = lastParam(req.route.path, res.get('Location'), coll.name);
				}
				if (coll.value !== undefined) {
					// either promote a DELETE element method to a collection method,
					// or integrate coll.name into a PUT element method data
					if (!data) data = {};
					if (data[coll.name] === undefined) data[coll.name] = coll.value;
				}
				invalidateAndSend(raja, {
					url: coll.url,
					method: method,
					mtime: now,
					data: ensureArray(data)
				}, headers);
			} else if (method == "post") {
				debug(url, "a POST to a non-collection route is not correct, invalidating anyway");
				invalidateAndSend(raja, {
					url: url,
					method: method,
					mtime: now,
					data: ensureArray(data)
				}, headers);
			}
		});
		next();
	}
};

function isResJson(res) {
	return res.req.is.call({headers: {
		'transfer-encoding': true,
		'content-length': 0,
		'content-type': res.get('Content-Type')
	}}, "json");
}

function isReqJson(req) {
	var type = req.get('Content-Type') || req.get('Accept') || '';
	return req.is('json') || type.indexOf('/json') > 0;
}

function collectionInfo(routePath, url, params) {
	var find = /^:(.+)\?$/.exec(routePath.split('/').pop());
	if (find != null && params.hasOwnProperty(find[1])) {
		var name = find[1];
		var value = params[name];
		if (value !== undefined) {
			url = url.split('/');
			url.pop();
			url = url.join('/');
		}
		return {url: url, name: name, value: value};
	} else {
		return;
	}
}

function lastParam(routePath, url, param) {
	var keys = [];
	var regexp = pathRegexp(routePath, keys);
	var m = regexp.exec(URL.parse(url).pathname);
	if (!m) return;
	var val = m[m.length-1];
	var key = keys[keys.length-1];
	if (key.name != param) return;
	if (typeof val == 'string') {
		try {	val = decodeURIComponent(val); } catch (e) { val = undefined; }
	}
	return val;
}

function ensureArray(data) {
	if (!data || data.length == 0) {
		// empty
		data = [];
	} else if (Array.isArray(data)) {
		// all right, do nothing
	} else if (typeof data == "string") {
		// pure strings
		if (data[0] == '{') data = '[' + data + ']';
	} else if (typeof data.slice == "function") {
		// Buffers, bl-buffers
		if (data.slice(0, 1).toString() == '{') data = '[' + data.toString() + ']';
	} else if (typeof data == "object") {
		// pure objects
		data = [data];
	}
	return data;
}

function invalidateAndSend(raja, msg, headers) {
	if (!msg.key) {
		msg.key = raja.reskey({url:msg.url, headers: headers});
		delete msg.url;
	}
	debug("invalidate and send", msg.key);
	raja.store.invalidate(msg.key, function(err) {
		if (err) return raja.error(err);
		raja.send(msg);
	});
}

<commit_msg>Express proxy: properly set raja namespace on response<commit_after>var BufferResponse = require('express-buffer-response');
var onHeaders = require('on-headers');
var pathRegexp = require('path-to-regexp');
var URL = require('url');
//var CacheDebounce = require('cache-debounce');
var debug = require('debug')('raja:express');

module.exports = ExpressProxy;

function ExpressProxy(raja, opts) {
	if (!(this instanceof ExpressProxy)) return new ExpressProxy(raja, opts);
	this.raja = raja;
	this.opts = opts || {};
	this.middleware = this.middleware.bind(this);
}

//var GetMw = CacheDebounce(function(url, baseUrl, req, res, next, cb) {
var GetMw = function(url, baseUrl, req, res, next, cb) {
	var raja = this.raja;
	res.set('X-Raja', raja.opts.namespace);
	raja.retrieve(url, res, function(err, resource) {
		if (err) raja.error(err); // fall through
		if (resource && resource.valid) {
			debug('found valid', resource.key, resource.headers);
			for (var name in resource.headers) {
				if (name in {'Content-Type':1, 'ETag':1, 'Vary':1, 'Content-Encoding':1, 'X-Grant': 1, 'X-Raja':1}) {
					res.set(name, resource.headers[name]);
				}
			}
			var ETag = resource.headers.ETag;
			if (ETag && req.get('If-None-Match') == ETag) {
				res.sendStatus(304);
			} else {
				if (resource.headers['X-Grant']) {
					res.set('Cache-Control', 'max-age=0,must-revalidate,private,no-cache');
					res.set('Pragma', 'no-cache');
				}
				if (resource.mtime) {
					res.set('Last-Modified', resource.mtime.toUTCString());
				}
				res.send(resource.data);
			}
			cb();
		} else {
			if (!resource) {
				debug('no resource found', url);
				resource = raja.create({url: url, builder: 'express'}, res);
			} else {
				debug('found invalid resource', resource.key);
			}
			req.resource = resource;
			onHeaders(res, function() {
				var lastMod = res.get('Last-Modified');
				if (lastMod) {
					lastMod = new Date(lastMod);
					if (isNaN(lastMod.getTime())) lastMod = null;
				}
				if (!lastMod) {
					lastMod = new Date();
				}
				if (resource.headers['X-Grant']) {
					res.set('Cache-Control', 'max-age=0,must-revalidate,private,no-cache');
					res.set('Pragma', 'no-cache');
				}
				res.set('Last-Modified', lastMod.toUTCString());
				resource.mtime = lastMod;
			});

			BufferResponse(res, function(err, bufl) {
				delete req.resource;
				if (err) return cb(err);
				// not sure we must ignore codes above 300 but what to do with them ?
				if (res.statusCode >= 200 && res.statusCode < 300) {
					resource.headers = raja.headers(res);
					debug('cache response', res.statusCode, url, resource.headers);
					if (url != baseUrl && isResJson(res)) {
						debug("url with query depends on baseUrl", url, baseUrl);
						resource.depend({url: baseUrl, headers: resource.headers, builder: 'express'});
					}
					resource.code = res.statusCode;
					resource.data = bufl.slice();
					resource.save();
				}
				cb();
			}.bind(this));
			next();
		}
	}.bind(this));
};
//}, function(url) {
//	return url;
//});

ExpressProxy.prototype.middleware = function(req, res, next) {
	if (this.opts.disable) return next();
	var method = req.method.toLowerCase();
	var baseUrl = req.protocol + "://" + req.get('Host');
	var url = baseUrl + req.url;
	baseUrl += req.path;

	var raja = this.raja;
	if (method == "get") {
		GetMw.call(this, url, baseUrl, req, res, next, function(err) {
			if (err) console.error(err);
		});
	} else if (method == "purge") {
		req.headers['x-raja'] = raja.opts.namespace;
		raja.retrieve(url, req, function(err, resource) {
			if (err) return next(err);
			if (!resource) return next(404);
			function finish(err) {
				if (err) console.error(err);
				invalidateAndSend(raja, {
					key: raja.reqkey(url, req),
					method: method,
					mtime: new Date()
				});
				res.sendStatus(200);
			}
			if (resource.page && raja.opts.dom) raja.opts.dom.pool.release(resource.page, finish);
			else finish();
		});
	} else {
		// must be done before because req.params changes
		var coll = collectionInfo(req.route.path, url, req.params);
		BufferResponse(res, function(err, bufl) {
			if (err) return raja.error(err);
			if (res.statusCode < 200 || res.statusCode >= 400) return;
			var now = new Date(); // TODO inspect HTTP response headers like Date, Last-Modified
			// data is response buffer or request body, in that order
			var data = bufl;
			if (method != "delete" && isResJson(res) && bufl && bufl.length) {
				data = JSON.parse(bufl.toString());
			} else if (isReqJson(req) && req.body) {
				data = req.body;
			}

			var headers = raja.headers(res);
			headers['X-Raja'] = raja.opts.namespace;
			if (method == "put" && !(coll && coll.url == url)) {
				// send the message about the element if the url concerns an element
				// and the method is PUT.
				invalidateAndSend(raja, {
					url: url,
					method: method,
					mtime: now,
					data: data
				}, headers);
			}
			if (coll) {
				if (method == "post" && res.get('Location')) {
					// obtain coll.value from matching loc against req.route.path
					coll.value = lastParam(req.route.path, res.get('Location'), coll.name);
				}
				if (coll.value !== undefined) {
					// either promote a DELETE element method to a collection method,
					// or integrate coll.name into a PUT element method data
					if (!data) data = {};
					if (data[coll.name] === undefined) data[coll.name] = coll.value;
				}
				invalidateAndSend(raja, {
					url: coll.url,
					method: method,
					mtime: now,
					data: ensureArray(data)
				}, headers);
			} else if (method == "post") {
				debug(url, "a POST to a non-collection route is not correct, invalidating anyway");
				invalidateAndSend(raja, {
					url: url,
					method: method,
					mtime: now,
					data: ensureArray(data)
				}, headers);
			}
		});
		next();
	}
};

function isResJson(res) {
	return res.req.is.call({headers: {
		'transfer-encoding': true,
		'content-length': 0,
		'content-type': res.get('Content-Type')
	}}, "json");
}

function isReqJson(req) {
	var type = req.get('Content-Type') || req.get('Accept') || '';
	return req.is('json') || type.indexOf('/json') > 0;
}

function collectionInfo(routePath, url, params) {
	var find = /^:(.+)\?$/.exec(routePath.split('/').pop());
	if (find != null && params.hasOwnProperty(find[1])) {
		var name = find[1];
		var value = params[name];
		if (value !== undefined) {
			url = url.split('/');
			url.pop();
			url = url.join('/');
		}
		return {url: url, name: name, value: value};
	} else {
		return;
	}
}

function lastParam(routePath, url, param) {
	var keys = [];
	var regexp = pathRegexp(routePath, keys);
	var m = regexp.exec(URL.parse(url).pathname);
	if (!m) return;
	var val = m[m.length-1];
	var key = keys[keys.length-1];
	if (key.name != param) return;
	if (typeof val == 'string') {
		try {	val = decodeURIComponent(val); } catch (e) { val = undefined; }
	}
	return val;
}

function ensureArray(data) {
	if (!data || data.length == 0) {
		// empty
		data = [];
	} else if (Array.isArray(data)) {
		// all right, do nothing
	} else if (typeof data == "string") {
		// pure strings
		if (data[0] == '{') data = '[' + data + ']';
	} else if (typeof data.slice == "function") {
		// Buffers, bl-buffers
		if (data.slice(0, 1).toString() == '{') data = '[' + data.toString() + ']';
	} else if (typeof data == "object") {
		// pure objects
		data = [data];
	}
	return data;
}

function invalidateAndSend(raja, msg, headers) {
	if (!msg.key) {
		msg.key = raja.reskey({url:msg.url, headers: headers});
		delete msg.url;
	}
	debug("invalidate and send", msg.key);
	raja.store.invalidate(msg.key, function(err) {
		if (err) return raja.error(err);
		raja.send(msg);
	});
}

<|endoftext|>


<commit_before>module.exports = function (RED) {
  var spawn = require('child_process').spawn;
  var util = require('util');

  function indentLines(fnCode, depth) {
    return fnCode.split('\n').map((line) => Array(depth).join(' ') + line).join('\n')
  }

  function spawnFn(self) {
    self.child = spawn('python', ['-uc', self.func.code], {stdio: ['pipe', 'pipe', 'pipe', 'ipc']});
    self.child.stdout.on('data', function (data) {
      self.log(data.toString());
    });
    self.child.stderr.on('data', function (data) {
      self.error(data.toString());
    });
    self.child.on('close', function (exitCode) {
      if (exitCode) {
        self.error(`Python Function process exited with code ${exitCode}`);
        if (self.func.attempts) {
          spawnFn(self);
          self.func.attempts--;
        } else {
          self.error(`Function '${self.name}' has failed more than 10 times. Fix it and deploy again`)
          self.status({fill: 'red', shape: 'dot', text: 'Stopped, see debug panel'});
        }
      }
    });
    self.child.on('message', function (response) {
      switch (response.ctx) {
        case 'send':
          sendResults(self, response.msgid, response.value);
          break;
        case 'log':
        case 'warn':
        case 'error':
        case 'status':
          self[response.ctx].apply(self, response.value);
          break;
        default:
          throw new Error(`Don't know what to do with ${response.ctx}`);
      }
    });
    self.log(`Python function '${self.name}' running on PID ${self.child.pid}`);
    self.status({fill: 'green', shape: 'dot', text: 'Running'});
  }

  function sendResults(self, _msgid, msgs) {
      if (msgs == null) {
          return;
      } else if (!util.isArray(msgs)) {
          msgs = [msgs];
      }
      var msgCount = 0;
      for (var m=0;m<msgs.length;m++) {
          if (msgs[m]) {
              if (util.isArray(msgs[m])) {
                  for (var n=0; n < msgs[m].length; n++) {
                      msgs[m][n]._msgid = _msgid;
                      msgCount++;
                  }
              } else {
                  msgs[m]._msgid = _msgid;
                  msgCount++;
              }
          }
      }
      if (msgCount>0) {
          self.send(msgs);
      }
  }

  function PythonFunction(config) {
    var self = this;
    RED.nodes.createNode(self, config);
    self.name = config.name;
    self.func = {
      code: `
import sys
import os
import json

channel = os.fdopen(3, "r+")

class Msg(object):
    SEND = 'send'
    LOG = 'log'
    WARN = 'warn'
    ERROR = 'error'
    STATUS = 'status'

    def __init__(self, ctx, value, msgid):
        self.ctx = ctx
        self.value = value
        self.msgid = msgid

    def dumps(self):
        return json.dumps(vars(self)) + "\\n"

    @classmethod
    def loads(cls, json_string):
        return cls(**json.loads(json_string))

class Node(object):
    def __init__(self, msgid, channel):
        self.__msgid = msgid
        self.__channel = channel

    def send(self, msg):
        msg = Msg(Msg.SEND, msg, self.__msgid)
        self.send_to_node(msg)

    def log(self, *args):
        msg = Msg(Msg.LOG, args, self.__msgid)
        self.send_to_node(msg)

    def warn(self, *args):
        msg = Msg(Msg.WARN, args, self.__msgid)
        self.send_to_node(msg)

    def error(self, *args):
        msg = Msg(Msg.ERROR, args, self.__msgid)
        self.send_to_node(msg)

    def status(self, *args):
        msg = Msg(Msg.STATUS, args, self.__msgid)
        self.send_to_node(msg)

    def send_to_node(self, msg):
        self.__channel.write(msg.dumps())

def python_function(msg):
` + indentLines(config.func, 4) +
`
while True:
    raw_msg = channel.readline()
    if not raw_msg:
        raise RuntimeError('Received EOF!')
    msg = json.loads(raw_msg)
    msgid = msg["_msgid"]
    node = Node(msgid, channel)
    res_msgs = python_function(msg)
    node.send(res_msgs)
`,
      attempts: 10
    };
    spawnFn(self);
    self.on('input', function(msg) {
      var cache = [];
      jsonMsg = JSON.stringify(msg, function(key, value) {
          if (typeof value === 'object' && value !== null) {
              if (cache.indexOf(value) !== -1) {
                  // Circular reference found, discard key
                  return;
              }
              // Store value in our collection
              cache.push(value);
          }
          return value;
      });
      cache = null; // Enable garbage collection
      self.child.send(JSON.parse(jsonMsg));
    });
    self.on('close', function () {
      self.child.kill();
    });
  }
  RED.nodes.registerType('python-function', PythonFunction);
};
<commit_msg>Remove unused sys import in Python code<commit_after>module.exports = function (RED) {
  var spawn = require('child_process').spawn;
  var util = require('util');

  function indentLines(fnCode, depth) {
    return fnCode.split('\n').map((line) => Array(depth).join(' ') + line).join('\n')
  }

  function spawnFn(self) {
    self.child = spawn('python', ['-uc', self.func.code], {stdio: ['pipe', 'pipe', 'pipe', 'ipc']});
    self.child.stdout.on('data', function (data) {
      self.log(data.toString());
    });
    self.child.stderr.on('data', function (data) {
      self.error(data.toString());
    });
    self.child.on('close', function (exitCode) {
      if (exitCode) {
        self.error(`Python Function process exited with code ${exitCode}`);
        if (self.func.attempts) {
          spawnFn(self);
          self.func.attempts--;
        } else {
          self.error(`Function '${self.name}' has failed more than 10 times. Fix it and deploy again`)
          self.status({fill: 'red', shape: 'dot', text: 'Stopped, see debug panel'});
        }
      }
    });
    self.child.on('message', function (response) {
      switch (response.ctx) {
        case 'send':
          sendResults(self, response.msgid, response.value);
          break;
        case 'log':
        case 'warn':
        case 'error':
        case 'status':
          self[response.ctx].apply(self, response.value);
          break;
        default:
          throw new Error(`Don't know what to do with ${response.ctx}`);
      }
    });
    self.log(`Python function '${self.name}' running on PID ${self.child.pid}`);
    self.status({fill: 'green', shape: 'dot', text: 'Running'});
  }

  function sendResults(self, _msgid, msgs) {
      if (msgs == null) {
          return;
      } else if (!util.isArray(msgs)) {
          msgs = [msgs];
      }
      var msgCount = 0;
      for (var m=0;m<msgs.length;m++) {
          if (msgs[m]) {
              if (util.isArray(msgs[m])) {
                  for (var n=0; n < msgs[m].length; n++) {
                      msgs[m][n]._msgid = _msgid;
                      msgCount++;
                  }
              } else {
                  msgs[m]._msgid = _msgid;
                  msgCount++;
              }
          }
      }
      if (msgCount>0) {
          self.send(msgs);
      }
  }

  function PythonFunction(config) {
    var self = this;
    RED.nodes.createNode(self, config);
    self.name = config.name;
    self.func = {
      code: `
import os
import json

channel = os.fdopen(3, "r+")

class Msg(object):
    SEND = 'send'
    LOG = 'log'
    WARN = 'warn'
    ERROR = 'error'
    STATUS = 'status'

    def __init__(self, ctx, value, msgid):
        self.ctx = ctx
        self.value = value
        self.msgid = msgid

    def dumps(self):
        return json.dumps(vars(self)) + "\\n"

    @classmethod
    def loads(cls, json_string):
        return cls(**json.loads(json_string))

class Node(object):
    def __init__(self, msgid, channel):
        self.__msgid = msgid
        self.__channel = channel

    def send(self, msg):
        msg = Msg(Msg.SEND, msg, self.__msgid)
        self.send_to_node(msg)

    def log(self, *args):
        msg = Msg(Msg.LOG, args, self.__msgid)
        self.send_to_node(msg)

    def warn(self, *args):
        msg = Msg(Msg.WARN, args, self.__msgid)
        self.send_to_node(msg)

    def error(self, *args):
        msg = Msg(Msg.ERROR, args, self.__msgid)
        self.send_to_node(msg)

    def status(self, *args):
        msg = Msg(Msg.STATUS, args, self.__msgid)
        self.send_to_node(msg)

    def send_to_node(self, msg):
        self.__channel.write(msg.dumps())

def python_function(msg):
` + indentLines(config.func, 4) +
`
while True:
    raw_msg = channel.readline()
    if not raw_msg:
        raise RuntimeError('Received EOF!')
    msg = json.loads(raw_msg)
    msgid = msg["_msgid"]
    node = Node(msgid, channel)
    res_msgs = python_function(msg)
    node.send(res_msgs)
`,
      attempts: 10
    };
    spawnFn(self);
    self.on('input', function(msg) {
      var cache = [];
      jsonMsg = JSON.stringify(msg, function(key, value) {
          if (typeof value === 'object' && value !== null) {
              if (cache.indexOf(value) !== -1) {
                  // Circular reference found, discard key
                  return;
              }
              // Store value in our collection
              cache.push(value);
          }
          return value;
      });
      cache = null; // Enable garbage collection
      self.child.send(JSON.parse(jsonMsg));
    });
    self.on('close', function () {
      self.child.kill();
    });
  }
  RED.nodes.registerType('python-function', PythonFunction);
};
<|endoftext|>


<commit_before>var _ = require("underscore"),
    logger = require("./logging").getLogger(),
    Promise = require("bluebird"),
    deepCopy = require("deep-copy"),
    models = require("./server-models");

module.exports = function(db, options) {
    var group = {};

    group.assignGroupToUser = function(userId, eventId) {
        return new Promise(function(resolve, reject) {
            return resolve(group.setSessionAssigneesAndUserPreference(eventId, userId));
        });
    };

    group.createSession = function(eventId, userId) {
        return new Promise(function(resolve, reject) {
            var user = db.users.get(userId);
            var event = db.events.get(eventId);
            var randomizedSessionsList = group.getRandomizedSessionsList(eventId);
            var roomNum = randomizedSessionsList.length + 1;
            var title = "Breakout Room " + roomNum;
            //Force sessions type to be "simple"
            var activities = [];
            activities.push({type: "about", autoHide: true});
            /* Keep the sessions' joining cap as 2 for now, we'll change this
            later and make it as an input available for admins */
            var joinCap = 2;
            var newSession;

            newSession = new models.ServerSession({
                title: title,
                proposedBy: null,
                activities: activities,
                joinCap: joinCap,
                description: "",
                approved: true,
                randomized: true,
                assignedParticipants: [userId]
            }, {
                collection: event.get("sessions")
            });
            newSession.save({}, {
                success: function() {
                    event.get("sessions").add(newSession);
                    event.logAnalytics({
                        action: "create-session",
                        user: user,
                        session: newSession,
                        title: title,
                        activities: activities,
                        description: ""
                    });
                    return resolve({newSessionId: newSession.id});
                },
                error: function(err) {
                    return reject(err, "Error creating session");
                }
            });
        });
    };

    group.setSessionAssigneesAndUserPreference = function(eventId, userId) {
        return new Promise(function(resolve, reject) {
            var user = db.users.get(userId);
            if (!user) {
                return reject("Error! User or event is missing.");
            }
            var randomizedSessionsList = group.getRandomizedSessionsList(eventId);
            var pref = deepCopy(user.get("sessionPreference") || {});
            if(pref && pref[eventId] && pref[eventId].length > 0) {
                for(c= 0; c< randomizedSessionsList.length; c++) {
                    var session = db.events.getSessionById(randomizedSessionsList[c]);
                    if(session.get("id") == pref[eventId]) {
                        session.removeAssignedParticipant(user);
                        randomizedSessionsList.splice(c, 1);
                        break;
                    }
                }
                user.setSessionPreference(eventId, null);
            }
            if(randomizedSessionsList.length == 0) {
                group.createSession(eventId, userId).then(function(data) {
                    user.setSessionPreference(eventId, data.newSessionId);
                    return resolve();
                });
            } else {
                var assigned = false;
                var sessionId;
                for(var c=0; c< randomizedSessionsList.length; c++) {
                    var session = db.events.getSessionById(randomizedSessionsList[c]);
                    sessionId = session.get("id");
                    assigned = session.setAssignedParticipants(userId);
                    if(assigned) {
                        break;
                    }
                }
                if(!assigned) {
                    group.createSession(eventId, userId).then(function(data) {
                        sessionId = data.newSessionId;
                    });
                }
                user.setSessionPreference(eventId, sessionId);
                return resolve();
            }
        });
    };

    group.getRandomizedSessionsList = function(eventId) {
        var event = db.events.get(eventId);
        if (!event) {
            return reject("Error! Event is missing.");
        }
        var randomizedSessionsList = [];
        event.get("sessions").each(function(session) {
            if(session.get("randomized")) {
                randomizedSessionsList.push(session.get("id"));
            }
        });
        return randomizedSessionsList;
    };

    return group;
};<commit_msg>hard coding join cap as 6<commit_after>var _ = require("underscore"),
    logger = require("./logging").getLogger(),
    Promise = require("bluebird"),
    deepCopy = require("deep-copy"),
    models = require("./server-models");

module.exports = function(db, options) {
    var group = {};

    group.assignGroupToUser = function(userId, eventId) {
        return new Promise(function(resolve, reject) {
            return resolve(group.setSessionAssigneesAndUserPreference(eventId, userId));
        });
    };

    group.createSession = function(eventId, userId) {
        return new Promise(function(resolve, reject) {
            var user = db.users.get(userId);
            var event = db.events.get(eventId);
            var randomizedSessionsList = group.getRandomizedSessionsList(eventId);
            var roomNum = randomizedSessionsList.length + 1;
            var title = "Breakout Room " + roomNum;
            //Force sessions type to be "simple"
            var activities = [];
            activities.push({type: "about", autoHide: true});
            /* Keep the sessions' joining cap as 2 for now, we'll change this
            later and make it as an input available for admins */
            var joinCap = 6;
            var newSession;

            newSession = new models.ServerSession({
                title: title,
                proposedBy: null,
                activities: activities,
                joinCap: joinCap,
                description: "",
                approved: true,
                randomized: true,
                assignedParticipants: [userId]
            }, {
                collection: event.get("sessions")
            });
            newSession.save({}, {
                success: function() {
                    event.get("sessions").add(newSession);
                    event.logAnalytics({
                        action: "create-session",
                        user: user,
                        session: newSession,
                        title: title,
                        activities: activities,
                        description: ""
                    });
                    return resolve({newSessionId: newSession.id});
                },
                error: function(err) {
                    return reject(err, "Error creating session");
                }
            });
        });
    };

    group.setSessionAssigneesAndUserPreference = function(eventId, userId) {
        return new Promise(function(resolve, reject) {
            var user = db.users.get(userId);
            if (!user) {
                return reject("Error! User or event is missing.");
            }
            var randomizedSessionsList = group.getRandomizedSessionsList(eventId);
            var pref = deepCopy(user.get("sessionPreference") || {});
            if(pref && pref[eventId] && pref[eventId].length > 0) {
                for(c= 0; c< randomizedSessionsList.length; c++) {
                    var session = db.events.getSessionById(randomizedSessionsList[c]);
                    if(session.get("id") == pref[eventId]) {
                        session.removeAssignedParticipant(user);
                        randomizedSessionsList.splice(c, 1);
                        break;
                    }
                }
                user.setSessionPreference(eventId, null);
            }
            if(randomizedSessionsList.length == 0) {
                group.createSession(eventId, userId).then(function(data) {
                    user.setSessionPreference(eventId, data.newSessionId);
                    return resolve();
                });
            } else {
                var assigned = false;
                var sessionId;
                for(var c=0; c< randomizedSessionsList.length; c++) {
                    var session = db.events.getSessionById(randomizedSessionsList[c]);
                    sessionId = session.get("id");
                    assigned = session.setAssignedParticipants(userId);
                    if(assigned) {
                        break;
                    }
                }
                if(!assigned) {
                    group.createSession(eventId, userId).then(function(data) {
                        sessionId = data.newSessionId;
                    });
                }
                user.setSessionPreference(eventId, sessionId);
                return resolve();
            }
        });
    };

    group.getRandomizedSessionsList = function(eventId) {
        var event = db.events.get(eventId);
        if (!event) {
            return reject("Error! Event is missing.");
        }
        var randomizedSessionsList = [];
        event.get("sessions").each(function(session) {
            if(session.get("randomized")) {
                randomizedSessionsList.push(session.get("id"));
            }
        });
        return randomizedSessionsList;
    };

    return group;
};<|endoftext|>


<commit_before>'use strict';
const AsyncStream = require('./streams/async-stream');
const Fragment = require('./fragment');
const StringifierStream = require('./streams/stringifier-stream');
const ContentLengthStream = require('./streams/content-length-stream');
const FRAGMENT_EVENTS = ['start', 'response', 'end', 'error', 'timeout', 'fallback', 'warn'];

module.exports = function processRequest (options, request, response) {

    this.emit('start', request);

    const fetchContext = options.fetchContext;
    const fetchTemplate = options.fetchTemplate;
    const handleTag = options.handleTag;
    const parseTemplate = options.parseTemplate;
    const requestFragment = options.requestFragment;
    const pipeInstanceName = options.pipeInstanceName();
    const pipeDefinition = options.pipeDefinition(pipeInstanceName);

    const asyncStream = new AsyncStream();
    const contextPromise = fetchContext(request).catch((err) => {
        this.emit('context:error', request, err);
        return {};
    });
    const templatePromise = fetchTemplate(request, parseTemplate);
    const responseHeaders = {
        // Disable cache in browsers and proxies
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Content-Type': 'text/html'
    };

    let shouldWriteHead = true;
    let index = 0;

    contextPromise.then((context) => {

        const contentLengthStream = new ContentLengthStream((contentLength) => {
            this.emit('end', request, contentLength);
        });

        const resultStream = new StringifierStream((tag) => {

            if (tag.placeholder === 'pipe') {
                return pipeDefinition;
            }

            if (tag.placeholder === 'async') {
                // end of body tag
                return asyncStream;
            }

            if (tag.name === options.fragmentTag) {
                const fragment = new Fragment(
                    tag,
                    context,
                    index++,
                    requestFragment,
                    pipeInstanceName
                );

                FRAGMENT_EVENTS.forEach((eventName) => {
                    fragment.on(eventName, (function () {
                        // this has to be a function, because
                        // arrow functions don't have `arguments`
                        const prefixedName = 'fragment:' + eventName;
                        const prefixedArgs = [prefixedName, request, fragment.attributes].concat(...arguments);
                        this.emit.apply(this, prefixedArgs);
                    }).bind(this));
                });

                if (fragment.attributes.async) {
                    asyncStream.write(fragment.stream);
                }

                if (fragment.attributes.primary && shouldWriteHead) {
                    shouldWriteHead = false;
                    fragment.on('response', (statusCode, headers) => {
                        if (headers.location) {
                            responseHeaders['Location'] = headers.location;
                        }
                        this.emit('response', request, statusCode, responseHeaders);
                        response.writeHead(statusCode, responseHeaders);
                        resultStream
                            .pipe(contentLengthStream)
                            .pipe(response);
                    });
                    fragment.on('fallback', (err) => {
                        this.emit('error', request, err);
                        response.writeHead(500, responseHeaders);
                        resultStream
                            .pipe(contentLengthStream)
                            .pipe(response);
                    });
                    fragment.on('error', (err) => {
                        this.emit('error', request, err);
                        response.writeHead(500, responseHeaders);
                        response.end();
                    });
                }

                return fragment.fetch(request, false);
            }

            return handleTag(request, tag);
        });

        resultStream.on('finish', () => {
            asyncStream.end();
            const statusCode = response.statusCode || 200;
            if (shouldWriteHead) {
                shouldWriteHead = false;
                this.emit('response', request, statusCode, responseHeaders);
                response.writeHead(statusCode, responseHeaders);
                resultStream
                    .pipe(contentLengthStream)
                    .pipe(response);
            }
        });

        resultStream.on('error', (err) => {
            this.emit('error', request, err);
            if (shouldWriteHead) {
                shouldWriteHead = false;
                response.writeHead(500, responseHeaders);
                // To render with custom error template
                if (typeof err.presentable === 'string') {
                    response.end(`${err.presentable}`);
                } else {
                    response.end();
                }
            } else {
                contentLengthStream.end();
            }
        });

        templatePromise
            .then((template) => {
                resultStream.end(template);
            })
            .catch((err) => {
                resultStream.emit('error', err);
            });
    });
};
<commit_msg>changes to remove the binding<commit_after>'use strict';
const AsyncStream = require('./streams/async-stream');
const Fragment = require('./fragment');
const StringifierStream = require('./streams/stringifier-stream');
const ContentLengthStream = require('./streams/content-length-stream');
const FRAGMENT_EVENTS = ['start', 'response', 'end', 'error', 'timeout', 'fallback', 'warn'];

module.exports = function processRequest (options, request, response) {
    this.emit('start', request);

    const fetchContext = options.fetchContext;
    const fetchTemplate = options.fetchTemplate;
    const handleTag = options.handleTag;
    const parseTemplate = options.parseTemplate;
    const requestFragment = options.requestFragment;
    const pipeInstanceName = options.pipeInstanceName();
    const pipeDefinition = options.pipeDefinition(pipeInstanceName);

    const asyncStream = new AsyncStream();
    const contextPromise = fetchContext(request).catch((err) => {
        this.emit('context:error', request, err);
        return {};
    });
    const templatePromise = fetchTemplate(request, parseTemplate);
    const responseHeaders = {
        // Disable cache in browsers and proxies
        'Cache-Control': 'no-cache, no-store, must-revalidate',
        'Pragma': 'no-cache',
        'Content-Type': 'text/html'
    };

    let shouldWriteHead = true;
    let index = 0;

    contextPromise.then((context) => {

        const contentLengthStream = new ContentLengthStream((contentLength) => {
            this.emit('end', request, contentLength);
        });

        const resultStream = new StringifierStream((tag) => {

            if (tag.placeholder === 'pipe') {
                return pipeDefinition;
            }

            if (tag.placeholder === 'async') {
                // end of body tag
                return asyncStream;
            }

            if (tag.name === options.fragmentTag) {
                const fragment = new Fragment(
                    tag,
                    context,
                    index++,
                    requestFragment,
                    pipeInstanceName
                );

                FRAGMENT_EVENTS.forEach((eventName) => {
                    fragment.on(eventName, (...args) => {
                        const prefixedName = 'fragment:' + eventName;
                        this.emit(prefixedName, request, fragment.attributes, ...args);
                    });
                });

                if (fragment.attributes.async) {
                    asyncStream.write(fragment.stream);
                }

                if (fragment.attributes.primary && shouldWriteHead) {
                    shouldWriteHead = false;
                    fragment.on('response', (statusCode, headers) => {
                        if (headers.location) {
                            responseHeaders['Location'] = headers.location;
                        }
                        this.emit('response', request, statusCode, responseHeaders);
                        response.writeHead(statusCode, responseHeaders);
                        resultStream
                            .pipe(contentLengthStream)
                            .pipe(response);
                    });
                    fragment.on('fallback', (err) => {
                        this.emit('error', request, err);
                        response.writeHead(500, responseHeaders);
                        resultStream
                            .pipe(contentLengthStream)
                            .pipe(response);
                    });
                    fragment.on('error', (err) => {
                        this.emit('error', request, err);
                        response.writeHead(500, responseHeaders);
                        response.end();
                    });
                }

                return fragment.fetch(request, false);
            }

            return handleTag(request, tag);
        });

        resultStream.on('finish', () => {
            asyncStream.end();
            const statusCode = response.statusCode || 200;
            if (shouldWriteHead) {
                shouldWriteHead = false;
                this.emit('response', request, statusCode, responseHeaders);
                response.writeHead(statusCode, responseHeaders);
                resultStream
                    .pipe(contentLengthStream)
                    .pipe(response);
            }
        });

        resultStream.on('error', (err) => {
            this.emit('error', request, err);
            if (shouldWriteHead) {
                shouldWriteHead = false;
                response.writeHead(500, responseHeaders);
                // To render with custom error template
                if (typeof err.presentable === 'string') {
                    response.end(`${err.presentable}`);
                } else {
                    response.end();
                }
            } else {
                contentLengthStream.end();
            }
        });

        templatePromise
            .then((template) => {
                resultStream.end(template);
            })
            .catch((err) => {
                resultStream.emit('error', err);
            });
    });
};
<|endoftext|>