<commit_before>var fs = require('fs');
var path = require('path');
var xmldom = require('xmldom');

var file = 'pom.xml';

function version() {
  var pom = fs.readFileSync(path.join(process.cwd(), file), 'utf8');
  var doc = new xmldom.DOMParser().parseFromString(pom);
  return doc.documentElement.getElementsByTagName("version")[0].firstChild.data;
}

function bump(version, filename) {
  var filename = path.join(process.cwd(), filename);
  var pom = fs.readFileSync(filename, 'utf8');
  var doc = new xmldom.DOMParser().parseFromString(pom);
  doc.documentElement.getElementsByTagName('version')[0].firstChild.data = version;
  fs.writeFileSync(filename, new xmldom.XMLSerializer().serializeToString(doc));
}

exports.name = 'Maven';
exports.file = file;
exports.version = version;
exports.bump = bump;
<commit_msg>maven: don't try to update parent version<commit_after>var fs = require('fs');
var path = require('path');
var xmldom = require('xmldom');

var file = 'pom.xml';

function version() {
  var pom = fs.readFileSync(path.join(process.cwd(), file), 'utf8');
  var doc = new xmldom.DOMParser().parseFromString(pom);
  var versions = doc.documentElement.getElementsByTagName("version");
  for (var i = 0; i < versions.length; i++) {
    if (versions[i].parentNode.localName == "project") {
      return versions[i].firstChild.data;
    }
  }
}

function bump(version, filename) {
  var filename = path.join(process.cwd(), filename);
  var pom = fs.readFileSync(filename, 'utf8');
  var doc = new xmldom.DOMParser().parseFromString(pom);
  var versions = doc.documentElement.getElementsByTagName("version");
  for (var i = 0; i < versions.length; i++) {
    if (versions[i].parentNode.localName == "project") {
      versions[i].firstChild.data = version;
    }
  }
  fs.writeFileSync(filename, new xmldom.XMLSerializer().serializeToString(doc));
}

exports.name = 'Maven';
exports.file = file;
exports.version = version;
exports.bump = bump;
<|endoftext|>


<commit_before>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');

/**
 * @api private
 */
AWS.ParamValidator = AWS.util.inherit({
  validate: function validate(rules, params, context) {
    var cRules = (rules || {}).members || {};
    var payload = rules ? rules.xml : null;
    if (payload) {
      cRules = AWS.util.merge(cRules, (cRules[payload] || {}).members || {});
      delete cRules[payload];
    }

    return this.validateStructure(cRules, params || {}, context || 'params');
  },

  validateStructure: function validateStructure(rules, params, context) {
    /*jshint maxcomplexity:12*/
    this.validateType(context, params, ['object'], 'structure');

    /*jshint forin:false*/
    for (var paramName in rules) {
      if (!rules.hasOwnProperty(paramName)) continue;
      if (rules[paramName].required && params[paramName] === undefined) {
        this.fail('MissingRequiredParameter',
          'Missing required key \'' + paramName + '\' in ' + context);
      }
    }

    // validate hash members
    for (paramName in params) {
      if (!params.hasOwnProperty(paramName)) continue;

      var paramValue = params[paramName],
          paramRules = rules[paramName];

      if (paramRules !== undefined) {
        var memberContext = [context, paramName].join('.');
        this.validateMember(paramRules, paramValue, memberContext);
      } else {
        this.fail('UnexpectedParameter',
          'Unexpected key \'' + paramName + '\' found in ' + context);
      }
    }

    return true;
  },

  validateMember: function validateMember(rules, param, context) {
    var memberRules = rules.members || {};
    switch(rules.type) {
      case 'structure':
        return this.validateStructure(memberRules, param, context);
      case 'list':
        return this.validateList(memberRules, param, context);
      case 'map':
        return this.validateMap(memberRules, param, context);
      default:
        return this.validateScalar(rules, param, context);
    }
  },

  validateList: function validateList(rules, params, context) {
    this.validateType(context, params, [Array]);

    // validate array members
    for (var i = 0; i < params.length; i++) {
      this.validateMember(rules, params[i], context + '[' + i + ']');
    }
  },

  validateMap: function validateMap(rules, params, context) {
    this.validateType(context, params, ['object'], 'map');

    /*jshint forin:false*/
    for (var param in params) {
      if (!params.hasOwnProperty(param)) continue;
      this.validateMember(rules, params[param],
                          context + '[\'' +  param + '\']');
    }
  },

  validateScalar: function validateScalar(rules, value, context) {
    /*jshint maxcomplexity:12*/
    switch (rules.type) {
      case null:
      case undefined:
      case 'string':
        return this.validateType(context, value, ['string']);
      case 'base64':
      case 'binary':
        return this.validatePayload(context, value);
      case 'integer':
      case 'float':
        return this.validateType(context, value, ['number']);
      case 'boolean':
        return this.validateType(context, value, ['boolean']);
      case 'timestamp':
        return this.validateType(context, value, [Date,
          /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, 'number'],
          'Date object, ISO-8601 string, or a UNIX timestamp');
      default:
        return this.fail('UnkownType', 'Unhandled type ' +
                         rules.type + ' for ' + context);
    }
  },

  fail: function fail(code, message) {
    throw AWS.util.error(new Error(message), {code: code});
  },

  validateType: function validateType(context, value, acceptedTypes, type) {
    /*jshint maxcomplexity:12*/
    var foundInvalidType = false;
    for (var i = 0; i < acceptedTypes.length; i++) {
      if (typeof acceptedTypes[i] === 'string') {
        if (typeof value === acceptedTypes[i]) return;
      } else if (acceptedTypes[i] instanceof RegExp) {
        if ((value || '').toString().match(acceptedTypes[i])) return;
      } else {
        if (value instanceof acceptedTypes[i]) return;
        if (AWS.util.isType(value, acceptedTypes[i])) return;
        if (!type && !foundInvalidType) acceptedTypes = acceptedTypes.slice();
        acceptedTypes[i] = AWS.util.typeName(acceptedTypes[i]);
      }
      foundInvalidType = true;
    }

    var acceptedType = type;
    if (!acceptedType) {
      /*jshint regexp:false*/
      acceptedType = acceptedTypes.join(', ').replace(/,([^,]+)$/, ', or$1');
    }

    var vowel = acceptedType.match(/^[aeiou]/i) ? 'n' : '';
    this.fail('InvalidParameterType', 'Expected ' + context + ' to be a' +
              vowel + ' ' + acceptedType);
  },

  validatePayload: function validatePayload(context, value) {
    if (typeof value === 'string') return;
    if (value && typeof value.byteLength === 'number') return; // typed arrays

    var types = ['Buffer', 'Stream', 'File', 'Blob', 'ArrayBuffer', 'DataView'];
    if (value) {
      for (var i = 0; i < types.length; i++) {
        if (AWS.util.isType(value, types[i])) return;
        if (AWS.util.typeName(value.constructor) == types[i]) return;
      }
    }

    this.fail('InvalidParameterType', 'Expected ' + context + ' to be a ' +
      'string, Buffer, Stream, Blob, or typed array object');
  }
});
<commit_msg>Add special case checking for Streams in Node.js environment<commit_after>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');

/**
 * @api private
 */
AWS.ParamValidator = AWS.util.inherit({
  validate: function validate(rules, params, context) {
    var cRules = (rules || {}).members || {};
    var payload = rules ? rules.xml : null;
    if (payload) {
      cRules = AWS.util.merge(cRules, (cRules[payload] || {}).members || {});
      delete cRules[payload];
    }

    return this.validateStructure(cRules, params || {}, context || 'params');
  },

  validateStructure: function validateStructure(rules, params, context) {
    /*jshint maxcomplexity:12*/
    this.validateType(context, params, ['object'], 'structure');

    /*jshint forin:false*/
    for (var paramName in rules) {
      if (!rules.hasOwnProperty(paramName)) continue;
      if (rules[paramName].required && params[paramName] === undefined) {
        this.fail('MissingRequiredParameter',
          'Missing required key \'' + paramName + '\' in ' + context);
      }
    }

    // validate hash members
    for (paramName in params) {
      if (!params.hasOwnProperty(paramName)) continue;

      var paramValue = params[paramName],
          paramRules = rules[paramName];

      if (paramRules !== undefined) {
        var memberContext = [context, paramName].join('.');
        this.validateMember(paramRules, paramValue, memberContext);
      } else {
        this.fail('UnexpectedParameter',
          'Unexpected key \'' + paramName + '\' found in ' + context);
      }
    }

    return true;
  },

  validateMember: function validateMember(rules, param, context) {
    var memberRules = rules.members || {};
    switch(rules.type) {
      case 'structure':
        return this.validateStructure(memberRules, param, context);
      case 'list':
        return this.validateList(memberRules, param, context);
      case 'map':
        return this.validateMap(memberRules, param, context);
      default:
        return this.validateScalar(rules, param, context);
    }
  },

  validateList: function validateList(rules, params, context) {
    this.validateType(context, params, [Array]);

    // validate array members
    for (var i = 0; i < params.length; i++) {
      this.validateMember(rules, params[i], context + '[' + i + ']');
    }
  },

  validateMap: function validateMap(rules, params, context) {
    this.validateType(context, params, ['object'], 'map');

    /*jshint forin:false*/
    for (var param in params) {
      if (!params.hasOwnProperty(param)) continue;
      this.validateMember(rules, params[param],
                          context + '[\'' +  param + '\']');
    }
  },

  validateScalar: function validateScalar(rules, value, context) {
    /*jshint maxcomplexity:12*/
    switch (rules.type) {
      case null:
      case undefined:
      case 'string':
        return this.validateType(context, value, ['string']);
      case 'base64':
      case 'binary':
        return this.validatePayload(context, value);
      case 'integer':
      case 'float':
        return this.validateType(context, value, ['number']);
      case 'boolean':
        return this.validateType(context, value, ['boolean']);
      case 'timestamp':
        return this.validateType(context, value, [Date,
          /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, 'number'],
          'Date object, ISO-8601 string, or a UNIX timestamp');
      default:
        return this.fail('UnkownType', 'Unhandled type ' +
                         rules.type + ' for ' + context);
    }
  },

  fail: function fail(code, message) {
    throw AWS.util.error(new Error(message), {code: code});
  },

  validateType: function validateType(context, value, acceptedTypes, type) {
    /*jshint maxcomplexity:12*/
    var foundInvalidType = false;
    for (var i = 0; i < acceptedTypes.length; i++) {
      if (typeof acceptedTypes[i] === 'string') {
        if (typeof value === acceptedTypes[i]) return;
      } else if (acceptedTypes[i] instanceof RegExp) {
        if ((value || '').toString().match(acceptedTypes[i])) return;
      } else {
        if (value instanceof acceptedTypes[i]) return;
        if (AWS.util.isType(value, acceptedTypes[i])) return;
        if (!type && !foundInvalidType) acceptedTypes = acceptedTypes.slice();
        acceptedTypes[i] = AWS.util.typeName(acceptedTypes[i]);
      }
      foundInvalidType = true;
    }

    var acceptedType = type;
    if (!acceptedType) {
      /*jshint regexp:false*/
      acceptedType = acceptedTypes.join(', ').replace(/,([^,]+)$/, ', or$1');
    }

    var vowel = acceptedType.match(/^[aeiou]/i) ? 'n' : '';
    this.fail('InvalidParameterType', 'Expected ' + context + ' to be a' +
              vowel + ' ' + acceptedType);
  },

  validatePayload: function validatePayload(context, value) {
    if (typeof value === 'string') return;
    if (value && typeof value.byteLength === 'number') return; // typed arrays
    if (AWS.util.isNode()) { // special check for buffer/stream in Node.js
      var Stream = require('stream').Stream;
      if (value instanceof Buffer || value instanceof Stream) return;
    }

    var types = ['Buffer', 'Stream', 'File', 'Blob', 'ArrayBuffer', 'DataView'];
    if (value) {
      for (var i = 0; i < types.length; i++) {
        if (AWS.util.isType(value, types[i])) return;
        if (AWS.util.typeName(value.constructor) === types[i]) return;
      }
    }

    this.fail('InvalidParameterType', 'Expected ' + context + ' to be a ' +
      'string, Buffer, Stream, Blob, or typed array object');
  }
});
<|endoftext|>


<commit_before>var  _ = require('underscore')
  , logging = require('./logging')
  , uuid = require('uuid')
  , util = require('util')
  , common = require('./common')
  // FIXME: rename serialize to serializer
  , Serializer = require('./serialize')
  , key = common.redisKey
  , stringifyOrNull = common.stringifyOrNull
  , parseOrNull = common.parseOrNull
  , redis = require('./redis')
  , Task = require('./task')
  , LinkableMixin = require('./linkable')
  , Runner = require('./runner')
  //, Canvas = require('./chord')
  , DeferredResult = require('./deferredresult')
  , argumentsToArray = require('./common').argumentsToArray
  , EventEmitter2 = require('eventemitter2').EventEmitter2
;

// TODO: Create a global Command registry so we can store command id's as
// callbacks and fetch them on demand when saving.

// TODO: make saving more efficient, right now we always re-save the whole hash
// TODO: use Redis' MULTI command to make things more transaction-safe

// Singleton pattern to avoid having multiple CommandQueue's
var enqueue = (function() {
  var cq;

  return function(command) {
    if(!cq) {
      // FIXME: Maybe use a singleton for CommandQueue instead
      var CommandQueue = require('./commandqueue');
      cq = new CommandQueue();
    }
    return cq.enqueue(command);
  };
}());

var Command = function Command(task) {

  EventEmitter2.call(this);

  var logger = logging.getLogger('Parsley.Task');
  this.log = _.bind(logger.log, logger);

  if(task) {
    this.task = Task.make(task);
    this.name = this.task.name;

    this.id = uuid.v4().toString();
    this.status = 'idle';
    this.addArguments(argumentsToArray(arguments).slice(1));

    // FIXME: here, really?
    this.callbacks = this.callbacks || [];
    this.error_callbacks = this.error_callbacks || [];
  }

  this.serializer = new Serializer(this.serializationMap);

  this.redis = redis.get();
};

util.inherits(Command, EventEmitter2);

_.extend(Command.prototype, LinkableMixin, {
  addArguments: function(args) {
    args = argumentsToArray(args || []);
    this.arguments = (this.arguments || []).concat(args);
  },

  dispatch: function() {
    return enqueue(this);
  },

  clone: function() {
    // TODO: Create the same task but with a new id
  },

  /*
   * Invokes this command - used to run tasks in the worker, or for
   * immediate execution on client.
   */

  run: function() {
    var self = this;
    var result = new DeferredResult(this.id);

    var args = util.format(this.arguments);
    this.log('info', 'running command %s with arguments: %s', common.shortId(this.id), args);

    Runner.run(this.task.fn, this, this.arguments, function(err, ret) {

      if(err) {
        this.error = err;
        this.log('error', 'task %s failed with error: %s', common.shortId(this.id), this.error);
        this.status = 'failed';
        this._dispatchErrorCallbacks(ret);
      } else {
        this.status = 'finished';
        this._dispatchCallbacks(ret);
      }

      this.emit('finished', this);
      this.result = ret;
      this.save();

      // FIXME: what to do with the commandset when one task fails?
      if(this.commandset) {
        CommandSet.finishCommand(this.commandset, this);
      }

      // FIXME: The way we serialize the error for sending it of to
      // DeferredResult is sooo nasty. Basically, we want the error serialized
      // when sending to the pub/sub channel. Come up with a clearer data
      // format for the pub/sub instead.

      var error;
      if(this.error) {
        error = JSON.parse(this.serializer.serializeOne('error', this.error));
      }
      result.publish(error, ret);

    }.bind(this));

    return this;
  },

  get: function(cb) {
    var result = new DeferredResult(this.id);
    return result.get(cb);
  },

  key: function() {
    return key('command', this.id);
  },

  serializationMap: {
    'id'              : 'normal',
    'status'          : 'normal',
    'arguments'       : 'json',
    'result'          : 'json',
    'error'           : 'exception',
    'arguments'       : 'json',
    'callbacks'       : 'commands',
    'error_callbacks' : 'commands',

    // FIXME: register these as serializers in the top instead, so they can be reused.
    'commandset': {
      'serialize': function(commandset) {
        return commandset.id ? commandset.id : commandset;
      },
      'deserialize': function(val) { return val; }
    },

    'task': {
      'serialize': function(task) {
        return JSON.stringify(task.serialize());
      },
      'deserialize': function(val) {
        return Task.deserialize(JSON.parse(val));
      }
    }
  }

});

///////
// .fetch() and .save() dual methods

Command.prototype.save = function(callback) {
  var key = this.key();
  var data = this.serializer.serialize(this);

  this._saveCallbacks();
  this.redis.hmset(key, data, callback || function() {});

  this.log('debug', 'saved command %s', common.shortId(this.id));

  return this;
};

// FIXME: put these in a separate directory for Lua files instead
redis.registerScript('fetch-command',
  'local commandData = redis.call("HGETALL", KEYS[1])\n' +
  'local commandSetId = redis.call("HGET", KEYS[1], "commandset")\n' +
  'local commandSet\n' +
  'if commandSetId then\n' +
    'commandSet = redis.call("HGETALL", "parsley:commandset:"..commandSetId)\n' +
  'end\n' +
  'return { commandData, commandSet }\n'
);

Command.prototype.fetch = function(id, callback) {
  var r = redis.get();
  var _key = key('command', id);
  var self = this;

  redis.runScript('fetch-command', 1, [ _key ], function(err, data) {
    if(err) return callback(err);

    var serialized = {
      'command'    : redis.replyToObject(data[0]),
      'commandset' : data[1] ? redis.replyToObject(data[1]) : null
    };

    _.extend(self, self.serializer.deserialize(serialized['command']));

    self.log('debug', 'fetched task %s(%s)', common.shortId(self.id), self.task.name);

    callback(null, self);

    // TODO: add the commandset to the command as well.

  });

};

Command.make = function(obj) {
  var command;
  if(obj instanceof Task) {
    command = new Command(obj);
  } else if(_.isFunction(obj)) {
    command = new Command(new Task(obj));
  } else {
    // FIXME: might not be a good idea. Problem is that instanceof doesn't work
    // with hiearchies.
    command = obj;
  }

  return command;
}

// Children can be functions, tasks, or arrays
var CommandSet = function(children) {

  _.bindAll(this, 'addCommand');

  // Make a chain of all tasks

  this.id = uuid.v4().toString();
  this.n_completed = 0;
  _.each(children || [], this.addCommand);

  var logger = logging.getLogger('Parsley.CommandSet');
  logger.extend(this);

  this.redis = redis.get();
  this.listener = redis.getListener();
};

_.extend(CommandSet.prototype, LinkableMixin, {

  addCommand: function(child) {
    var command = Command.make(child);
    command.commandset = this;

    if(!this.commands) this.commands = [];
    this.commands.push(command);
    this.n_total = this.commands.length;
  },

  key: function() {
    return key('commandset', this.id)
  },

  get: function(cb) {
    var command = new Command(function(results) {
      return results;
    });
    this.link(command);
    command.get(cb);
    return this;
  },

  _finishCommand: function(id) {

  },

  // FIXME: this could be done more efficient if we stored the results in the commandset instead
  _collectResults: function(cb) {
    var ids = this.commands;

    var multi = this.redis.multi();
    multi = _.reduce(ids, function(multi, commandId) {
      var _key = key('command', commandId);
      return multi.hget(_key, 'result');
    }, multi);

    multi.exec(function(err, results) {
      if(err) return cb(err);
      results = _.object(ids, results);
      cb(null, results);
    });

  },

  _finish: function() {
    var self = this;

    this.log('info', 'commandset %s finished', this.id);

    this._collectResults(function(err, results) {
      if(err) {
        self.log('error', 'unable to collect results from tasks in chord: %s', err);
        return;
      }
      self._dispatchCallbacks(results);
    });
  }

});

var CommandSetSerializer = new Serializer({
  'id'              : 'normal',
  'n_total'         : 'normal',
  'n_completed'     : 'normal',
  'callbacks'       : 'commands',
  'error_callbacks' : 'commands',
  'commands'        : 'commands'
});

///////
// .fetch() and .save() dual methods

CommandSet.prototype.save = function(callback) {
  var key = this.key(),
      id = this.id,
      data = CommandSetSerializer.serialize(this),
      multi = this.redis.multi();

  this._saveCallbacks();

  _.reduce(this.commands, function(multi, command) {
    return multi.hset(command.key(), 'commandset', id);
  }, multi);

  multi
    .hmset(key, data)
    .exec(callback || function() {});

  this.log('debug', 'saved commandset id', common.shortId(key));

  return this;
};

CommandSet.fetch = function(id, callback) {
  var r = redis.get();
  var _key = key('commandset', id);

  r.hgetall(_key, function(err, data) {
    if(err) return callback(err);

    var commandset = new CommandSet();
    _.extend(commandset, CommandSetSerializer.deserialize(data));
    callback(null, commandset);
  });

};

// Performs a Redis operation that increases n_completed AND returns the new
// value of n_completed and n_total. This is atomic, so we can be sure nothing
// happened in between. This is used to atomically determine whether the
// CommandSet was completed or not.
//
// @id       : id of commandset
// @callback : function(err, n_total, n_completed)
//
var incrementAndGetCounts = function(id, callback) {

  var luaTemplate = _.template('\
local key = "<%= key %>" \
local n_total = tonumber(redis.call("HGET", key, "n_total")) \
local n_completed = tonumber(redis.call("HINCRBY", key, "n_completed", 1)) \
return { n_total, n_completed } \
');

  redis.get().eval(luaTemplate({ key: id }), 0, function(err, ret) {
    if(err) return callback(err);

    var n_total = ret[0] || 0,
        n_completed = ret[1] || 0;
    callback(null, n_total, n_completed);
  });

};

CommandSet.key = function(id) { return key('commandset', id); };

var logger = logging.getLogger('Parsley.CommandSet');

CommandSet.finishCommand = function(commandSetId, command) {

  logger.log('debug', 'marking command %s as finished in commandset %s',
             common.shortId(command.id), common.shortId(commandSetId));

  var key = CommandSet.key(commandSetId);
  incrementAndGetCounts(key, function(err, n_total, n_completed) {

    if(n_total === 0) {
      logger.log('error', 'commandset %s is empty', common.shortId(commandSetId));
      return;
    }

    logger.log('debug', 'commandset %s has n_total=%d and n_completed=%d',
               common.shortId(commandSetId), n_total, n_completed);

    // CommandSet is not finished yet
    if (n_completed === n_total) {
      logger.log('info', 'commandset %s finished all %d commands', common.shortId(commandSetId), n_total);
      CommandSet.fetch(commandSetId, function(err, commandset) {
        commandset._finish();
      });
    }

    if(n_completed > n_total) {
      throw new Error('n_completed > n_total for commandset ' + commandSetId);
    }

  })
};

module.exports = {
  Command: Command,
  CommandSet: CommandSet,
  incrementAndGetCounts: incrementAndGetCounts
};
<commit_msg>Assorted modifications to make API more consistent<commit_after>var  _ = require('underscore')
  , logging = require('./logging')
  , uuid = require('uuid')
  , util = require('util')
  , common = require('./common')
  // FIXME: rename serialize to serializer
  , Serializer = require('./serialize')
  , key = common.redisKey
  , stringifyOrNull = common.stringifyOrNull
  , parseOrNull = common.parseOrNull
  , redis = require('./redis')
  , Task = require('./task')
  , LinkableMixin = require('./linkable')
  , Runner = require('./runner')
  //, Canvas = require('./chord')
  , DeferredResult = require('./deferredresult')
  , argumentsToArray = require('./common').argumentsToArray
  , EventEmitter2 = require('eventemitter2').EventEmitter2
;

// TODO: Create a global Command registry so we can store command id's as
// callbacks and fetch them on demand when saving.

// TODO: make saving more efficient, right now we always re-save the whole hash
// TODO: use Redis' MULTI command to make things more transaction-safe

// Singleton pattern to avoid having multiple CommandQueue's
var enqueue = (function() {
  var cq;

  return function(command) {
    if(!cq) {
      // FIXME: Maybe use a singleton for CommandQueue instead
      var CommandQueue = require('./commandqueue');
      cq = new CommandQueue();
    }
    return cq.enqueue(command);
  };
}());

var Command = function Command(task) {

  EventEmitter2.call(this);

  var logger = logging.getLogger('Parsley.Task');
  this.log = _.bind(logger.log, logger);

  this.id = uuid.v4().toString();

  if(task) {
    this.task = Task.make(task);
    this.name = this.task.name;

    this.status = 'idle';
    this.addArguments(argumentsToArray(arguments).slice(1));

    // FIXME: here, really?
    this.callbacks = this.callbacks || [];
    this.error_callbacks = this.error_callbacks || [];
  }

  this.serializer = new Serializer(this.serializationMap);

  this.redis = redis.get();
};

util.inherits(Command, EventEmitter2);

_.extend(Command.prototype, LinkableMixin, {
  addArguments: function(args) {
    args = argumentsToArray(args || []);
    this.arguments = (this.arguments || []).concat(args);
    return this;
  },

  dispatch: function() {
    var args = argumentsToArray(arguments);
    this.addArguments(args);
    this.emit('dispatch', this);
    enqueue(this);
    return this;
  },

  clone: function() {
    var s = this.serializer;;
    var data = s.deserialize(s.serialize(this));
    delete data.id;
    var command = new Command();
    _.extend(command, data);
    return command;
  },

  /*
   * Invokes this command - used to run tasks in the worker, or for
   * immediate execution on client.
   */

  run: function() {
    var self = this;
    var result = new DeferredResult(this.id);

    var args = util.format(this.arguments);
    this.log('info', 'running command %s with arguments: %s', common.shortId(this.id), args);

    this.emit('run:before', this);
    Runner.run(this.task.fn, this, this.arguments, function(err, ret) {

      if(err) {
        this.error = err;
        this.log('error', 'task %s failed with error: %s', common.shortId(this.id), this.error);
        this.status = 'failed';
        this._dispatchErrorCallbacks(ret);
      } else {
        this.status = 'finished';
        this._dispatchCallbacks(ret);
      }

      this.emit('finished', this);
      this.result = ret;
      this.save();

      // FIXME: what to do with the commandset when one task fails?
      if(this.commandset) {
        CommandSet.finishCommand(this.commandset, this);
      }

      // FIXME: The way we serialize the error for sending it of to
      // DeferredResult is sooo nasty. Basically, we want the error serialized
      // when sending to the pub/sub channel. Come up with a clearer data
      // format for the pub/sub instead.

      var error;
      if(this.error) {
        error = JSON.parse(this.serializer.serializeOne('error', this.error));
      }
      result.publish(error, ret);

    }.bind(this));

    return this;
  },

  get: function(cb) {
    var result = new DeferredResult(this.id);
    return result.get(cb);
  },

  key: function() {
    return key('command', this.id);
  },

  serializationMap: {
    'id'              : 'normal',
    'status'          : 'normal',
    'arguments'       : 'json',
    'result'          : 'json',
    'error'           : 'exception',
    'arguments'       : 'json',
    'callbacks'       : 'commands',
    'error_callbacks' : 'commands',

    // FIXME: register these as serializers in the top instead, so they can be reused.
    'commandset': {
      'serialize': function(commandset) {
        return commandset.id ? commandset.id : commandset;
      },
      'deserialize': function(val) { return val; }
    },

    'task': {
      'serialize': function(task) {
        return JSON.stringify(task.serialize());
      },
      'deserialize': function(val) {
        return Task.deserialize(JSON.parse(val));
      }
    }
  }

});

///////
// .fetch() and .save() dual methods

Command.prototype.save = function(callback) {
  var key = this.key();
  var data = this.serializer.serialize(this);

  this._saveCallbacks();
  this.redis.hmset(key, data, callback || function() {});

  this.log('debug', 'saved command %s', common.shortId(this.id));

  return this;
};

// FIXME: put these in a separate directory for Lua files instead
redis.registerScript('fetch-command',
  'local commandData = redis.call("HGETALL", KEYS[1])\n' +
  'local commandSetId = redis.call("HGET", KEYS[1], "commandset")\n' +
  'local commandSet\n' +
  'if commandSetId then\n' +
    'commandSet = redis.call("HGETALL", "parsley:commandset:"..commandSetId)\n' +
  'end\n' +
  'return { commandData, commandSet }\n'
);

Command.prototype.fetch = function(id, callback) {
  var r = redis.get();
  var _key = key('command', id);
  var self = this;

  redis.runScript('fetch-command', 1, [ _key ], function(err, data) {
    if(err) return callback(err);

    var serialized = {
      'command'    : redis.replyToObject(data[0]),
      'commandset' : data[1] ? redis.replyToObject(data[1]) : null
    };

    _.extend(self, self.serializer.deserialize(serialized['command']));

    self.log('debug', 'fetched task %s(%s)', common.shortId(self.id), self.task.name);

    callback(null, self);

    // TODO: add the commandset to the command as well.

  });

};

Command.make = function(obj) {
  var command;
  if(obj instanceof Task) {
    command = new Command(obj);
  } else if(_.isFunction(obj)) {
    command = new Command(new Task(obj));
  } else {
    // FIXME: might not be a good idea. Problem is that instanceof doesn't work
    // with hiearchies.
    command = obj;
  }

  return command;
}

// Children can be functions, tasks, or arrays
var CommandSet = function(children) {

  _.bindAll(this, 'addCommand');

  // Make a chain of all tasks

  this.id = uuid.v4().toString();
  this.n_completed = 0;
  _.each(children || [], this.addCommand);

  var logger = logging.getLogger('Parsley.CommandSet');
  logger.extend(this);

  this.redis = redis.get();
  this.listener = redis.getListener();
};

_.extend(CommandSet.prototype, LinkableMixin, {

  addCommand: function(child) {
    var command = Command.make(child);
    command.commandset = this;

    if(!this.commands) this.commands = [];
    this.commands.push(command);
    this.n_total = this.commands.length;
  },

  key: function() {
    return key('commandset', this.id)
  },

  get: function(cb) {
    var command = new Command(function(results) {
      return results;
    });
    this.link(command);
    command.get(cb);
    return this;
  },

  _finishCommand: function(id) {

  },

  // FIXME: this could be done more efficient if we stored the results in the commandset instead
  _collectResults: function(cb) {
    var ids = this.commands;

    var multi = this.redis.multi();
    multi = _.reduce(ids, function(multi, commandId) {
      var _key = key('command', commandId);
      return multi.hget(_key, 'result');
    }, multi);

    multi.exec(function(err, results) {
      if(err) return cb(err);
      results = _.object(ids, results);
      cb(null, results);
    });

  },

  _finish: function() {
    var self = this;

    this.log('info', 'commandset %s finished', this.id);

    this._collectResults(function(err, results) {
      if(err) {
        self.log('error', 'unable to collect results from tasks in chord: %s', err);
        return;
      }

      self._dispatchCallbacks(results);
    });
  }

});

var CommandSetSerializer = new Serializer({
  'id'              : 'normal',
  'n_total'         : 'normal',
  'n_completed'     : 'normal',
  'callbacks'       : 'commands',
  'error_callbacks' : 'commands',
  'commands'        : 'commands'
});

///////
// .fetch() and .save() dual methods

CommandSet.prototype.save = function(callback) {
  var key = this.key(),
      id = this.id,
      data = CommandSetSerializer.serialize(this),
      multi = this.redis.multi();

  this._saveCallbacks();

  _.reduce(this.commands, function(multi, command) {
    return multi.hset(command.key(), 'commandset', id);
  }, multi);

  multi
    .hmset(key, data)
    .exec(callback || function() {});

  this.log('debug', 'saved commandset id', common.shortId(key));

  return this;
};

CommandSet.fetch = function(id, callback) {
  var r = redis.get();
  var _key = key('commandset', id);

  r.hgetall(_key, function(err, data) {
    if(err) return callback(err);

    var commandset = new CommandSet();
    _.extend(commandset, CommandSetSerializer.deserialize(data));
    callback(null, commandset);
  });

};

// Performs a Redis operation that increases n_completed AND returns the new
// value of n_completed and n_total. This is atomic, so we can be sure nothing
// happened in between. This is used to atomically determine whether the
// CommandSet was completed or not.
//
// @id       : id of commandset
// @callback : function(err, n_total, n_completed)
//
var incrementAndGetCounts = function(id, callback) {

  var luaTemplate = _.template('\
local key = "<%= key %>" \
local n_total = tonumber(redis.call("HGET", key, "n_total")) \
local n_completed = tonumber(redis.call("HINCRBY", key, "n_completed", 1)) \
return { n_total, n_completed } \
');

  redis.get().eval(luaTemplate({ key: id }), 0, function(err, ret) {
    if(err) return callback(err);

    var n_total = ret[0] || 0,
        n_completed = ret[1] || 0;
    callback(null, n_total, n_completed);
  });

};

CommandSet.key = function(id) { return key('commandset', id); };

var logger = logging.getLogger('Parsley.CommandSet');

CommandSet.finishCommand = function(commandSetId, command) {

  logger.log('debug', 'marking command %s as finished in commandset %s',
             common.shortId(command.id), common.shortId(commandSetId));

  var key = CommandSet.key(commandSetId);
  incrementAndGetCounts(key, function(err, n_total, n_completed) {

    if(n_total === 0) {
      logger.log('error', 'commandset %s is empty', common.shortId(commandSetId));
      return;
    }

    logger.log('debug', 'commandset %s has n_total=%d and n_completed=%d',
               common.shortId(commandSetId), n_total, n_completed);

    // CommandSet is not finished yet
    if (n_completed === n_total) {
      logger.log('info', 'commandset %s finished all %d commands', common.shortId(commandSetId), n_total);
      CommandSet.fetch(commandSetId, function(err, commandset) {
        commandset._finish();
      });
    }

    if(n_completed > n_total) {
      throw new Error('n_completed > n_total for commandset ' + commandSetId);
    }

  })
};

module.exports = {
  Command: Command,
  CommandSet: CommandSet,
  incrementAndGetCounts: incrementAndGetCounts
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');
var levels = require('../levels');
var timeago = require('./timeago')();

function init() {

  var openaps = {
    name: 'openaps'
    , label: 'OpenAPS'
    , pluginType: 'pill-status'
  };

  openaps.getPrefs = function getPrefs(sbx) {
    return {
      warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30,
      urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60,
      enableAlerts: sbx.extendedSettings.enableAlerts
    };
  };

  openaps.setProperties = function setProperties (sbx) {
    sbx.offerProperty('openaps', function setOpenAPS ( ) {
      return openaps.analyzeData(sbx);
    });
  };

  openaps.analyzeData = function analyzeData (sbx) {
    var recentHours = 6; //TODO dia*2
    var recentMills = sbx.time - times.hours(recentHours).msecs;

    var recentData = _.filter(sbx.data.devicestatus, function (status) {
      return ('openaps' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills;
    });

    var prefs = openaps.getPrefs(sbx);
    var recent = moment(sbx.time).subtract(prefs.warn / 2, 'minutes');

    var result = {
      seenDevices: {}
      , lastEnacted: null
      , lastNotEnacted: null
      , lastSuggested: null
      , lastIOB: null
      , lastMMTune: null
    };

    function getDevice(status) {
      var uri = status.device || 'device';
      var device = result.seenDevices[uri];

      if (!device) {
        device = {
          name: uri.indexOf('openaps://') === 0 ? uri.substring('openaps://'.length) : uri
          , uri: uri
        };

        result.seenDevices[uri] = device;
      }
      return device;
    }

    function toMoments (status) {
      return {
        when:  moment(status.mills)
        , enacted: status.openaps.enacted && status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , notEnacted: status.openaps.enacted && !status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , suggested: status.openaps.suggested && moment(status.openaps.suggested.timestamp)
        , iob: status.openaps.iob && moment(status.openaps.iob.timestamp)
      };
    }

    function momentsToLoopStatus (moments, noWarning)  {

      var status = {
        symbol: '⚠'
        , code: 'warning'
        , label: 'Warning'
      };

      if (moments.notEnacted && moments.notEnacted.isAfter(recent)) {
        status.symbol = 'x';
        status.code = 'notenacted';
        status.label = 'Not Enacted';
      } else if (moments.enacted && moments.enacted.isAfter(recent)) {
        status.symbol = '⌁';
        status.code = 'enacted';
        status.label = 'Enacted';
      } else if (moments.suggested && moments.suggested.isAfter(recent)) {
        status.symbol = '↻';
        status.code = 'looping';
        status.label = 'Looping';
      } else if (moments.when && (noWarning || moments.when.isAfter(recent))) {
        status.symbol = '◉';
        status.code = 'waiting';
        status.label = 'Waiting';
      }

      return status;
    }

    _.forEach(recentData, function eachStatus (status) {
      var device = getDevice(status);

      var moments = toMoments(status);
      var loopStatus = momentsToLoopStatus(moments, true);

      if (!device.status || moments.when.isAfter(device.status.when)) {
        device.status = loopStatus;
        device.status.when = moments.when;
      }

      var enacted = status.openaps && status.openaps.enacted;
      if (enacted && moments.enacted && (!result.lastEnacted || moments.enacted.isAfter(result.lastEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastEnacted = enacted;
      }

      if (enacted && moments.notEnacted && (!result.lastNotEnacted || moments.notEnacted.isAfter(result.lastNotEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastNotEnacted = enacted;
      }

      var suggested = status.openaps && status.openaps.suggested;
      if (suggested && moments.suggested && (!result.lastSuggested || moments.suggested.isAfter(result.lastSuggested.moment))) {
        suggested.moment = moment(suggested.timestamp);
        result.lastSuggested = suggested;
      }

      var iob = status.openaps && status.openaps.iob;
      if (moments.iob && (!result.lastIOB || moments.iob.isAfter(result.lastIOB.moment))) {
        iob.moment = moments.iob;
        result.lastIOB = iob;
      }

      if (status.mmtune && status.mmtune.timestamp) {
        status.mmtune.moment = moment(status.mmtune.timestamp);
        if (!result.lastMMTune || status.mmtune.moment.isAfter(result.lastMMTune.moment)) {
          result.lastMMTune = status.mmtune;
        }
      }
    });

    result.lastLoopMoment = result.lastEnacted && result.lastSuggested ? moment.max(result.lastEnacted.moment, result.lastSuggested.moment) : result.lastEnacted && result.lastEnacted.moment || result.lastSuggested && result.lastSuggested.moment;

    result.status = momentsToLoopStatus({
      enacted: result.lastEnacted && result.lastEnacted.moment
      , notEnacted: result.lastNotEnacted && result.lastNotEnacted.moment
      , suggested: result.lastSuggested && result.lastSuggested.moment
    }, false, recent);

    return result;
  };

  openaps.getEventTypes = function getEventTypes ( ) {
    return [
      {
        val: 'OpenAPS Offline'
        , name: 'OpenAPS Offline'
        , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false
      }
    ];
  };

  openaps.checkNotifications = function checkNotifications(sbx) {
    var prefs = openaps.getPrefs(sbx);

    if (!prefs.enableAlerts) { return; }

    var prop = sbx.properties.openaps;

    if (!prop.lastLoopMoment) {
      console.info('OpenAPS hasn\'t reported a loop yet');
      return;
    }

    var now = moment();
    var level = statusLevel(prop, prefs, sbx);
    if (level >= levels.WARN) {
      sbx.notifications.requestNotify({
        level: level
        , title: 'OpenAPS isn\'t looping'
        , message: 'Last Loop: ' + formatAgo(prop.lastLoopMoment, now.valueOf())
        , pushoverSound: 'echo'
        , plugin: openaps
        , debug: prop
      });
    }
  };

  openaps.findOfflineMarker = function findOfflineMarker(sbx) {
    return _.findLast(sbx.data.treatments, function match(treatment) {
      var eventTime = sbx.entryMills(treatment);
      var eventEnd = treatment.duration ? eventTime + times.mins(treatment.duration).msecs : eventTime;
      return eventTime <= sbx.time && treatment.eventType === 'OpenAPS Offline' && eventEnd >= sbx.time;
    });
  };

  openaps.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.openaps;

    var prefs = openaps.getPrefs(sbx);

    function valueString (prefix, value) {
      return value ? prefix + value : '';
    }

    var events = [ ];

    function addSuggestion() {
      if (prop.lastSuggested) {

        var valueParts = [
          valueString('BG: ', prop.lastSuggested.bg)
          , valueString(', ', prop.lastSuggested.reason)
          , prop.lastSuggested.mealAssist ? ' Meal Assist: ' + prop.lastSuggested.mealAssist : ''
        ];

        valueParts = concatIOB(valueParts);

        events.push({
          time: prop.lastSuggested.moment
          , value: valueParts.join('')
        });
      }
    }

    function concatIOB (valueParts) {
      if (prop.lastIOB) {
        var bolussnooze = prop.lastIOB.bolussnooze || prop.lastIOB.bolusiob;
        valueParts = valueParts.concat([
          ' IOB: '
          , sbx.roundInsulinForDisplayFormat(prop.lastIOB.iob) + 'U'
          , prop.lastIOB.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(prop.lastIOB.basaliob) + 'U' : ''
          , bolussnooze ? ', Bolus Snooze ' + sbx.roundInsulinForDisplayFormat(bolussnooze) + 'U' : ''
        ]);
      }

      return valueParts;
    }

    if ('enacted' === prop.status.code) {
      var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0;

      var valueParts = [
        valueString('BG: ', prop.lastEnacted.bg)
        , ', Temp Basal' + (canceled ? ' Canceled' : ' Started') + ''
        , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + ' for ' + prop.lastEnacted.duration + 'm'
        , valueString(', ', prop.lastEnacted.reason)
        , prop.lastEnacted.mealAssist ? ' Meal Assist: ' + prop.lastEnacted.mealAssist : ''
      ];

      if (prop.lastSuggested && prop.lastSuggested.moment.isAfter(prop.lastEnacted.moment)) {
        addSuggestion();
      } else {
        valueParts = concatIOB(valueParts);
      }

      events.push({
        time: prop.lastEnacted.moment
        , value: valueParts.join('')
      });
    } else {
      addSuggestion();
    }

    _.forIn(prop.seenDevices, function seenDevice (device) {
      events.push({
        time: device.status.when
        , value: ['update from', device.name, device.status.symbol, device.status.label].join(' ')
      });
    });

    var sorted = _.sortBy(events, function toMill(event) {
      return event.time.valueOf();
    }).reverse();

    var info = _.map(sorted, function eventToInfo (event) {
      return {
        label: timeAt(false, sbx) + timeFormat(event.time, sbx)
        , value: event.value
      };
    });

    if (prop.lastMMTune) {
      var best = _.max(prop.lastMMTune.scanDetails, function (d) {
        return d[2];
      });

      var mmtuneDetails = [prop.lastMMTune.setFreq, 'MHz'];

      if (best && best.length > 2) {
        mmtuneDetails = mmtuneDetails.concat([' @ ', best[2] + 'dB']);
      }

      info.push({label: 'mmtune', value: mmtuneDetails.join('')});
    }

    sbx.pluginBase.updatePillText(openaps, {
      value: timeFormat(prop.lastLoopMoment, sbx)
      , label: 'OpenAPS ' + prop.status.symbol
      , info: info
      , pillClass: statusClass(prop, prefs, sbx)
    });
  };

  function statusClass (prop, prefs, sbx) {
    var level = statusLevel(prop, prefs, sbx);
    var cls = 'current';

    if (level === levels.WARN) {
      cls = 'warn';
    } else if (level === levels.URGENT) {
      cls = 'urgent';
    }

    return cls;
  }

  function statusLevel (prop, prefs, sbx) {
    var level = levels.NONE;
    var now = moment(sbx.time);

    if (openaps.findOfflineMarker(sbx)) {
      console.info('OpenAPS known offline, not checking for alerts');
    } else if (prop.lastLoopMoment) {
      var urgentTime = prop.lastLoopMoment.clone().add(prefs.urgent, 'minutes');
      var warningTime = prop.lastLoopMoment.clone().add(prefs.warn, 'minutes');

      if (urgentTime.isBefore(now)) {
        level = levels.URGENT;
      } else if (warningTime.isBefore(now)) {
        level = levels.WARN;
      }
    }

    return level;
  }

  return openaps;

}

function timeFormat (m, sbx) {

  var when;
  if (m && sbx.data.inRetroMode) {
    when = m.format('LT');
  } else if (m) {
    when = formatAgo(m, sbx.time);
  } else {
    when = 'unknown';
  }

  return when;
}

function formatAgo (m, nowMills) {
  var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
  return (ago.value ? ago.value : '') + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : '');
}

function timeAt (prefix, sbx) {
  return sbx.data.inRetroMode ? (prefix ? ' ' : '') + '@ ' : (prefix ? ', ' : '');
}

module.exports = init;<commit_msg>move mmtune into device status so it works with multiple rigs<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');
var times = require('../times');
var levels = require('../levels');
var timeago = require('./timeago')();

function init() {

  var openaps = {
    name: 'openaps'
    , label: 'OpenAPS'
    , pluginType: 'pill-status'
  };

  openaps.getPrefs = function getPrefs(sbx) {
    return {
      warn: sbx.extendedSettings.warn ? sbx.extendedSettings.warn : 30,
      urgent: sbx.extendedSettings.urgent ? sbx.extendedSettings.urgent : 60,
      enableAlerts: sbx.extendedSettings.enableAlerts
    };
  };

  openaps.setProperties = function setProperties (sbx) {
    sbx.offerProperty('openaps', function setOpenAPS ( ) {
      return openaps.analyzeData(sbx);
    });
  };

  openaps.analyzeData = function analyzeData (sbx) {
    var recentHours = 6; //TODO dia*2
    var recentMills = sbx.time - times.hours(recentHours).msecs;

    var recentData = _.filter(sbx.data.devicestatus, function (status) {
      return ('openaps' in status) && sbx.entryMills(status) <= sbx.time && sbx.entryMills(status) >= recentMills;
    });

    var prefs = openaps.getPrefs(sbx);
    var recent = moment(sbx.time).subtract(prefs.warn / 2, 'minutes');

    var result = {
      seenDevices: {}
      , lastEnacted: null
      , lastNotEnacted: null
      , lastSuggested: null
      , lastIOB: null
      , lastMMTune: null
    };

    function getDevice(status) {
      var uri = status.device || 'device';
      var device = result.seenDevices[uri];

      if (!device) {
        device = {
          name: uri.indexOf('openaps://') === 0 ? uri.substring('openaps://'.length) : uri
          , uri: uri
        };

        result.seenDevices[uri] = device;
      }
      return device;
    }

    function toMoments (status) {
      return {
        when:  moment(status.mills)
        , enacted: status.openaps.enacted && status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , notEnacted: status.openaps.enacted && !status.openaps.enacted.recieved && moment(status.openaps.enacted.timestamp)
        , suggested: status.openaps.suggested && moment(status.openaps.suggested.timestamp)
        , iob: status.openaps.iob && moment(status.openaps.iob.timestamp)
      };
    }

    function momentsToLoopStatus (moments, noWarning)  {

      var status = {
        symbol: '⚠'
        , code: 'warning'
        , label: 'Warning'
      };

      if (moments.notEnacted && moments.notEnacted.isAfter(recent)) {
        status.symbol = 'x';
        status.code = 'notenacted';
        status.label = 'Not Enacted';
      } else if (moments.enacted && moments.enacted.isAfter(recent)) {
        status.symbol = '⌁';
        status.code = 'enacted';
        status.label = 'Enacted';
      } else if (moments.suggested && moments.suggested.isAfter(recent)) {
        status.symbol = '↻';
        status.code = 'looping';
        status.label = 'Looping';
      } else if (moments.when && (noWarning || moments.when.isAfter(recent))) {
        status.symbol = '◉';
        status.code = 'waiting';
        status.label = 'Waiting';
      }

      return status;
    }

    _.forEach(recentData, function eachStatus (status) {
      var device = getDevice(status);

      var moments = toMoments(status);
      var loopStatus = momentsToLoopStatus(moments, true);

      if (!device.status || moments.when.isAfter(device.status.when)) {
        device.status = loopStatus;
        device.status.when = moments.when;
      }

      var enacted = status.openaps && status.openaps.enacted;
      if (enacted && moments.enacted && (!result.lastEnacted || moments.enacted.isAfter(result.lastEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastEnacted = enacted;
      }

      if (enacted && moments.notEnacted && (!result.lastNotEnacted || moments.notEnacted.isAfter(result.lastNotEnacted.moment))) {
        enacted.moment = moment(enacted.timestamp);
        result.lastNotEnacted = enacted;
      }

      var suggested = status.openaps && status.openaps.suggested;
      if (suggested && moments.suggested && (!result.lastSuggested || moments.suggested.isAfter(result.lastSuggested.moment))) {
        suggested.moment = moment(suggested.timestamp);
        result.lastSuggested = suggested;
      }

      var iob = status.openaps && status.openaps.iob;
      if (moments.iob && (!result.lastIOB || moments.iob.isAfter(result.lastIOB.moment))) {
        iob.moment = moments.iob;
        result.lastIOB = iob;
      }

      if (status.mmtune && status.mmtune.timestamp) {
        status.mmtune.moment = moment(status.mmtune.timestamp);
        if (!device.mmtune || moments.when.isAfter(device.mmtune.moment)) {
          device.mmtune = status.mmtune;
        }
      }
    });

    result.lastLoopMoment = result.lastEnacted && result.lastSuggested ? moment.max(result.lastEnacted.moment, result.lastSuggested.moment) : result.lastEnacted && result.lastEnacted.moment || result.lastSuggested && result.lastSuggested.moment;

    result.status = momentsToLoopStatus({
      enacted: result.lastEnacted && result.lastEnacted.moment
      , notEnacted: result.lastNotEnacted && result.lastNotEnacted.moment
      , suggested: result.lastSuggested && result.lastSuggested.moment
    }, false, recent);

    return result;
  };

  openaps.getEventTypes = function getEventTypes ( ) {
    return [
      {
        val: 'OpenAPS Offline'
        , name: 'OpenAPS Offline'
        , bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false, profile: false, split: false
      }
    ];
  };

  openaps.checkNotifications = function checkNotifications(sbx) {
    var prefs = openaps.getPrefs(sbx);

    if (!prefs.enableAlerts) { return; }

    var prop = sbx.properties.openaps;

    if (!prop.lastLoopMoment) {
      console.info('OpenAPS hasn\'t reported a loop yet');
      return;
    }

    var now = moment();
    var level = statusLevel(prop, prefs, sbx);
    if (level >= levels.WARN) {
      sbx.notifications.requestNotify({
        level: level
        , title: 'OpenAPS isn\'t looping'
        , message: 'Last Loop: ' + formatAgo(prop.lastLoopMoment, now.valueOf())
        , pushoverSound: 'echo'
        , plugin: openaps
        , debug: prop
      });
    }
  };

  openaps.findOfflineMarker = function findOfflineMarker(sbx) {
    return _.findLast(sbx.data.treatments, function match(treatment) {
      var eventTime = sbx.entryMills(treatment);
      var eventEnd = treatment.duration ? eventTime + times.mins(treatment.duration).msecs : eventTime;
      return eventTime <= sbx.time && treatment.eventType === 'OpenAPS Offline' && eventEnd >= sbx.time;
    });
  };

  openaps.updateVisualisation = function updateVisualisation (sbx) {
    var prop = sbx.properties.openaps;

    var prefs = openaps.getPrefs(sbx);

    function valueString (prefix, value) {
      return value ? prefix + value : '';
    }

    var events = [ ];

    function addSuggestion() {
      if (prop.lastSuggested) {

        var valueParts = [
          valueString('BG: ', prop.lastSuggested.bg)
          , valueString(', ', prop.lastSuggested.reason)
          , prop.lastSuggested.mealAssist ? ' Meal Assist: ' + prop.lastSuggested.mealAssist : ''
        ];

        valueParts = concatIOB(valueParts);

        events.push({
          time: prop.lastSuggested.moment
          , value: valueParts.join('')
        });
      }
    }

    function concatIOB (valueParts) {
      if (prop.lastIOB) {
        var bolussnooze = prop.lastIOB.bolussnooze || prop.lastIOB.bolusiob;
        valueParts = valueParts.concat([
          ' IOB: '
          , sbx.roundInsulinForDisplayFormat(prop.lastIOB.iob) + 'U'
          , prop.lastIOB.basaliob ? ', Basal IOB ' + sbx.roundInsulinForDisplayFormat(prop.lastIOB.basaliob) + 'U' : ''
          , bolussnooze ? ', Bolus Snooze ' + sbx.roundInsulinForDisplayFormat(bolussnooze) + 'U' : ''
        ]);
      }

      return valueParts;
    }

    if ('enacted' === prop.status.code) {
      var canceled = prop.lastEnacted.rate === 0 && prop.lastEnacted.duration === 0;

      var valueParts = [
        valueString('BG: ', prop.lastEnacted.bg)
        , ', Temp Basal' + (canceled ? ' Canceled' : ' Started') + ''
        , canceled ? '' : ' ' + prop.lastEnacted.rate.toFixed(2) + ' for ' + prop.lastEnacted.duration + 'm'
        , valueString(', ', prop.lastEnacted.reason)
        , prop.lastEnacted.mealAssist ? ' Meal Assist: ' + prop.lastEnacted.mealAssist : ''
      ];

      if (prop.lastSuggested && prop.lastSuggested.moment.isAfter(prop.lastEnacted.moment)) {
        addSuggestion();
      } else {
        valueParts = concatIOB(valueParts);
      }

      events.push({
        time: prop.lastEnacted.moment
        , value: valueParts.join('')
      });
    } else {
      addSuggestion();
    }

    _.forIn(prop.seenDevices, function seenDevice (device) {
      var deviceInfo = [device.name, device.status.symbol, device.status.label];
      if (device.mmtune) {
        var best = _.max(device.mmtune.scanDetails, function (d) {
          return d[2];
        });

        deviceInfo = deviceInfo.concat([device.mmtune.setFreq, 'MHz']);
        if (best && best.length > 2) {
          deviceInfo = deviceInfo.concat([' @ ', best[2] + 'dB']);
        }
      }
      events.push({
        time: device.status.when
        , value: deviceInfo.join(' ')
      });
    });

    var sorted = _.sortBy(events, function toMill(event) {
      return event.time.valueOf();
    }).reverse();

    var info = _.map(sorted, function eventToInfo (event) {
      return {
        label: timeAt(false, sbx) + timeFormat(event.time, sbx)
        , value: event.value
      };
    });

    sbx.pluginBase.updatePillText(openaps, {
      value: timeFormat(prop.lastLoopMoment, sbx)
      , label: 'OpenAPS ' + prop.status.symbol
      , info: info
      , pillClass: statusClass(prop, prefs, sbx)
    });
  };

  function statusClass (prop, prefs, sbx) {
    var level = statusLevel(prop, prefs, sbx);
    var cls = 'current';

    if (level === levels.WARN) {
      cls = 'warn';
    } else if (level === levels.URGENT) {
      cls = 'urgent';
    }

    return cls;
  }

  function statusLevel (prop, prefs, sbx) {
    var level = levels.NONE;
    var now = moment(sbx.time);

    if (openaps.findOfflineMarker(sbx)) {
      console.info('OpenAPS known offline, not checking for alerts');
    } else if (prop.lastLoopMoment) {
      var urgentTime = prop.lastLoopMoment.clone().add(prefs.urgent, 'minutes');
      var warningTime = prop.lastLoopMoment.clone().add(prefs.warn, 'minutes');

      if (urgentTime.isBefore(now)) {
        level = levels.URGENT;
      } else if (warningTime.isBefore(now)) {
        level = levels.WARN;
      }
    }

    return level;
  }

  return openaps;

}

function timeFormat (m, sbx) {

  var when;
  if (m && sbx.data.inRetroMode) {
    when = m.format('LT');
  } else if (m) {
    when = formatAgo(m, sbx.time);
  } else {
    when = 'unknown';
  }

  return when;
}

function formatAgo (m, nowMills) {
  var ago = timeago.calcDisplay({mills: m.valueOf()}, nowMills);
  return (ago.value ? ago.value : '') + ago.shortLabel + (ago.shortLabel.length === 1 ? ' ago' : '');
}

function timeAt (prefix, sbx) {
  return sbx.data.inRetroMode ? (prefix ? ' ' : '') + '@ ' : (prefix ? ', ' : '');
}

module.exports = init;<|endoftext|>


<commit_before>'use strict';

var through = require('through2');
var utils_ = require('utils');
var utils = require('../utils');

/**
 * Expose `process` plugin
 */

module.exports = plugin('update');

function plugin(appname) {
  return function (locals, options) {
    var app = this;
    return function (file, enc, cb) {
      var collection = app.inflections[locals.id];
      var template = app.views[collection][file.id];

      template.content = file.contents.toString();
      var context = utils_.extend({}, locals, file.locals);

      try {
        var stream = this;
        app.render(template, context, function (err, content) {
          if (err) {
            stream.emit('error', new utils.PluginError(appname + '-process', err));
            return cb(err);
          }
          file.contents = new Buffer(content);
          stream.push(file);
          cb();
        });
      } catch (err) {
        this.emit('error', new utils.PluginError(appname + '-process', err));
        return cb();
      }
    };
  };
};
<commit_msg>fix plugin<commit_after>'use strict';

var PluginError = require('plugin-error');
var through = require('through2');
var utils = require('utils');

/**
 * Expose `process` plugin
 */

module.exports = plugin('update');

function plugin(appname) {
  return function (locals) {
    var app = this;
    return through.obj(function (file, enc, cb) {
      var collection = app.inflections[locals.id];
      var template = app.views[collection][file.id];

      template.content = file.contents.toString();
      var context = utils.extend({}, locals, file.locals);

      try {
        var stream = this;
        app.render(template, context, function (err, content) {
          if (err) {
            stream.emit('error', new PluginError(appname + '-process', err));
            return cb(err);
          }
          file.contents = new Buffer(content);
          stream.push(file);
          cb();
        });
      } catch (err) {
        this.emit('error', new PluginError(appname + '-process', err));
        return cb();
      }
    });
  };
}
<|endoftext|>


<commit_before>module.exports = function (xports) {
    return function (resource, content) {
        var glbl = "", args = "", exportStmt = "";
        if (xports && xports.length > 0) {
            glbl = "typeof global != \"undefined\" ? global : this";
            args = "__GLOBAL";
            exportStmt = xports.map(function (variable) {
                return "__GLOBAL." + variable + "=" + variable + ";";
            }).join("");
            exportStmt = !/;\s*$/.test(content) ? ";" + exportStmt : exportStmt;
        }

        return "(function (" + args + ") {" + content + exportStmt + "}(" +
            glbl + "));";
    };
};
<commit_msg>Upfront errors on wrong usage<commit_after>var toString = Object.prototype.toString;

module.exports = function (xports) {
    if (!!xports && toString.call(xports) != "[object Array]") {
        throw new Error("IIFE exports should be an array of names");
    }
    return function (resource, content) {
        var glbl = "", args = "", exportStmt = "";
        if (xports && xports.length > 0) {
            glbl = "typeof global != \"undefined\" ? global : this";
            args = "__GLOBAL";
            exportStmt = xports.map(function (variable) {
                return "__GLOBAL." + variable + "=" + variable + ";";
            }).join("");
            exportStmt = !/;\s*$/.test(content) ? ";" + exportStmt : exportStmt;
        }

        return "(function (" + args + ") {" + content + exportStmt + "}(" +
            glbl + "));";
    };
};
<|endoftext|>


<commit_before>'use strict';

var leb = require('leb');

/**
 * Provide operations for serializing/deserializing integer data into
 * variable-length 64-bit LEB128 integer encoding [1].
 *
 * References:
 * [1] https://en.wikipedia.org/wiki/LEB128
 *
**/
var LEB128 = {

  /**
   * Encode an arbitrarily large positive integer value using a small
   * number of bytes. This function returns a Buffer containing the
   * byte values of the 64-bit LEB128-encoded integer.
  **/
  encode: function (value) {

    if (value < 0) {
      throw new Error('Negative values are not supported');
    }

    return leb.encodeUInt64(value);

  },

  /**
   * Decode a data Buffer containing a 64-bit LEB128-encoded integer.
  **/
  decode: function (data, offset) {

    if (!Buffer.isBuffer(data)) {
      throw new Error('Data to decode must be a Buffer object');
    }

    return leb.decodeUInt64(data, offset ? offset : 0).value;

  },

  /**
   * Decode a data Buffer containing a 64-bit LEB128-encoded integer
   * and return all metadata about the operation
   *
   * The metadata contains the following fields:
   *   value:     The value of the extracted LEB128-encoded integer
   *   nextIndex: The next unseen/unused byte in the input buffer
   *   lossy:     Whether or not the extraction involved loss of precision
   *
   * Example return value: { value: 1, nextIndex: 6, lossy: false }
   **/
  decodeWithMetadata: function (data, offset) {

    if (!Buffer.isBuffer(data)) {
      throw new Error('Data to decode must be a Buffer object');
    }

    return leb.decodeUInt64(data, offset ? offset : 0);
  }

};

// Public API for this object
module.exports = LEB128;
<commit_msg>Clean documentation and spacing issues in OutputType, LEB128<commit_after>/* Copyright (c) 2014, Andrew Hart <hello@andrewfhart.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var leb = require('leb');

/**
 * Provide operations for serializing/deserializing integer data into
 * variable-length 64-bit LEB128 integer encoding [1].
 *
 * References:
 * [1] https://en.wikipedia.org/wiki/LEB128
 *
**/
var LEB128 = {

  /**
   * Encode an arbitrarily large positive integer value using a small
   * number of bytes. This function returns a Buffer containing the
   * byte values of the 64-bit LEB128-encoded integer.
  **/
  encode: function (value) {

    if (value < 0) {
      throw new Error('Negative values are not supported');
    }

    return leb.encodeUInt64(value);

  },

  /**
   * Decode a data Buffer containing a 64-bit LEB128-encoded integer.
  **/
  decode: function (data, offset) {

    if (!Buffer.isBuffer(data)) {
      throw new Error('Data to decode must be a Buffer object');
    }

    return leb.decodeUInt64(data, offset ? offset : 0).value;

  },

  /**
   * Decode a data Buffer containing a 64-bit LEB128-encoded integer
   * and return all metadata about the operation
   *
   * The metadata contains the following fields:
   *   value:     The value of the extracted LEB128-encoded integer
   *   nextIndex: The next unseen/unused byte in the input buffer
   *   lossy:     Whether or not the extraction involved loss of precision
   *
   * Example return value: { value: 1, nextIndex: 6, lossy: false }
   **/
  decodeWithMetadata: function (data, offset) {

    if (!Buffer.isBuffer(data)) {
      throw new Error('Data to decode must be a Buffer object');
    }

    return leb.decodeUInt64(data, offset ? offset : 0);
  }

};

// Public API for this object
module.exports = LEB128;
<|endoftext|>


<commit_before>/**
 * Core widget library, every widget has to be derived from the widget.
 *
 * @octdoc      ui/widget
 * @copyright   copyright (c) 2010-2012 by Harald Lapp
 * @author      Harald Lapp <harald@octris.org>
 */
/**/

;(function() {
    if ('widget' in oui) return;

    var registry = {};      // Global widget registry.

    /**
     * Constructor.
     *
     * @octdoc      widget/
     * @public
     * @param       string      name            Name to set for widget.
     * @param       object      options         Optional options for widget.
     * @return      oui.widget                  Widget instance.
     */
    oui.widget = function(name, options)
    /**/
    {
        /**
         * Main DOM node of widget.
         *
         * @octdoc      widget/node
         * @private
         * @var         DOMElement
         */
        var node = null;
        /**/

        /**
         * Name of widget.
         *
         * @octdoc      widget/name
         * @private
         * @var         string
         */
        var name = name || '';
        /**/

        /**
         * Options of widget.
         *
         * @octdoc      widget/options
         * @private
         * @var         object
         */
        var options = options || {};
        /**/

        /**
         * Whether the widget is disabled.
         *
         * @octdoc      widget/disabled
         * @private
         * @var         bool
         */
        var disabled = false;
        /**/

        /**
         * Child widgets.
         *
         * @octdoc      widget/children
         * @private
         * @var         array
         */
        var children = [];
        /**/

        /**
         * Return name of the widget.
         *
         * @octdoc      widget/getName
         * @public
         * @return      string                  The name of the widget.
         */
        this.getName = function()
        /**/
        {
            return name;
        }

        /**
         * Return main DOM node of the widget.
         *
         * @octdoc      widget/getNode
         * @public
         * @return      DOMElement
         */
        this.getNode = function()
        /**/
        {
            return node;
        }

        /**
         * Return value of a widget.
         *
         * @octdoc      widget/getValue
         * @public
         * @return      null|mixed
         */
        this.getValue = function()
        /**/
        {
            return null;
        }

        /**
         * Set value for a widget.
         *
         * @octdoc      widget/setValue
         * @public
         * @param       mixed           value               Value to set.
         */
        this.setValue = function(value)
        /**/
        {
        }

        /**
         * Enable or disable the widget and all it's child widgets.
         *
         * @octdoc      widget/setEnabled
         * @public
         * @param       bool            enabled             True enables the widget, false disables it.
         */
        this.setEnabled = function(enabled)
        /**/
        {
            if (disabled = !enabled) {
                return;
            }

            if ((disabled = !enabled)) {
                this.onDisable();
            } else {
                this.onEnable();
            }

            for (var i = 0, cnt = children.length; i < cnt; ++i) {
                children[i].setEnabled(enabled);
            }
        }

        /**
         * Initiate building of child widgets.
         *
         * @octdoc  widget/processChildren
         * @param   array           def                 Widget definition.
         * @param   callback        processor           Callback method for processing
         *                                              child widgets.
         * @param   parent          object              Optional parent DOM node.
         */
        this.processChildren = function(def, processor, parent)
        /**/
        {
            var me = this;

            (function process(parent, def) {
                if (!('children' in def)) return;

                var type, child, widget, instance;

                for (var i = 0, cnt = def['children'].length; i < cnt; ++i) {
                    child = null;

                    for (type in def['children'][i]) {
                        child = def['children'][i][type];
                        break;
                    }

                    if (child == null) {
                        // no child element
                        continue;
                    }

                    widget   = registry[type];
                    name     = ('name' in child ? child['name'] : '');
                    instance = me.addChild(new widget.widget(name, widget.options));

                    processor(parent, instance, child);
                }
            })(parent || node, def);
        }

        /**
         * Add child widget
         *
         * @octdoc      widget/addChild
         * @public
         * @param       oui.widget      instance            Instance of a widget.
         * @return      oui.widget                          The widget instance specified as parameter.
         */
        this.addChild = function(instance)
        /**/
        {
            if (!(instance instanceof oui.widget)) {
                throw 'The parameter specified is not an instance of \'oui.widget\'!';
            }

            // TODO: check, if instance is allowed to add (eg.: by checking options [eg.: container must not be
            //       a child of tile])

            var me = this;

            instance.getDialog = function() {
                return me.getDialog();
            }

            instance.getParent = function() {
                return me;
            }

            children.push(instance);

            return instance;
        }

        /**
         * Create widget. This method is normally called from within the attach method to create
         * the widget container.
         *
         * @octdoc      widget/create
         * @public
         * @param       DOMElement      parent              Parent node to create widget in.
         * @param       object          def                 Widget definition.
         * @return      DOMElement                          Node of new created widget.
         */
        this.create = function(parent, def)
        /**/
        {
            node = oui.$(document.createElement(this.container));
            node.addClass(
                this.cssclass + ('class' in def
                                 ? ' ' + def['class']
                                 : '')
            );

            parent.append(node);

            return node;
        }
    }

    /**
     * Default container type of a widget.
     *
     * @octdoc      widget/container
     * @public
     * @var         string
     */
    oui.widget.prototype.container = 'DIV';
    /**/

    /**
     * Default css class of a widget.
     *
     * @octdoc      widget/cssclass
     * @public
     * @var         string
     */
    oui.widget.prototype.cssclass = 'oui_widget';
    /**/

    /**
     * Return dialog, the widget is assigned to. Returns false, if the widget is not assigned
     * to any dialog.
     *
     * @octdoc      widget/getDialog
     * @public
     * @var         bool|oui.dialog
     */
    oui.widget.prototype.getDialog = function()
    /**/
    {
        return false;
    }

    /**
     * Return parent widget.
     *
     * @octdoc      widget/getParent
     * @public
     * @var         bool|oui.widget
     */
    oui.widget.prototype.getParent = function()
    /**/
    {
        return false;
    }

    /**
     * Build a widget and attach it to a parent node.
     *
     * @octdoc      widget/attach
     * @public
     * @param       DOMElement      parent          Parent node to attach widget to.
     * @param       object          def             Widget definitions.
     */
    oui.widget.prototype.attach = function(parent, def)
    /**/
    {
        this.create(parent, def);

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    }

    /**
     * Assimilate a part of a page and try to wrap OUI functionality around page elements.
     *
     * @octdoc      widget/assimilate
     * @public
     * @param       DOMElement      node            Node to assimilate.
     */
    oui.widget.prototype.assimilate = function(node)
    /**/
    {
        var me = this;

        (function _assimilate(node) {
            // TODO: implementation
        })(node);
    }

    /**
     * Add a widget object to the registry.
     *
     * @octdoc      widget/register
     * @static
     * @param       string          name            Name of widget to register.
     * @param       object          widget          Widget object.
     * @param       object          options         Optional options for widget creation.
     */
    oui.widget.register = function(name, widget, options)
    /**/
    {
        if (name in registry) {
            throw 'Widget was registered before!';
        } else if (name == 'widget') {
            throw 'A widget cannot have the name \'widget\'!';
        } else {
            registry[name] = {
                'widget':  widget,
                'options': options || {}
            }
        }
    }

    /*
     * event handlers to be overwritten by children
     */

    /**
     * Event handler get's called, when widget get's enabled
     *
     * @octdoc      widget/onEnable
     * @public
     */
    oui.widget.prototype.onEnable = function()
    /**/
    {
    }

    /**
     * Event handler get's called, when widget get's disabled.
     *
     * @octdoc      widget/onDisable
     * @public
     */
    oui.widget.prototype.onDisable = function()
    /**/
    {
    }

    /**
     * Event handler get's called, after widget was populated with data.
     *
     * @octdoc      widget/onPopulate
     * @public
     */
    oui.widget.prototype.onPopulate = function()
    /**/
    {
    }

    /**
     * Event handler get's called, after widget's value get's updated/changed.
     *
     * @octdoc      widget/onChange
     * @public
     */
    oui.widget.prototype.onChange = function()
    /**/
    {
    }

    /**
     * Event handler get's called, when widget is clicked on.
     *
     * @octdoc      widget/onClick
     * @public
     */
    oui.widget.prototype.onClick = function()
    /**/
    {
    }

    /*
     * static methods
     */

    /**
     * Tests if something is registered with a specified widget name.
     *
     * @octdoc      widget/isRegistered
     * @static
     * @param       string          name            Name of widget to test if it's registered.
     */
    oui.widget.isRegistered = function(name)
    /**/
    {
        return (name in registry);
    }
})();
<commit_msg>omit non-objects, throw an exception if widget type is not registered<commit_after>/**
 * Core widget library, every widget has to be derived from the widget.
 *
 * @octdoc      ui/widget
 * @copyright   copyright (c) 2010-2012 by Harald Lapp
 * @author      Harald Lapp <harald@octris.org>
 */
/**/

;(function() {
    if ('widget' in oui) return;

    var registry = {};      // Global widget registry.

    /**
     * Constructor.
     *
     * @octdoc      widget/
     * @public
     * @param       string      name            Name to set for widget.
     * @param       object      options         Optional options for widget.
     * @return      oui.widget                  Widget instance.
     */
    oui.widget = function(name, options)
    /**/
    {
        /**
         * Main DOM node of widget.
         *
         * @octdoc      widget/node
         * @private
         * @var         DOMElement
         */
        var node = null;
        /**/

        /**
         * Name of widget.
         *
         * @octdoc      widget/name
         * @private
         * @var         string
         */
        var name = name || '';
        /**/

        /**
         * Options of widget.
         *
         * @octdoc      widget/options
         * @private
         * @var         object
         */
        var options = options || {};
        /**/

        /**
         * Whether the widget is disabled.
         *
         * @octdoc      widget/disabled
         * @private
         * @var         bool
         */
        var disabled = false;
        /**/

        /**
         * Child widgets.
         *
         * @octdoc      widget/children
         * @private
         * @var         array
         */
        var children = [];
        /**/

        /**
         * Return name of the widget.
         *
         * @octdoc      widget/getName
         * @public
         * @return      string                  The name of the widget.
         */
        this.getName = function()
        /**/
        {
            return name;
        }

        /**
         * Return main DOM node of the widget.
         *
         * @octdoc      widget/getNode
         * @public
         * @return      DOMElement
         */
        this.getNode = function()
        /**/
        {
            return node;
        }

        /**
         * Return value of a widget.
         *
         * @octdoc      widget/getValue
         * @public
         * @return      null|mixed
         */
        this.getValue = function()
        /**/
        {
            return null;
        }

        /**
         * Set value for a widget.
         *
         * @octdoc      widget/setValue
         * @public
         * @param       mixed           value               Value to set.
         */
        this.setValue = function(value)
        /**/
        {
        }

        /**
         * Enable or disable the widget and all it's child widgets.
         *
         * @octdoc      widget/setEnabled
         * @public
         * @param       bool            enabled             True enables the widget, false disables it.
         */
        this.setEnabled = function(enabled)
        /**/
        {
            if (disabled = !enabled) {
                return;
            }

            if ((disabled = !enabled)) {
                this.onDisable();
            } else {
                this.onEnable();
            }

            for (var i = 0, cnt = children.length; i < cnt; ++i) {
                children[i].setEnabled(enabled);
            }
        }

        /**
         * Initiate building of child widgets.
         *
         * @octdoc  widget/processChildren
         * @param   array           def                 Widget definition.
         * @param   callback        processor           Callback method for processing
         *                                              child widgets.
         * @param   parent          object              Optional parent DOM node.
         */
        this.processChildren = function(def, processor, parent)
        /**/
        {
            var me = this;

            (function process(parent, def) {
                if (!('children' in def)) return;

                var type, child, widget, instance;

                for (var i = 0, cnt = def['children'].length; i < cnt; ++i) {
                    child = null;

                    for (type in def['children'][i]) {
                        child = def['children'][i][type];
                        break;
                    }

                    if (child == null) {
                        // no child element
                        continue;
                    } else if (typeof child != 'object') {
                        // wrong datatype
                        // TODO: output notice
                        continue;
                    }

                    if (!(type in registry)) {
                        throw 'Unknown widget type \'' + type + '\'!';
                    }

                    widget   = registry[type];
                    name     = ('name' in child ? child['name'] : '');
                    instance = me.addChild(new widget.widget(name, widget.options));

                    processor(parent, instance, child);
                }
            })(parent || node, def);
        }

        /**
         * Add child widget
         *
         * @octdoc      widget/addChild
         * @public
         * @param       oui.widget      instance            Instance of a widget.
         * @return      oui.widget                          The widget instance specified as parameter.
         */
        this.addChild = function(instance)
        /**/
        {
            if (!(instance instanceof oui.widget)) {
                throw 'The parameter specified is not an instance of \'oui.widget\'!';
            }

            // TODO: check, if instance is allowed to add (eg.: by checking options [eg.: container must not be
            //       a child of tile])

            var me = this;

            instance.getDialog = function() {
                return me.getDialog();
            }

            instance.getParent = function() {
                return me;
            }

            children.push(instance);

            return instance;
        }

        /**
         * Create widget. This method is normally called from within the attach method to create
         * the widget container.
         *
         * @octdoc      widget/create
         * @public
         * @param       DOMElement      parent              Parent node to create widget in.
         * @param       object          def                 Widget definition.
         * @return      DOMElement                          Node of new created widget.
         */
        this.create = function(parent, def)
        /**/
        {
            node = oui.$(document.createElement(this.container));
            node.addClass(
                this.cssclass + ('class' in def
                                 ? ' ' + def['class']
                                 : '')
            );

            parent.append(node);

            return node;
        }
    }

    /**
     * Default container type of a widget.
     *
     * @octdoc      widget/container
     * @public
     * @var         string
     */
    oui.widget.prototype.container = 'DIV';
    /**/

    /**
     * Default css class of a widget.
     *
     * @octdoc      widget/cssclass
     * @public
     * @var         string
     */
    oui.widget.prototype.cssclass = 'oui_widget';
    /**/

    /**
     * Return dialog, the widget is assigned to. Returns false, if the widget is not assigned
     * to any dialog.
     *
     * @octdoc      widget/getDialog
     * @public
     * @var         bool|oui.dialog
     */
    oui.widget.prototype.getDialog = function()
    /**/
    {
        return false;
    }

    /**
     * Return parent widget.
     *
     * @octdoc      widget/getParent
     * @public
     * @var         bool|oui.widget
     */
    oui.widget.prototype.getParent = function()
    /**/
    {
        return false;
    }

    /**
     * Build a widget and attach it to a parent node.
     *
     * @octdoc      widget/attach
     * @public
     * @param       DOMElement      parent          Parent node to attach widget to.
     * @param       object          def             Widget definitions.
     */
    oui.widget.prototype.attach = function(parent, def)
    /**/
    {
        this.create(parent, def);

        this.processChildren(def, function(parent, instance, def) {
            instance.attach(parent, def);
        });
    }

    /**
     * Assimilate a part of a page and try to wrap OUI functionality around page elements.
     *
     * @octdoc      widget/assimilate
     * @public
     * @param       DOMElement      node            Node to assimilate.
     */
    oui.widget.prototype.assimilate = function(node)
    /**/
    {
        var me = this;

        (function _assimilate(node) {
            // TODO: implementation
        })(node);
    }

    /**
     * Add a widget object to the registry.
     *
     * @octdoc      widget/register
     * @static
     * @param       string          name            Name of widget to register.
     * @param       object          widget          Widget object.
     * @param       object          options         Optional options for widget creation.
     */
    oui.widget.register = function(name, widget, options)
    /**/
    {
        if (name in registry) {
            throw 'Widget was registered before!';
        } else if (name == 'widget') {
            throw 'A widget cannot have the name \'widget\'!';
        } else {
            registry[name] = {
                'widget':  widget,
                'options': options || {}
            }
        }
    }

    /*
     * event handlers to be overwritten by children
     */

    /**
     * Event handler get's called, when widget get's enabled
     *
     * @octdoc      widget/onEnable
     * @public
     */
    oui.widget.prototype.onEnable = function()
    /**/
    {
    }

    /**
     * Event handler get's called, when widget get's disabled.
     *
     * @octdoc      widget/onDisable
     * @public
     */
    oui.widget.prototype.onDisable = function()
    /**/
    {
    }

    /**
     * Event handler get's called, after widget was populated with data.
     *
     * @octdoc      widget/onPopulate
     * @public
     */
    oui.widget.prototype.onPopulate = function()
    /**/
    {
    }

    /**
     * Event handler get's called, after widget's value get's updated/changed.
     *
     * @octdoc      widget/onChange
     * @public
     */
    oui.widget.prototype.onChange = function()
    /**/
    {
    }

    /**
     * Event handler get's called, when widget is clicked on.
     *
     * @octdoc      widget/onClick
     * @public
     */
    oui.widget.prototype.onClick = function()
    /**/
    {
    }

    /*
     * static methods
     */

    /**
     * Tests if something is registered with a specified widget name.
     *
     * @octdoc      widget/isRegistered
     * @static
     * @param       string          name            Name of widget to test if it's registered.
     */
    oui.widget.isRegistered = function(name)
    /**/
    {
        return (name in registry);
    }
})();
<|endoftext|>


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

define([
    'knockout'
], function (ko) {
    'use strict';

    // Define the formula version model.
    function FormulaVersion(raw, parent) {
        // Save the raw in order to get things like URLs
        this.raw = raw;

        // Save the parent VM
        this.parent = parent;

        // Editable fields
        this.formula = ko.observable();
        this.version = ko.observable();

        this.formulaHtmlId = ko.observable();

        this._process(raw);
    }

    FormulaVersion.constructor = FormulaVersion;

    FormulaVersion.prototype._process = function (raw) {
        this.formula(raw.formula);
        this.version(raw.version);
        // This is necessary because html ids can't contain slashes, colons, or periods.
        // We use the formula URI as an html ID at points.
        this.formulaHtmlId(raw.formula.replace(/\//g, '-').replace(/:/g, '-').replace(/\./g, '-'));
    };

    return FormulaVersion;
});<commit_msg>Need to replace '@' signs with dashes in formula versions as well<commit_after>
/*!
  * Copyright 2014,  Digital Reasoning
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
*/

define([
    'knockout'
], function (ko) {
    'use strict';

    // Define the formula version model.
    function FormulaVersion(raw, parent) {
        // Save the raw in order to get things like URLs
        this.raw = raw;

        // Save the parent VM
        this.parent = parent;

        // Editable fields
        this.formula = ko.observable();
        this.version = ko.observable();

        this.formulaHtmlId = ko.observable();

        this._process(raw);
    }

    FormulaVersion.constructor = FormulaVersion;

    FormulaVersion.prototype._process = function (raw) {
        this.formula(raw.formula);
        this.version(raw.version);
        // This is necessary because html ids can't contain slashes, colons, or periods.
        // We use the formula URI as an html ID at points.
        this.formulaHtmlId(raw.formula.replace(/\//g, '-').replace(/:/g, '-').replace(/\./g, '-')).replace(/@/g, '-');
    };

    return FormulaVersion;
});<|endoftext|>