<commit_before>(function() {
  'use strict';

  var L20n = {
    Context: Context,
    getContext: function L20n_getContext(id) {
      return new Context(id);
    },
  };

  function Resource(id, parser) {
    var self = this;

    this.id = id;
    this.resources = [];
    this.source = null;
    this.isReady = false;
    this.ast = {
      type: 'LOL',
      body: [],
    };

    this.build = build;

    var _imports_positions = [];

    function build(nesting, async) {
      if (nesting >= 7) {
        throw new ContextError("Too many nested imports.");
      }
      if (!async) {
        fetch(async);
        parse();
        buildImports(nesting + 1, async);
        flatten();
        return;
      }
      return fetch(async)
        .then(parse)
        .then(buildImports.bind(this, nesting + 1, async))
        .then(flatten);
    }

    function fetch(async) {
      if (!async) {
        if (!self.source) {
          self.source = L20n.IO.loadSync(self.id);
        }
        return;
      }
      if (self.source) {
        var source = new L20n.Promise();
        source.fulfill();
        return source;
      }
      return L20n.IO.loadAsync(self.id).then(function load_success(text) {
        self.source = text;
      });
    }

    function parse() {
      self.ast = parser.parse(self.source);
    }

    function buildImports(nesting, async) {
      var imports = self.ast.body.filter(function(elem, i) {
        if (elem.type == 'ImportStatement') {
          _imports_positions.push(i);
          return true;
        }
        return false;
      });

      imports.forEach(function(imp) {
        var uri = relativeToSelf(imp.uri.content);
        var res = new Resource(uri, parser);
        self.resources.push(res);
      });

      var imports_built = [];
      self.resources.forEach(function(res) {
        imports_built.push(res.build(nesting, async));
      });

      if (async) {
        return L20n.Promise.all(imports_built);
      }
    }

    function flatten() {
      for (var i = self.resources.length-1; i >= 0; i--) {
        var pos = _imports_positions[i] || 0;
        Array.prototype.splice.apply(self.ast.body,
          [pos, 1].concat(self.resources[i].ast.body));
      }
      self.isReady = true;
    }

    function relativeToSelf(url) {
      if (self.id === null || url[0] == '/') {
        return url;
      }
      var dirname = self.id.split('/').slice(0, -1).join('/');
      if (dirname) {
        // strip the trailing slash if present
        if (dirname[dirname.length - 1] == '/') {
          dirname = dirname.slice(0, dirname.length - 1);
        }
        return dirname + '/' + url;
      } else {
        return './' + url;
      }
    }

  }

  function Locale(id, parser, compiler) {
    this.id = id;
    this.resources = [];
    this.entries = null;
    this.ast = {
      type: 'LOL',
      body: [],
    };
    this.isReady = false;

    this.build = build;
    this.getEntry = getEntry;

    var self = this;

    function build(async) {
      if (!async) {
        buildResources(async);
        flatten();
        compile();
        return this;
      }
      return buildResources(async)
        .then(flatten)
        .then(compile);
    }

    function buildResources(async) {
      var resources_built = [];
      self.resources.forEach(function(res) {
        resources_built.push(res.build(0, async));
      });
      if (async) {
        return L20n.Promise.all(resources_built);
      }
    }

    function flatten() {
      self.ast.body = self.resources.reduce(function(prev, curr) {
        return prev.concat(curr.ast.body);
      }, self.ast.body);
    }

    function compile() {
      self.entries = compiler.compile(self.ast);
      self.isReady = true;
    }

    function getEntry(id) {
      if (this.entries.hasOwnProperty(id)) {
        return this.entries[id];
      }
      return undefined;
    }
  }

  function Context(id) {

    this.id = id;
    this.data = {};

    this.addResource = addResource;
    this.linkResource = linkResource;
    this.registerLocales = registerLocales;
    this.freeze = freeze;

    this.get = get;
    this.getEntity = getEntity;
    this.localize = localize;

    this.addEventListener = addEventListener;
    this.removeEventListener = removeEventListener;

    // all languages registered as available (list of codes)
    var _available = [];
    var _locales = {};
    // a special Locale for resources not associated with any other
    var _none;

    var _reslinks = [];

    var _isFrozen = false;
    var _isReady = false;
    var _emitter = new L20n.EventEmitter();
    var _parser = new L20n.Parser(L20n.EventEmitter);
    var _compiler = new L20n.Compiler(L20n.EventEmitter, L20n.Parser);

    var _retr = new L20n.RetranslationManager();

    var _listeners = [];

    _parser.addEventListener('error', echo);
    _compiler.addEventListener('error', echo);
    _compiler.setGlobals(_retr.globals);

    function get(id, data) {
      if (!_isReady) {
        throw new ContextError("Context not ready");
      }
      return getFromLocale(0, id, data).value;
    }

    function getEntity(id, data) {
      if (!_isReady) {
        throw new ContextError("Context not ready");
      }
      return getFromLocale(0, id, data);
    }

    function localize(idsOrTuples, callback) {
      if (!_isReady) {
        if (!callback) {
          throw new ContextError("Context not ready");
        }
        _retr.bindGet({
          id: callback,
          callback: getMany.bind(this, idsOrTuples, callback),
          globals: [],
        });
        // XXX: add stop and retranslate to the returned object
        return {};
      }
      return getMany(idsOrTuples, callback);
    }

    function getMany(idsOrTuples, callback) {
      var vals = {};
      var globalsUsed = {};
      var id;
      for (var i = 0, iot; iot = idsOrTuples[i]; i++) {
        if (Array.isArray(iot)) {
          id = iot[0];
          vals[id] = getEntity(iot[0], iot[1]);
        } else {
          id = iot;
          vals[id] = getEntity(iot);
        }
        for (var global in vals[id].globals) {
          if (vals[id].globals.hasOwnProperty(global)) {
            globalsUsed[global] = true;
          }
        }
      }
      var l10n = {
        entities: vals,
        //stop: fn
      };
      if (callback) {
        callback(l10n);
        _retr.bindGet({
          id: callback,
          callback: getMany.bind(this, idsOrTuples, callback),
          globals: Object.keys(globalsUsed),
        });
      }
      // XXX: add stop and retranslate to the returned object
      return {};
    }

    function getLocale(i) {
      // if we're out of locales from `_available`, resort to `_none`
      if (_available.length - i == 0) {
        return _none;
      }
      return  _locales[_available[i]];
    }

    function getFromLocale(cur, id, data, sourceString) {
      var locale = getLocale(cur);

      if (!locale) {
        var ex = new GetError("Entity couldn't be retrieved", id, _available);
        _emitter.emit('error', ex);
        // imitate the return value of Compiler.Entity.get
        return {
          value: sourceString ? sourceString : id,
          attributes: {},
          globals: {}
        };
      }

      if (!locale.isReady) {
        locale.build(false);
      }

      var entry = locale.getEntry(id);

      // if the entry is missing, just go to the next locale immediately
      if (entry === undefined) {
        _emitter.emit('error', new EntityError("Not found", id, locale.id));
        return getFromLocale(cur + 1, id, data, sourceString);
      }

      // otherwise, try to get the value of the entry
      try {
        return entry.get(getArgs.bind(this, data));
      } catch(e) {
        if (e instanceof L20n.Compiler.RuntimeError) {
          _emitter.emit('error', new EntityError(e.message, id, locale.id));
          return getFromLocale(cur + 1, id, data, sourceString || e.source);
        } else {
          throw e;
        }
      }
    }

    function getArgs(data) {
      if (!data) {
        return this.data;
      }
      var args = {};
      for (var i in this.data) {
        if (this.data.hasOwnProperty(i)) {
          args[i] = this.data[i];
        }
      }
      if (data) {
        for (i in data) {
          if (data.hasOwnProperty(i)) {
            args[i] = data[i];
          }
        }
      }
      return args;
    }

    function addResource(text) {
      if (_none === undefined) {
        _none = new Locale(null, _parser, _compiler);
      }
      var res = new Resource(null, _parser);
      res.source = text;
      _none.resources.push(res);
    }

    function linkResource(uri) {
      _reslinks.push(uri);
    }

    function link(uri) {
      if (typeof uri === 'function') {
        return linkTemplate(uri);
      } else {
        return linkURI(uri);
      }
    }

    function linkTemplate(uriTemplate) {
      for (var lang in _locales) {
        var res = new Resource(uriTemplate(lang), _parser);
        // XXX detect if the resource has been already added?
        _locales[lang].resources.push(res);
      }
      return true;
    }

    function linkURI(uri) {
      var res = new Resource(uri, _parser);
      if (_available.length !== 0) {
        for (var lang in _locales) {
          _locales[lang].resources.push(res);
        }
      }
      if (_none === undefined) {
        _none = new Locale(null, _parser, _compiler);
      }
      _none.resources.push(res);
      return true;
    }

    function registerLocales() {
      if (_isFrozen && !_isReady) {
        throw new ContextError("Context not ready");
      }
      _available = [];
      for (var i in arguments) {
        var lang = arguments[i];
        _available.push(lang);
        _locales[lang] = new Locale(lang, _parser, _compiler);
      }
      if (_isFrozen) {
        freeze();
      }
    }

    function freeze() {
      _isFrozen = true;
      for (var i = 0; i < _reslinks.length; i++) {
        link(_reslinks[i]);
      }
      var locale = _available.length > 0 ? _locales[_available[0]] : _none;
      return locale.build(true).then(setReady);
    }

    function setReady() {
      _isReady = true;
      _emitter.emit('ready');
      _retr.retranslate();
    }

    function addEventListener(type, listener) {
      _emitter.addEventListener(type, listener);
    }

    function removeEventListener(type, listener) {
      _emitter.removeEventListener(type, listener);
    }

    function echo(e) {
      _emitter.emit('error', e);
    }
}

  Context.Error = ContextError;
  Context.EntityError = EntityError;

  function ContextError(message) {
    this.name = 'ContextError';
    this.message = message;
  }
  ContextError.prototype = Object.create(Error.prototype);
  ContextError.prototype.constructor = ContextError;

  function EntityError(message, id, lang) {
    ContextError.call(this, message);
    this.name = 'EntityError';
    this.id = id;
    this.lang = lang;
    this.message = '[' + lang + '] ' + id + ': ' + message;
  }
  EntityError.prototype = Object.create(ContextError.prototype);
  EntityError.prototype.constructor = EntityError;

  function GetError(message, id, langs) {
    ContextError.call(this, message);
    this.name = 'GetError';
    this.id = id;
    this.tried = langs;
    if (langs.length) {
      this.message = id + ': ' + message + '; tried ' + langs.join(', ');
    } else {
      this.message = id + ': ' + message;
    }
  }
  GetError.prototype = Object.create(ContextError.prototype);
  GetError.prototype.constructor = GetError;

  this.L20n = L20n;

}).call(this);
<commit_msg>Call (not bind) getArgs at the time of calling getEntity<commit_after>(function() {
  'use strict';

  var L20n = {
    Context: Context,
    getContext: function L20n_getContext(id) {
      return new Context(id);
    },
  };

  function Resource(id, parser) {
    var self = this;

    this.id = id;
    this.resources = [];
    this.source = null;
    this.isReady = false;
    this.ast = {
      type: 'LOL',
      body: [],
    };

    this.build = build;

    var _imports_positions = [];

    function build(nesting, async) {
      if (nesting >= 7) {
        throw new ContextError("Too many nested imports.");
      }
      if (!async) {
        fetch(async);
        parse();
        buildImports(nesting + 1, async);
        flatten();
        return;
      }
      return fetch(async)
        .then(parse)
        .then(buildImports.bind(this, nesting + 1, async))
        .then(flatten);
    }

    function fetch(async) {
      if (!async) {
        if (!self.source) {
          self.source = L20n.IO.loadSync(self.id);
        }
        return;
      }
      if (self.source) {
        var source = new L20n.Promise();
        source.fulfill();
        return source;
      }
      return L20n.IO.loadAsync(self.id).then(function load_success(text) {
        self.source = text;
      });
    }

    function parse() {
      self.ast = parser.parse(self.source);
    }

    function buildImports(nesting, async) {
      var imports = self.ast.body.filter(function(elem, i) {
        if (elem.type == 'ImportStatement') {
          _imports_positions.push(i);
          return true;
        }
        return false;
      });

      imports.forEach(function(imp) {
        var uri = relativeToSelf(imp.uri.content);
        var res = new Resource(uri, parser);
        self.resources.push(res);
      });

      var imports_built = [];
      self.resources.forEach(function(res) {
        imports_built.push(res.build(nesting, async));
      });

      if (async) {
        return L20n.Promise.all(imports_built);
      }
    }

    function flatten() {
      for (var i = self.resources.length-1; i >= 0; i--) {
        var pos = _imports_positions[i] || 0;
        Array.prototype.splice.apply(self.ast.body,
          [pos, 1].concat(self.resources[i].ast.body));
      }
      self.isReady = true;
    }

    function relativeToSelf(url) {
      if (self.id === null || url[0] == '/') {
        return url;
      }
      var dirname = self.id.split('/').slice(0, -1).join('/');
      if (dirname) {
        // strip the trailing slash if present
        if (dirname[dirname.length - 1] == '/') {
          dirname = dirname.slice(0, dirname.length - 1);
        }
        return dirname + '/' + url;
      } else {
        return './' + url;
      }
    }

  }

  function Locale(id, parser, compiler) {
    this.id = id;
    this.resources = [];
    this.entries = null;
    this.ast = {
      type: 'LOL',
      body: [],
    };
    this.isReady = false;

    this.build = build;
    this.getEntry = getEntry;

    var self = this;

    function build(async) {
      if (!async) {
        buildResources(async);
        flatten();
        compile();
        return this;
      }
      return buildResources(async)
        .then(flatten)
        .then(compile);
    }

    function buildResources(async) {
      var resources_built = [];
      self.resources.forEach(function(res) {
        resources_built.push(res.build(0, async));
      });
      if (async) {
        return L20n.Promise.all(resources_built);
      }
    }

    function flatten() {
      self.ast.body = self.resources.reduce(function(prev, curr) {
        return prev.concat(curr.ast.body);
      }, self.ast.body);
    }

    function compile() {
      self.entries = compiler.compile(self.ast);
      self.isReady = true;
    }

    function getEntry(id) {
      if (this.entries.hasOwnProperty(id)) {
        return this.entries[id];
      }
      return undefined;
    }
  }

  function Context(id) {

    this.id = id;
    this.data = {};

    this.addResource = addResource;
    this.linkResource = linkResource;
    this.registerLocales = registerLocales;
    this.freeze = freeze;

    this.get = get;
    this.getEntity = getEntity;
    this.localize = localize;

    this.addEventListener = addEventListener;
    this.removeEventListener = removeEventListener;

    // all languages registered as available (list of codes)
    var _available = [];
    var _locales = {};
    // a special Locale for resources not associated with any other
    var _none;

    var _reslinks = [];

    var _isFrozen = false;
    var _isReady = false;
    var _emitter = new L20n.EventEmitter();
    var _parser = new L20n.Parser(L20n.EventEmitter);
    var _compiler = new L20n.Compiler(L20n.EventEmitter, L20n.Parser);

    var _retr = new L20n.RetranslationManager();

    var _listeners = [];

    _parser.addEventListener('error', echo);
    _compiler.addEventListener('error', echo);
    _compiler.setGlobals(_retr.globals);

    function get(id, data) {
      if (!_isReady) {
        throw new ContextError("Context not ready");
      }
      return getFromLocale(0, id, data).value;
    }

    function getEntity(id, data) {
      if (!_isReady) {
        throw new ContextError("Context not ready");
      }
      return getFromLocale(0, id, data);
    }

    function localize(idsOrTuples, callback) {
      if (!_isReady) {
        if (!callback) {
          throw new ContextError("Context not ready");
        }
        _retr.bindGet({
          id: callback,
          callback: getMany.bind(this, idsOrTuples, callback),
          globals: [],
        });
        // XXX: add stop and retranslate to the returned object
        return {};
      }
      return getMany(idsOrTuples, callback);
    }

    function getMany(idsOrTuples, callback) {
      var vals = {};
      var globalsUsed = {};
      var id;
      for (var i = 0, iot; iot = idsOrTuples[i]; i++) {
        if (Array.isArray(iot)) {
          id = iot[0];
          vals[id] = getEntity(iot[0], iot[1]);
        } else {
          id = iot;
          vals[id] = getEntity(iot);
        }
        for (var global in vals[id].globals) {
          if (vals[id].globals.hasOwnProperty(global)) {
            globalsUsed[global] = true;
          }
        }
      }
      var l10n = {
        entities: vals,
        //stop: fn
      };
      if (callback) {
        callback(l10n);
        _retr.bindGet({
          id: callback,
          callback: getMany.bind(this, idsOrTuples, callback),
          globals: Object.keys(globalsUsed),
        });
      }
      // XXX: add stop and retranslate to the returned object
      return {};
    }

    function getLocale(i) {
      // if we're out of locales from `_available`, resort to `_none`
      if (_available.length - i == 0) {
        return _none;
      }
      return  _locales[_available[i]];
    }

    function getFromLocale(cur, id, data, sourceString) {
      var locale = getLocale(cur);

      if (!locale) {
        var ex = new GetError("Entity couldn't be retrieved", id, _available);
        _emitter.emit('error', ex);
        // imitate the return value of Compiler.Entity.get
        return {
          value: sourceString ? sourceString : id,
          attributes: {},
          globals: {}
        };
      }

      if (!locale.isReady) {
        locale.build(false);
      }

      var entry = locale.getEntry(id);

      // if the entry is missing, just go to the next locale immediately
      if (entry === undefined) {
        _emitter.emit('error', new EntityError("Not found", id, locale.id));
        return getFromLocale(cur + 1, id, data, sourceString);
      }

      // otherwise, try to get the value of the entry
      try {
        return entry.get(getArgs.call(this, data));
      } catch(e) {
        if (e instanceof L20n.Compiler.RuntimeError) {
          _emitter.emit('error', new EntityError(e.message, id, locale.id));
          return getFromLocale(cur + 1, id, data, sourceString || e.source);
        } else {
          throw e;
        }
      }
    }

    function getArgs(data) {
      if (!data) {
        return this.data;
      }
      var args = {};
      for (var i in this.data) {
        if (this.data.hasOwnProperty(i)) {
          args[i] = this.data[i];
        }
      }
      if (data) {
        for (i in data) {
          if (data.hasOwnProperty(i)) {
            args[i] = data[i];
          }
        }
      }
      return args;
    }

    function addResource(text) {
      if (_none === undefined) {
        _none = new Locale(null, _parser, _compiler);
      }
      var res = new Resource(null, _parser);
      res.source = text;
      _none.resources.push(res);
    }

    function linkResource(uri) {
      _reslinks.push(uri);
    }

    function link(uri) {
      if (typeof uri === 'function') {
        return linkTemplate(uri);
      } else {
        return linkURI(uri);
      }
    }

    function linkTemplate(uriTemplate) {
      for (var lang in _locales) {
        var res = new Resource(uriTemplate(lang), _parser);
        // XXX detect if the resource has been already added?
        _locales[lang].resources.push(res);
      }
      return true;
    }

    function linkURI(uri) {
      var res = new Resource(uri, _parser);
      if (_available.length !== 0) {
        for (var lang in _locales) {
          _locales[lang].resources.push(res);
        }
      }
      if (_none === undefined) {
        _none = new Locale(null, _parser, _compiler);
      }
      _none.resources.push(res);
      return true;
    }

    function registerLocales() {
      if (_isFrozen && !_isReady) {
        throw new ContextError("Context not ready");
      }
      _available = [];
      for (var i in arguments) {
        var lang = arguments[i];
        _available.push(lang);
        _locales[lang] = new Locale(lang, _parser, _compiler);
      }
      if (_isFrozen) {
        freeze();
      }
    }

    function freeze() {
      _isFrozen = true;
      for (var i = 0; i < _reslinks.length; i++) {
        link(_reslinks[i]);
      }
      var locale = _available.length > 0 ? _locales[_available[0]] : _none;
      return locale.build(true).then(setReady);
    }

    function setReady() {
      _isReady = true;
      _emitter.emit('ready');
      _retr.retranslate();
    }

    function addEventListener(type, listener) {
      _emitter.addEventListener(type, listener);
    }

    function removeEventListener(type, listener) {
      _emitter.removeEventListener(type, listener);
    }

    function echo(e) {
      _emitter.emit('error', e);
    }
}

  Context.Error = ContextError;
  Context.EntityError = EntityError;

  function ContextError(message) {
    this.name = 'ContextError';
    this.message = message;
  }
  ContextError.prototype = Object.create(Error.prototype);
  ContextError.prototype.constructor = ContextError;

  function EntityError(message, id, lang) {
    ContextError.call(this, message);
    this.name = 'EntityError';
    this.id = id;
    this.lang = lang;
    this.message = '[' + lang + '] ' + id + ': ' + message;
  }
  EntityError.prototype = Object.create(ContextError.prototype);
  EntityError.prototype.constructor = EntityError;

  function GetError(message, id, langs) {
    ContextError.call(this, message);
    this.name = 'GetError';
    this.id = id;
    this.tried = langs;
    if (langs.length) {
      this.message = id + ': ' + message + '; tried ' + langs.join(', ');
    } else {
      this.message = id + ': ' + message;
    }
  }
  GetError.prototype = Object.create(ContextError.prototype);
  GetError.prototype.constructor = GetError;

  this.L20n = L20n;

}).call(this);
<|endoftext|>


<commit_before>/**
 * A context tracks the context of a particular execution queue.
 *
 * As the queue is processed, the context will gain information
 * about the results of each command executed.
 */
var Logger = require('./logging').Logger;

module.exports = Context;

function Context(initialValues) {
  this.resultMap = {};
  this.datasourceMap = {};

  // Debug flag.
  this.debug = false;

  // The logging event emitter.
  this.logger = new Logger();

  // This was initially done for safety.
  // Could this just be turned into
  // this.resultMap = intialvalues || {}
  if (typeof initialValues == 'object') {
    for (var name in initialValues) {
      this.add(name, initialValues[name]);
    }
  }
}

Context.prototype.add = function(name, value) {
  if (value == undefined)
    return;

  this.resultMap[name] = value;

}
Context.prototype.get = function(name) {
  return this.resultMap[name];
}

// Property 'length'.
Context.prototype.__defineGetter__('length', function() {
  return this.size();
});

Context.prototype.size = function() {
  // Since size is called rarely, we
  // calculate on the fly.
  var i = 0;
  for (var o in this.resultMap) {
    ++i;
  }
  return i;
}
Context.prototype.getAll = function() {
  return this.resultMap;
}

/**
 * Log a message to the logging facility.
 *
 * Attach an event listen to cxt.logger.
 * Then handle the following events (at your option):
 *
 * - warning
 * - fatal
 * - error
 * - info
 * - debug
 * - custom
 *
 * Note that the parameters are in reverse order for historical reasons.
 *
 * @param {String} msg
 *   The message.
 * @param {String} level
 *   THe log level.
 */
Context.prototype.log = function(msg, level) {
  this.logger.log(level || "info", msg);
}

// XXX: I don't like having one object serve as the compartment for both
// general objects and datasources. This should get refactored. But for
// the first go-around, I feel like I should favor simplicity.

Context.prototype.addDatasource = function(name, ds) {
  this.datasourceMap[name] = ds;
}

Context.prototype.getDatasource = function(name) {
  return this.datasourceMap[name];
}

Context.prototype.removeDatasource = function(name) {
  this.datasourceMap[name] = null;
}

/**
 * A partial clone of a context.
 *
 * This returns a new context with the same data
 * and datasources as the base. Note that the
 * values in this context are not cloned. So modifying them
 * will modify the base copy as well.
 */
Context.prototype.copy = function() {
  var cxt = new Context();
  var map = this.getAll();
  for (var key in map) {
    cxt.add(key, map[key]);
  }
  for (var key in this.datasourceMap) {
    cxt.addDatasource(key, this.datasourceMap[key]);
  }
  return cxt;
}
<commit_msg>Added setLogger() back to context.<commit_after>/**
 * A context tracks the context of a particular execution queue.
 *
 * As the queue is processed, the context will gain information
 * about the results of each command executed.
 */
var Logger = require('./logging').Logger;

module.exports = Context;

function Context(initialValues) {
  this.resultMap = {};
  this.datasourceMap = {};

  // Debug flag.
  this.debug = false;

  // The logging event emitter.
  this.logger = new Logger();

  // This was initially done for safety.
  // Could this just be turned into
  // this.resultMap = intialvalues || {}
  if (typeof initialValues == 'object') {
    for (var name in initialValues) {
      this.add(name, initialValues[name]);
    }
  }
}

Context.prototype.add = function(name, value) {
  if (value == undefined)
    return;

  this.resultMap[name] = value;

}
Context.prototype.get = function(name) {
  return this.resultMap[name];
}

// Property 'length'.
Context.prototype.__defineGetter__('length', function() {
  return this.size();
});

Context.prototype.size = function() {
  // Since size is called rarely, we
  // calculate on the fly.
  var i = 0;
  for (var o in this.resultMap) {
    ++i;
  }
  return i;
}
Context.prototype.getAll = function() {
  return this.resultMap;
}

/**
 * Log a message to the logging facility.
 *
 * Attach an event listen to cxt.logger.
 * Then handle the following events (at your option):
 *
 * - warning
 * - fatal
 * - error
 * - info
 * - debug
 * - custom
 *
 * Note that the parameters are in reverse order for historical reasons.
 *
 * @param {String} msg
 *   The message.
 * @param {String} level
 *   THe log level.
 */
Context.prototype.log = function(msg, level) {
  this.logger.log(level || "info", msg);
}

Context.prototype.setLogger = function(logger) {
  this.logger = logger;
}

// XXX: I don't like having one object serve as the compartment for both
// general objects and datasources. This should get refactored. But for
// the first go-around, I feel like I should favor simplicity.

Context.prototype.addDatasource = function(name, ds) {
  this.datasourceMap[name] = ds;
}

Context.prototype.getDatasource = function(name) {
  return this.datasourceMap[name];
}

Context.prototype.removeDatasource = function(name) {
  this.datasourceMap[name] = null;
}

/**
 * A partial clone of a context.
 *
 * This returns a new context with the same data
 * and datasources as the base. Note that the
 * values in this context are not cloned. So modifying them
 * will modify the base copy as well.
 */
Context.prototype.copy = function() {
  var cxt = new Context();
  var map = this.getAll();
  for (var key in map) {
    cxt.add(key, map[key]);
  }
  for (var key in this.datasourceMap) {
    cxt.addDatasource(key, this.datasourceMap[key]);
  }
  return cxt;
}
<|endoftext|>


<commit_before>var _ = require('underscore'),
    async = require('async'),
    fs = require('fs'),
    fu = require('./fileUtil');

function Context(options, config, plugins, mixins, event) {
  this._package = options.package;
  this._platform = options.platform;
  this._plugins = plugins;
  this.mode = options.mode;
  this.module = options.module;
  this.fileConfig = options.fileConfig;
  this.resource = options.resource;
  this.config = config;
  this.mixins = mixins || options.mixins;
  this.event = event || options.event;
}
Context.prototype = {
  fileUtil: fu,

  clone: function(options) {
    var ret = new Context(this, this.config);
    ret.parent = this;
    var prototype = Object.keys(Context.prototype);
    for (var name in this) {
      if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) {
        ret[name] = this[name];
      }
    }
    if (options) {
      _.extend(ret, options);
      ret._package = options.package || this._package;
      ret._platform = options.platform || this._platform;
    }
    return ret;
  },

  fileNamesForModule: function(mode, moduleName, callback) {
    var context = this.clone();
    context.mode = mode;
    context.module = moduleName && this.config.module(moduleName);
    if (moduleName && !context.module) {
      return callback(new Error('Unknown module "' + moduleName + '"'));
    }

    this.plugins.outputConfigs(context, function(err, configs) {
      if (err) {
        return callback(err);
      }

      async.map(configs, function(config, callback) {
        var fileContext = context.clone();
        fileContext.fileConfig = config;
        fileContext._plugins.fileName(fileContext, function(err, fileName) {
          config.fileName = fileName;
          callback(err, config);
        });
      },
      callback);
    });
  },

  loadResource: function(resource, callback) {
    if (!callback) {
      // if only single param, assume as callback and resource from context
      resource = this.resource;
      callback = resource;
    }

    var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src};

    function loaded(err, data) {
      /*jshint eqnull: true */
      if (err) {
        var json = '';
        try {
          // Output JSON for the resource... but protect ourselves from a failure masking a failure
          resource = _.clone(resource);
          delete resource.mixin;
          delete resource.enoent;
          json = '\n\t' + JSON.stringify(resource);
        } catch (err) { /* NOP */ }

        var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err));
        errorWrapper.source = err;
        callback(errorWrapper);
        return;
      }
      fileInfo.inputs = data.inputs;
      fileInfo.generated = data.generated;
      fileInfo.noSeparator = data.noSeparator;
      fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings;
      fileInfo.content = data.data != null ? data.data : data;

      // Ensure that we dump off the stack
      _.defer(function() {
        callback(err, fileInfo);
      });
    }

    if (typeof resource === 'function') {
      resource(this, loaded);
    } else if (resource.src) {
      // Assume a file page, attempt to load
      fu.readFile(resource.src, loaded);
    } else {
      loaded(undefined, {data: '', noSeparator: true, inputs: resource.dir ? [resource.dir] : []});
    }

    return fileInfo;
  },

  outputFile: function(writer, callback) {
    var context = this;
    context.plugins.file(context, function(err) {
      if (err) {
        return callback(err);
      }

      context.plugins.fileName(context, function(err, fileName) {
        if (err) {
          return callback(err);
        }

        context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension;
        context.fileName = context.outdir + '/' + context.buildPath;
        writer(function(err, data) {
          data = _.defaults({
              fileConfig: context.fileConfig,
              platform: context.platform,
              package: context.package,
              mode: context.mode
            }, data);

          if (err) {
            fs.unlink(context.fileName);
          } else {
            context.event.emit('output', data);
          }
          context.fileCache = undefined;
          callback(err, data);
        });
      });
    });
  },

  get description() {
    var ret = 'package:' + this.package + '_platform:' + this.platform;
    if (this.mode) {
      ret += '_mode:' + this.mode;
    }
    if (this.fileName) {
      ret += '_config:' + this.fileName;
    }
    if (this.module) {
      ret += '_module:' + (this.module.name || this.module);
    }
    if (this.resource) {
      // TODO : Anything better for this?
      ret += '_resource:' + (this.resource.src || this.resource);
    }
    return ret;
  },

  get plugins() { return this._plugins; },

  get package() { return this._package; },
  get platform() { return this._platform; },
  get platformPath() {
    return this.platform ? this.platform + '/' : '';
  },

  get combined() {
    return this.config.combineModules(this.package);
  },
  get baseName() {
    if (!this.combined) {
      return this.module.name;
    } else {
      return (this.config.attributes.packages[this.package] || {}).name || this.package;
    }
  },

  get resources() {
    if (this.parent) {
      return this.parent.resources;
    } else {
      return this._resources;
    }
  },
  set resources(value) {
    if (this.parent) {
      delete this.parent;
    }
    this._resources = value;
  }
};

module.exports = Context;
<commit_msg>Propagate code in errorWrapper <commit_after>var _ = require('underscore'),
    async = require('async'),
    fs = require('fs'),
    fu = require('./fileUtil');

function Context(options, config, plugins, mixins, event) {
  this._package = options.package;
  this._platform = options.platform;
  this._plugins = plugins;
  this.mode = options.mode;
  this.module = options.module;
  this.fileConfig = options.fileConfig;
  this.resource = options.resource;
  this.config = config;
  this.mixins = mixins || options.mixins;
  this.event = event || options.event;
}
Context.prototype = {
  fileUtil: fu,

  clone: function(options) {
    var ret = new Context(this, this.config);
    ret.parent = this;
    var prototype = Object.keys(Context.prototype);
    for (var name in this) {
      if (this.hasOwnProperty(name) && prototype.indexOf(name) === -1) {
        ret[name] = this[name];
      }
    }
    if (options) {
      _.extend(ret, options);
      ret._package = options.package || this._package;
      ret._platform = options.platform || this._platform;
    }
    return ret;
  },

  fileNamesForModule: function(mode, moduleName, callback) {
    var context = this.clone();
    context.mode = mode;
    context.module = moduleName && this.config.module(moduleName);
    if (moduleName && !context.module) {
      return callback(new Error('Unknown module "' + moduleName + '"'));
    }

    this.plugins.outputConfigs(context, function(err, configs) {
      if (err) {
        return callback(err);
      }

      async.map(configs, function(config, callback) {
        var fileContext = context.clone();
        fileContext.fileConfig = config;
        fileContext._plugins.fileName(fileContext, function(err, fileName) {
          config.fileName = fileName;
          callback(err, config);
        });
      },
      callback);
    });
  },

  loadResource: function(resource, callback) {
    if (!callback) {
      // if only single param, assume as callback and resource from context
      resource = this.resource;
      callback = resource;
    }

    var fileInfo = {name: resource.hasOwnProperty('sourceFile') ? resource.sourceFile : resource.src};

    function loaded(err, data) {
      /*jshint eqnull: true */
      if (err) {
        var json = '';
        try {
          // Output JSON for the resource... but protect ourselves from a failure masking a failure
          resource = _.clone(resource);
          delete resource.mixin;
          delete resource.enoent;
          json = '\n\t' + JSON.stringify(resource);
        } catch (err) { /* NOP */ }

        var errorWrapper = new Error('Failed to load resource "' + fileInfo.name + '"' + json + '\n\t' + (err.stack || err));
        errorWrapper.source = err;
        errorWrapper.code = err.code;
        callback(errorWrapper);
        return;
      }
      fileInfo.inputs = data.inputs;
      fileInfo.generated = data.generated;
      fileInfo.noSeparator = data.noSeparator;
      fileInfo.ignoreWarnings = data.ignoreWarnings || resource.ignoreWarnings;
      fileInfo.content = data.data != null ? data.data : data;

      // Ensure that we dump off the stack
      _.defer(function() {
        callback(err, fileInfo);
      });
    }

    if (typeof resource === 'function') {
      resource(this, loaded);
    } else if (resource.src) {
      // Assume a file page, attempt to load
      fu.readFile(resource.src, loaded);
    } else {
      loaded(undefined, {data: '', noSeparator: true, inputs: resource.dir ? [resource.dir] : []});
    }

    return fileInfo;
  },

  outputFile: function(writer, callback) {
    var context = this;
    context.plugins.file(context, function(err) {
      if (err) {
        return callback(err);
      }

      context.plugins.fileName(context, function(err, fileName) {
        if (err) {
          return callback(err);
        }

        context.buildPath = (fileName.root ? '' : context.platformPath) + fileName.path + '.' + fileName.extension;
        context.fileName = context.outdir + '/' + context.buildPath;
        writer(function(err, data) {
          data = _.defaults({
              fileConfig: context.fileConfig,
              platform: context.platform,
              package: context.package,
              mode: context.mode
            }, data);

          if (err) {
            fs.unlink(context.fileName);
          } else {
            context.event.emit('output', data);
          }
          context.fileCache = undefined;
          callback(err, data);
        });
      });
    });
  },

  get description() {
    var ret = 'package:' + this.package + '_platform:' + this.platform;
    if (this.mode) {
      ret += '_mode:' + this.mode;
    }
    if (this.fileName) {
      ret += '_config:' + this.fileName;
    }
    if (this.module) {
      ret += '_module:' + (this.module.name || this.module);
    }
    if (this.resource) {
      // TODO : Anything better for this?
      ret += '_resource:' + (this.resource.src || this.resource);
    }
    return ret;
  },

  get plugins() { return this._plugins; },

  get package() { return this._package; },
  get platform() { return this._platform; },
  get platformPath() {
    return this.platform ? this.platform + '/' : '';
  },

  get combined() {
    return this.config.combineModules(this.package);
  },
  get baseName() {
    if (!this.combined) {
      return this.module.name;
    } else {
      return (this.config.attributes.packages[this.package] || {}).name || this.package;
    }
  },

  get resources() {
    if (this.parent) {
      return this.parent.resources;
    } else {
      return this._resources;
    }
  },
  set resources(value) {
    if (this.parent) {
      delete this.parent;
    }
    this._resources = value;
  }
};

module.exports = Context;
<|endoftext|>


<commit_before>var
  sys = require("sys"),
  events = require("events"),
  http = require('http'),
  fs = require('fs'),
  path = require('path'),
  sys = require('sys'),
  querystring = require('querystring'),
  mime = require('./dep/mime'),
  base64 = require('./dep/base64');

function mixin() {
  // copy reference to target object
  var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source;

  // Handle a deep copy situation
  if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }

  // Handle case when target is a string or something (possible in deep copy)
  if ( typeof target !== "object" && !(typeof target === 'function') )
    target = {};

  // mixin process itself if only one argument is passed
  if ( length == i ) {
    target = GLOBAL;
    --i;
  }

  for ( ; i < length; i++ ) {
    // Only deal with non-null/undefined values
    if ( (source = arguments[i]) != null ) {
      // Extend the base object
      Object.getOwnPropertyNames(source).forEach(function(k){
        var d = Object.getOwnPropertyDescriptor(source, k) || {value: source[k]};
        if (d.get) {
          target.__defineGetter__(k, d.get);
          if (d.set) {
            target.__defineSetter__(k, d.set);
          }
        }
        else {
          // Prevent never-ending loop
          if (target === d.value) {
            continue;
          }

          if (deep && d.value && typeof d.value === "object") {
            target[k] = mixin(deep,
              // Never move original objects, clone them
              source[k] || (d.value.length != null ? [] : {})
            , d.value);
          }
          else {
            target[k] = d.value;
          }
        }
      });
    }
  }
  // Return the modified object
  return target;
}

// Stringify function embedded inside of objects. Useful for couch views
exports.toJSON = function(data) {
  return JSON.stringify(data, function(key, val) {
    if (typeof val == 'function') {
      return val.toString();
    }
    return val;
  });
};

// Encode only key, startkey and endkey as JSON
exports.toQuery = function(query) {
  for (var k in query) {
    if (['key', 'startkey', 'endkey'].indexOf(k) != -1) {
      query[k] = JSON.stringify(query[k]);
    } else {
      query[k] = String(query[k]);
    }
  }
  return querystring.stringify(query);
};

// Helps turning a file into an inline CouchDB attachment
exports.toAttachment = function(file, cb) {
  fs.readFile(file, 'binary', function (er, data) {
    if (er) return cb && cb(er, data);
    var ext = path.extname(file).substr(1);
    cb && cb(null, {
      content_type: mime.lookup(ext),
      data: base64.encode(data)
    });
  })
};

exports.createClient = function(port, host, user, pass) {
  if (isNaN(port)) {
    host = port;
    port = 5984;
  }
  host = host || "localhost";

  var
    httpClient = http.createClient(port, host),
    couchClient = new Client();

  couchClient.__defineGetter__('host', function() {
    return host;
  });

  couchClient.__defineGetter__('port', function() {
    return port;
  });

  couchClient._addClientListener = function() {
	  httpClient.addListener.apply(httpClient, arguments);
  };

  couchClient._queueRequest = function(options, cb) {
    if(user && pass) {
      options.headers.authorization = "Basic " + base64.encode(user + ":" + pass)
    }

    if (options.query) {
      options.path += '?'+exports.toQuery(options.query);
      delete options.query;
    }

    // CouchDB 1.0 requires application/json for _bulk_docs and other POST APIs.
    options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json';

    var
      request = httpClient.request(
        options.method.toUpperCase(),
        options.path,
        options.headers
      ),
      cbFired = false,
      onClose = function(hadError, reason) {
        if (hadError && !cbFired) cb && cb(new Error(reason));
        httpClient.removeListener('close', onClose);
      };

    httpClient.addListener('close', onClose);

    if (options.data && typeof options.data != 'string') {
      options.data = exports.toJSON(options.data);
    }

    if (options.data) {
      request.write(options.data, options.requestEncoding || 'utf8');
    }
    request.addListener("response", function(res) {
      var buffer = '';
      res.setEncoding(options.responseEncoding || 'utf8');
      res
        .addListener('data', function(chunk) {
          buffer += (chunk || '');
        })
        .addListener('end', function() {
          if(options.responseEncoding == 'binary') {
            cbFired = true;
            return cb && cb(null, buffer);
          }

          var json;
          try {
            json = JSON.parse(buffer);
          } catch (e) {
            cbFired = true;
            return cb && cb(new Error('invalid json: '+json+" "+e.message));
          }

          if ('error' in json) {
            cbFired = true;
            return cb && cb(json);
          }

          if (!options.full) {
            cbFired = true;
            return cb && cb(null, json);
          }

          cbFired = true;
          cb && cb(null, {
            headers: res.headers,
            json: json,
          });
        });

    });
    request.end();
  };

  return couchClient;
};

var Client = exports.Client = function() {

};

function requestOptions (args) {
  var options;
  // might get between 0 and 3 strings,
  // and then a function
  var args = Array.prototype.slice.call(args, 0);
  cb = (typeof args[args.length-1] === "function") ? args.pop() : null;
  // sys.debug(args.length);
  // sys.debug(sys.inspect(args));

  var method = args.shift(),
    path = args.shift(),
    data = args.shift();

  if (typeof method == 'object') {
    options = method;
  } else if (typeof method == 'string' && typeof path != 'string') {
    options = {
      path: method,
      query: path
    };
  } else {
    options = {
      method: method,
      path: path,
      data: data
    }
  }
  options.cb = cb;
  // sys.debug("requestOptions2 "+sys.inspect(options));
  return options;
}

Client.prototype.addListener = function(type, fn) {
	if (type == 'error' || type == 'timeout') {
		this._addClientListener(type, fn);
	} else {
		throw new Error('CouchDB client does not support the ' + type + ' event.');
	}
}

Client.prototype.request = function(method, path, data, cb) {
  var
    defaults = {
      method: 'get',
      path: '/',
      headers: {},
      data: null,
      query: null,
      full: false
    },
    options = requestOptions(arguments);

  options = mixin(defaults, options);
  options.headers.host = options.headers.host || this.host;

  return this._queueRequest(options, options.cb);
};

Client.prototype.allDbs = function(cb) {
  return this.request({
    path: '/_all_dbs'
  }, cb);
};

Client.prototype.config = function(cb) {
  return this.request({
    path: '/_config'
  }, cb);
};

Client.prototype.uuids = function(count, cb) {
  if (!cb && typeof(count) === "function") {
    cb = count;
    count = "";
  }
  return this.request({
    path: '/_uuids'+(count ? '?count='+count : '')
  }, cb);
};

Client.prototype.replicate = function(source, target, options, cb) {
  if (!cb && typeof(options) === "function") {
    cb = options;
    options = {};
  }
  options = mixin({
    source: source,
    target: target,
  }, options || {});

  return this.request({
    method: 'POST',
    path: '/_replicate',
    data: options
  }, cb);
};

Client.prototype.stats = function() {
  var args = Array.prototype.slice.call(arguments),
    cb = (typeof args[args.length - 1] === "function") ? args.pop() : null;
  return this.request({
    path: '/_stats'+((args) ? '/'+args.join('/') : '')
  }, cb);
};

Client.prototype.activeTasks = function(cb) {
  return this.request({
    path: '/_active_tasks'
  }, cb);
};

Client.prototype.db = function(name) {
  var
    couchClient = this,
    couchDb = new Db();

  couchDb.__defineGetter__('name', function() {
    return name;
  });

  couchDb.__defineGetter__('client', function() {
    return couchClient;
  });

  couchDb.request = function(method, path, data, cb) {
    var options = requestOptions(arguments);
    options.path = '/'+name+(options.path || '');
    return couchClient.request(options, options.cb);
  };

  return couchDb;
};

var Db = exports.Db = function() {

};

function ensureDesignId(id) {
  if (!id || id.match(/^_design\//)) { return id; }
  return '_design/' + id;
}

Db.prototype.exists = function(cb) {
  this.request({path: ''}, function (er) {
    // if it was an error, then that's a "no" on the existing.
    cb && cb(null, !(er && er.error == 'not_found'));
  });
};

Db.prototype.info = function(cb) {
  return this.request({}, cb);
};

Db.prototype.create = function(cb) {
  return this.request({
    method: 'PUT'
  }, cb);
};

Db.prototype.remove = function(cb) {
  return this.request('DELETE', '', cb);
};

Db.prototype.getDoc = function(id, cb) {
  return this.request({
    path: '/'+id
  }, cb);
};

Db.prototype.saveDoc = function(id, doc, cb) {
  if (typeof id == 'object') {
    cb = doc;
    return this.request({
      method: 'POST',
      path: '/',
      data: id,
    }, cb);
  }

  return this.request({
    method: 'PUT',
    path: '/'+id,
    data: doc,
  }, cb);
};

Db.prototype.removeDoc = function(id, rev, cb) {
  return this.request({
    method: 'DELETE',
    path: '/'+id,
    query: {rev: rev}
  }, cb);
};

Db.prototype.copyDoc = function(srcId, destId, destRev, cb) {
  if (!cb && typeof(destRev) === "function") {
    cb = destRev;
    destRev = null;
  }
  if (destRev) {
    destId += '?rev='+destRev;
  }

  return this.request({
    method: 'COPY',
    path: '/'+srcId,
    headers: {
      'Destination': destId
    }
  }, cb);
}

Db.prototype.bulkDocs = function(data, cb) {
  if (!cb && typeof(data) === "function") {
    cb = data;
    data = null;
  }
  return this.request({
    method: 'POST',
    path: '/_bulk_docs',
    data: data,
  }, cb);
};

Db.prototype.saveDesign = function(design, doc, cb) {
  if (typeof design == 'object') {
    design._id = ensureDesignId(design._id);
    return this.saveDoc(design, doc, cb);
  }

  return this.saveDoc(ensureDesignId(design), doc, cb);
};

Db.prototype.saveAttachment = function(file, docId, options, cb) {
  var
    self = this,
    ext = path.extname(file).substr(1);

  if (!cb && typeof(options) === "function") {
    cb = options;
    options = {};
  }

  options = mixin({
    name: path.basename(file),
    contentType: mime.lookup(ext),
    rev: null
  }, options || {});

  // We could stream the file here, but I doubt people store big enough files
  // in couch to make this worth it?
  fs.readFile(file, 'binary', function (er, data) {
    if (er) return cb && cb(er);
    self.request({
      method: 'PUT',
      requestEncoding: 'binary',
      path: '/'+docId+'/'+options.name+(options.rev ? '?rev='+options.rev : ''),
      headers: {
        'Content-Type': options.contentType
      },
      data: data,
    }, cb);
  });
};

Db.prototype.removeAttachment = function(docId, attachmentId, docRev, cb) {
  return this.request({
    method: 'DELETE',
    path: '/'+docId+'/'+attachmentId,
    query: {rev: docRev}
  }, cb)
};

Db.prototype.getAttachment = function(docId, attachmentId, cb) {
  return this.request({
    path: '/'+docId+'/'+attachmentId,
    responseEncoding: 'binary',
  }, cb);
};

Db.prototype.allDocs = function(query, cb) {
  if (!cb && typeof(query) === "function") {
    cb = query;
    query = null;
  }
  return this.request({
    path: '/_all_docs',
    query: query
  }, cb);
};

Db.prototype.allDocsBySeq = function(query, cb) {
  if (!cb && typeof(query) === "function") {
    cb = query;
    query = null;
  }
  return this.request({
    path: '/_all_docs_by_seq',
    query: query
  }, cb);
};

Db.prototype.compact = function(design, cb) {
  if (!cb && typeof(design) === "function") {
    cb = design;
    design = null;
  }
  return this.request({
    method: 'POST',
    path: '/_compact'+(design ? '/'+design : ''),
  }, cb);
};

Db.prototype.tempView = function(data, query, cb) {
  return this.request({
    method: 'POST',
    path: '/_temp_view',
    data: data,
    query: query
  }, cb);
};

Db.prototype.viewCleanup = function(cb) {
  return this.request({
    method: 'POST',
    path: '/_view_cleanup',
  }, cb);
};

Db.prototype.view = function(design, view, query, cb) {
  return this.request({
    path: ['', ensureDesignId(design), '_view', view].join('/'),
    query: query
  }, cb);
};

Db.prototype.changes = function(query, cb) {
  return this.request({
    path: '/_changes',
    query: query
  }, cb);
};

Db.prototype.changesStream = function(query, options) {
  query = mixin({
    feed: "continuous",
    heartbeat: 1 * 1000
  }, query || {});

  options = mixin({
    timeout: 0,
  }, options);

  var
    stream = new process.EventEmitter(),
    client = http.createClient(this.client.port, this.client.host),
    path = '/'+this.name+'/_changes?'+exports.toQuery(query),
    headers = {'Host': this.client.host},
    request = client.request('GET', path, headers),
    buffer = '';

  client.setTimeout(options.timeout);
  request.addListener("response", function(res) {
    res.addListener('data', function(chunk) {
      buffer += (chunk || '');

      var offset, change;
      while ((offset = buffer.indexOf("\n")) >= 0) {
        change = buffer.substr(0, offset);
        buffer = buffer.substr(offset +1);

        // Couch sends an empty line as the "heartbeat"
        if (change == '') {
          return stream.emit('heartbeat');
        }

        try {
          change = JSON.parse(change);
        } catch (e) {
          return stream.emit('error', 'invalid json: '+change);
        }

        stream.emit('data', change);
      }
    })
  });
  request.end();

  client.addListener('close', function(hadError) {
    stream.emit('end', hadError);
  });

  stream.close = function() {
    return client.destroy();
  };

  return stream;
};
<commit_msg>use ssl whn on port 443<commit_after>var
  sys = require("sys"),
  events = require("events"),
  http = require('http'),
  fs = require('fs'),
  path = require('path'),
  sys = require('sys'),
  querystring = require('querystring'),
  mime = require('./dep/mime'),
  base64 = require('./dep/base64');

function mixin() {
  // copy reference to target object
  var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source;

  // Handle a deep copy situation
  if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }

  // Handle case when target is a string or something (possible in deep copy)
  if ( typeof target !== "object" && !(typeof target === 'function') )
    target = {};

  // mixin process itself if only one argument is passed
  if ( length == i ) {
    target = GLOBAL;
    --i;
  }

  for ( ; i < length; i++ ) {
    // Only deal with non-null/undefined values
    if ( (source = arguments[i]) != null ) {
      // Extend the base object
      Object.getOwnPropertyNames(source).forEach(function(k){
        var d = Object.getOwnPropertyDescriptor(source, k) || {value: source[k]};
        if (d.get) {
          target.__defineGetter__(k, d.get);
          if (d.set) {
            target.__defineSetter__(k, d.set);
          }
        }
        else {
          // Prevent never-ending loop
          if (target === d.value) {
            continue;
          }

          if (deep && d.value && typeof d.value === "object") {
            target[k] = mixin(deep,
              // Never move original objects, clone them
              source[k] || (d.value.length != null ? [] : {})
            , d.value);
          }
          else {
            target[k] = d.value;
          }
        }
      });
    }
  }
  // Return the modified object
  return target;
}

// Stringify function embedded inside of objects. Useful for couch views
exports.toJSON = function(data) {
  return JSON.stringify(data, function(key, val) {
    if (typeof val == 'function') {
      return val.toString();
    }
    return val;
  });
};

// Encode only key, startkey and endkey as JSON
exports.toQuery = function(query) {
  for (var k in query) {
    if (['key', 'startkey', 'endkey'].indexOf(k) != -1) {
      query[k] = JSON.stringify(query[k]);
    } else {
      query[k] = String(query[k]);
    }
  }
  return querystring.stringify(query);
};

// Helps turning a file into an inline CouchDB attachment
exports.toAttachment = function(file, cb) {
  fs.readFile(file, 'binary', function (er, data) {
    if (er) return cb && cb(er, data);
    var ext = path.extname(file).substr(1);
    cb && cb(null, {
      content_type: mime.lookup(ext),
      data: base64.encode(data)
    });
  })
};

exports.createClient = function(port, host, user, pass) {
  if (isNaN(port)) {
    host = port;
    port = 5984;
  }
  host = host || "localhost";

  var
    httpClient = http.createClient(port, host),
    couchClient = new Client();

  couchClient.__defineGetter__('host', function() {
    return host;
  });

  couchClient.__defineGetter__('port', function() {
    return port;
  });

  couchClient._addClientListener = function() {
	  httpClient.addListener.apply(httpClient, arguments);
  };

  couchClient._queueRequest = function(options, cb) {
    if(user && pass) {
      options.headers.authorization = "Basic " + base64.encode(user + ":" + pass)
    }

    if (options.query) {
      options.path += '?'+exports.toQuery(options.query);
      delete options.query;
    }

    // CouchDB 1.0 requires application/json for _bulk_docs and other POST APIs.
    options.headers['Content-Type'] = options.headers['Content-Type'] || 'application/json';

    var
      request = httpClient.request(
        options.method.toUpperCase(),
        options.path,
        options.headers
      ),
      cbFired = false,
      onClose = function(hadError, reason) {
        if (hadError && !cbFired) cb && cb(new Error(reason));
        httpClient.removeListener('close', onClose);
      };

    httpClient.addListener('close', onClose);

    if (options.data && typeof options.data != 'string') {
      options.data = exports.toJSON(options.data);
    }

    if (options.data) {
      request.write(options.data, options.requestEncoding || 'utf8');
    }
    request.addListener("response", function(res) {
      var buffer = '';
      res.setEncoding(options.responseEncoding || 'utf8');
      res
        .addListener('data', function(chunk) {
          buffer += (chunk || '');
        })
        .addListener('end', function() {
          if(options.responseEncoding == 'binary') {
            cbFired = true;
            return cb && cb(null, buffer);
          }

          var json;
          try {
            json = JSON.parse(buffer);
          } catch (e) {
            cbFired = true;
            return cb && cb(new Error('invalid json: '+json+" "+e.message));
          }

          if ('error' in json) {
            cbFired = true;
            return cb && cb(json);
          }

          if (!options.full) {
            cbFired = true;
            return cb && cb(null, json);
          }

          cbFired = true;
          cb && cb(null, {
            headers: res.headers,
            json: json,
          });
        });

    });
    request.end();
  };

  return couchClient;
};

var Client = exports.Client = function() {

};

function requestOptions (args) {
  var options;
  // might get between 0 and 3 strings,
  // and then a function
  var args = Array.prototype.slice.call(args, 0);
  cb = (typeof args[args.length-1] === "function") ? args.pop() : null;
  // sys.debug(args.length);
  // sys.debug(sys.inspect(args));

  var method = args.shift(),
    path = args.shift(),
    data = args.shift();

  if (typeof method == 'object') {
    options = method;
  } else if (typeof method == 'string' && typeof path != 'string') {
    options = {
      path: method,
      query: path
    };
  } else {
    options = {
      method: method,
      path: path,
      data: data
    }
  }
  options.cb = cb;
  // sys.debug("requestOptions2 "+sys.inspect(options));
  return options;
}

Client.prototype.addListener = function(type, fn) {
	if (type == 'error' || type == 'timeout') {
		this._addClientListener(type, fn);
	} else {
		throw new Error('CouchDB client does not support the ' + type + ' event.');
	}
}

Client.prototype.request = function(method, path, data, cb) {
  var
    defaults = {
      method: 'get',
      path: '/',
      headers: {},
      data: null,
      query: null,
      full: false
    },
    options = requestOptions(arguments);

  options = mixin(defaults, options);
  options.headers.host = options.headers.host || this.host;

  return this._queueRequest(options, options.cb);
};

Client.prototype.allDbs = function(cb) {
  return this.request({
    path: '/_all_dbs'
  }, cb);
};

Client.prototype.config = function(cb) {
  return this.request({
    path: '/_config'
  }, cb);
};

Client.prototype.uuids = function(count, cb) {
  if (!cb && typeof(count) === "function") {
    cb = count;
    count = "";
  }
  return this.request({
    path: '/_uuids'+(count ? '?count='+count : '')
  }, cb);
};

Client.prototype.replicate = function(source, target, options, cb) {
  if (!cb && typeof(options) === "function") {
    cb = options;
    options = {};
  }
  options = mixin({
    source: source,
    target: target,
  }, options || {});

  return this.request({
    method: 'POST',
    path: '/_replicate',
    data: options
  }, cb);
};

Client.prototype.stats = function() {
  var args = Array.prototype.slice.call(arguments),
    cb = (typeof args[args.length - 1] === "function") ? args.pop() : null;
  return this.request({
    path: '/_stats'+((args) ? '/'+args.join('/') : '')
  }, cb);
};

Client.prototype.activeTasks = function(cb) {
  return this.request({
    path: '/_active_tasks'
  }, cb);
};

Client.prototype.db = function(name) {
  var
    couchClient = this,
    couchDb = new Db();

  couchDb.__defineGetter__('name', function() {
    return name;
  });

  couchDb.__defineGetter__('client', function() {
    return couchClient;
  });

  couchDb.request = function(method, path, data, cb) {
    var options = requestOptions(arguments);
    options.path = '/'+name+(options.path || '');
    return couchClient.request(options, options.cb);
  };

  return couchDb;
};

var Db = exports.Db = function() {

};

function ensureDesignId(id) {
  if (!id || id.match(/^_design\//)) { return id; }
  return '_design/' + id;
}

Db.prototype.exists = function(cb) {
  this.request({path: ''}, function (er) {
    // if it was an error, then that's a "no" on the existing.
    cb && cb(null, !(er && er.error == 'not_found'));
  });
};

Db.prototype.info = function(cb) {
  return this.request({}, cb);
};

Db.prototype.create = function(cb) {
  return this.request({
    method: 'PUT'
  }, cb);
};

Db.prototype.remove = function(cb) {
  return this.request('DELETE', '', cb);
};

Db.prototype.getDoc = function(id, cb) {
  return this.request({
    path: '/'+id
  }, cb);
};

Db.prototype.saveDoc = function(id, doc, cb) {
  if (typeof id == 'object') {
    cb = doc;
    return this.request({
      method: 'POST',
      path: '/',
      data: id,
    }, cb);
  }

  return this.request({
    method: 'PUT',
    path: '/'+id,
    data: doc,
  }, cb);
};

Db.prototype.removeDoc = function(id, rev, cb) {
  return this.request({
    method: 'DELETE',
    path: '/'+id,
    query: {rev: rev}
  }, cb);
};

Db.prototype.copyDoc = function(srcId, destId, destRev, cb) {
  if (!cb && typeof(destRev) === "function") {
    cb = destRev;
    destRev = null;
  }
  if (destRev) {
    destId += '?rev='+destRev;
  }

  return this.request({
    method: 'COPY',
    path: '/'+srcId,
    headers: {
      'Destination': destId
    }
  }, cb);
}

Db.prototype.bulkDocs = function(data, cb) {
  if (!cb && typeof(data) === "function") {
    cb = data;
    data = null;
  }
  return this.request({
    method: 'POST',
    path: '/_bulk_docs',
    data: data,
  }, cb);
};

Db.prototype.saveDesign = function(design, doc, cb) {
  if (typeof design == 'object') {
    design._id = ensureDesignId(design._id);
    return this.saveDoc(design, doc, cb);
  }

  return this.saveDoc(ensureDesignId(design), doc, cb);
};

Db.prototype.saveAttachment = function(file, docId, options, cb) {
  var
    self = this,
    ext = path.extname(file).substr(1);

  if (!cb && typeof(options) === "function") {
    cb = options;
    options = {};
  }

  options = mixin({
    name: path.basename(file),
    contentType: mime.lookup(ext),
    rev: null
  }, options || {});

  // We could stream the file here, but I doubt people store big enough files
  // in couch to make this worth it?
  fs.readFile(file, 'binary', function (er, data) {
    if (er) return cb && cb(er);
    self.request({
      method: 'PUT',
      requestEncoding: 'binary',
      path: '/'+docId+'/'+options.name+(options.rev ? '?rev='+options.rev : ''),
      headers: {
        'Content-Type': options.contentType
      },
      data: data,
    }, cb);
  });
};

Db.prototype.removeAttachment = function(docId, attachmentId, docRev, cb) {
  return this.request({
    method: 'DELETE',
    path: '/'+docId+'/'+attachmentId,
    query: {rev: docRev}
  }, cb)
};

Db.prototype.getAttachment = function(docId, attachmentId, cb) {
  return this.request({
    path: '/'+docId+'/'+attachmentId,
    responseEncoding: 'binary',
  }, cb);
};

Db.prototype.allDocs = function(query, cb) {
  if (!cb && typeof(query) === "function") {
    cb = query;
    query = null;
  }
  return this.request({
    path: '/_all_docs',
    query: query
  }, cb);
};

Db.prototype.allDocsBySeq = function(query, cb) {
  if (!cb && typeof(query) === "function") {
    cb = query;
    query = null;
  }
  return this.request({
    path: '/_all_docs_by_seq',
    query: query
  }, cb);
};

Db.prototype.compact = function(design, cb) {
  if (!cb && typeof(design) === "function") {
    cb = design;
    design = null;
  }
  return this.request({
    method: 'POST',
    path: '/_compact'+(design ? '/'+design : ''),
  }, cb);
};

Db.prototype.tempView = function(data, query, cb) {
  return this.request({
    method: 'POST',
    path: '/_temp_view',
    data: data,
    query: query
  }, cb);
};

Db.prototype.viewCleanup = function(cb) {
  return this.request({
    method: 'POST',
    path: '/_view_cleanup',
  }, cb);
};

Db.prototype.view = function(design, view, query, cb) {
  return this.request({
    path: ['', ensureDesignId(design), '_view', view].join('/'),
    query: query
  }, cb);
};

Db.prototype.changes = function(query, cb) {
  return this.request({
    path: '/_changes',
    query: query
  }, cb);
};

Db.prototype.changesStream = function(query, options) {
  query = mixin({
    feed: "continuous",
    heartbeat: 1 * 1000
  }, query || {});

  options = mixin({
    timeout: 0,
  }, options);

  var
    stream = new process.EventEmitter(),
    client = http.createClient(this.client.port, this.client.host, this.client.port == 443),
    path = '/'+this.name+'/_changes?'+exports.toQuery(query),
    headers = {'Host': this.client.host},
    request = client.request('GET', path, headers),
    buffer = '';

  client.setTimeout(options.timeout);
  request.addListener("response", function(res) {
    res.addListener('data', function(chunk) {
      buffer += (chunk || '');

      var offset, change;
      while ((offset = buffer.indexOf("\n")) >= 0) {
        change = buffer.substr(0, offset);
        buffer = buffer.substr(offset +1);

        // Couch sends an empty line as the "heartbeat"
        if (change == '') {
          return stream.emit('heartbeat');
        }

        try {
          change = JSON.parse(change);
        } catch (e) {
          return stream.emit('error', 'invalid json: '+change);
        }

        stream.emit('data', change);
      }
    })
  });
  request.end();

  client.addListener('close', function(hadError) {
    stream.emit('end', hadError);
  });

  stream.close = function() {
    return client.destroy();
  };

  return stream;
};
<|endoftext|>


<commit_before>/*global Buffer: false */

/**
 * Module dependencies
 */

var url = require('url'),
    http = require('http'),
    logger = require('./logger'),
    querystring = require('querystring');

var STATUS_MSGS = {
    400: '400: Bad Request',
    401: '401: Unauthorized',
    402: '402: Payment Required',
    403: '403: Forbidden',
    404: '404: Not Found',
    405: '405: Method Not Allowed',
    406: '406: Not Acceptable',
    407: '407: Proxy Authentication Required',
    408: '408: Request Timeout',
    409: '409: Conflict',
    410: '410: Gone',
    411: '411: Length Required',
    412: '412: Precondition Failed',
    413: '413: Request Entity Too Large',
    414: '414: Request-URI Too Long',
    415: '415: Unsupported Media Type',
    416: '416: Requested Range Not Satisfiable',
    417: '417: Expectation Failed',
    418: '418: I\'m a teapot',
    422: '422: Unprocessable Entity',
    423: '423: Locked',
    424: '424: Failed Dependency',
    425: '425: Unordered Collection',
    444: '444: No Response',
    426: '426: Upgrade Required',
    449: '449: Retry With',
    450: '450: Blocked by Windows Parental Controls',
    499: '499: Client Closed Request',
    500: '500: Internal Server Error',
    501: '501: Not Implemented',
    502: '502: Bad Gateway',
    503: '503: Service Unavailable',
    504: '504: Gateway Timeout',
    505: '505: HTTP Version Not Supported',
    506: '506: Variant Also Negotiates',
    507: '507: Insufficient Storage',
    509: '509: Bandwidth Limit Exceeded',
    510: '510: Not Extended'
};

var CouchDB;

/**
 * Convenience method for creating a CouchDB object instance.
 *
 * @param {String} db_url
 * @api public
 */

var exports = module.exports = function (db_url) {
    return new CouchDB(db_url);
};

/**
 * The CouchDB object constructor.
 *
 * @class CouchDB
 * @constructor
 * @param {String} db_url
 * @api public
 */

CouchDB = exports.CouchDB = function (db_url) {
    var ins = this.instance = url.parse(db_url);
    if (!ins.port) {
        if (ins.protocol === 'https:') {
            ins.port = 443;
        }
        else {
            ins.port = 80;
        }
    }
};

/**
 * Tests if a database exists, creates it if not.
 *
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.ensureDB = function (callback) {
    var that = this;
    this.exists('', function (err, exists) {
        if (err || exists) {
            return callback(err, that);
        }
        that.createDB(callback);
    });
};

/**
 * Creates a database.
 *
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.createDB = function (callback) {
    this.client('PUT', '', null, callback);
};

/**
 * Convenience HTTP client for querying a CouchDB instance. Buffers and parses
 * JSON responses before passing to callback. JSON.stringify's data before
 * sending.
 *
 * @param {String} method
 * @param {String} path
 * @param data
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.client = function (method, path, data, callback) {
    path = (this.instance.pathname || '') + '/' + path;

    var headers = {
        'Host': this.instance.hostname,
        'Accept': 'application/json'
    };
    if (method === 'POST' || method === 'PUT') {
        if (typeof data !== 'string') {
            try {
                data = JSON.stringify(data);
            }
            catch (e) {
                return callback(e);
            }
        }
        headers['Content-Type'] = 'application/json';
    }
    else if (data) {
        path = url.parse(path).pathname + '?' + querystring.stringify(data);
    }

    if (this.instance.auth) {
        var enc = new Buffer(this.instance.auth).toString('base64');
        headers.Authorization = "Basic " + enc;
    }

    var client = http.createClient(this.instance.port, this.instance.hostname);
    client.on('error', callback);
    var request = client.request(method, path, headers);

    request.on('response', function (response) {
        logger.debug('response:', {
            headers: response.headers,
            url: response.url,
            method: response.method,
            statusCode: response.statusCode
        });
        var buffer = [];
        response.on('data', function (chunk) {
            buffer.push(chunk.toString());
        });
        response.on('end', function () {
            var data = buffer.length ? JSON.parse(buffer.join('')): null;
            logger.debug('data:', data);
            if (response.statusCode >= 300) {
                if (data && data.error) {
                    //var err = new Error(data.reason || data.error);
                    var err = new Error(data.reason || data.error);
                    err.error = data.error;
                    err.reason = data.reason;
                    err.response = response;
                    callback(err, data, response);
                }
                else {
                    var err2 = exports.statusCodeError(response.statusCode);
                    err2.response = response;
                    callback(err2, data, response);
                }
            }
            else {
                process.nextTick(function () {
                    callback(null, data, response);
                });
            }
        });
    });

    if (data && (method === 'POST' || method === 'PUT')) {
        request.write(data, 'utf8');
    }
    request.end();

    logger.debug('request:', request.output[0]);
};

/**
 * Creates an error object with a message depending on the HTTP status code
 * of a response.
 */

exports.statusCodeError = function (code) {
    if (code in STATUS_MSGS) {
        return new Error(STATUS_MSGS[code]);
    }
    return new Error('Status code: ' + code);
};

/**
 * Test if a doc exists in the db by doing a HEAD request - doesn't fetch
 * the whole document.
 *
 * @param {String} id
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.exists = function (id, callback) {
    id = id || '';
    this.client('HEAD', id, null, function (err, data, res) {
        res = res || {};
        if (res.statusCode !== 404 && err) {
            return callback(err);
        }
        var exists = (res.statusCode === 200);
        var etag = res.headers.etag;
        var _rev = etag ? etag.substr(1, etag.length - 2): null;
        callback(null, exists, _rev);
    });
};

/**
 * Retrieve a document from a CouchDB instance.
 *
 * @param {String} id
 * @param {Object} data
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.get = function (id, /*optional*/data, callback) {
    if (arguments.length < 3) {
        callback = data;
        data = null;
    }
    this.client('GET', (id || ''), data, callback);
};

/**
 * Saves a document to a CouchDB instance.
 *
 * Options:
 *      {Boolean} force - write document regardless of conflicts!
 *
 * @param {String} id
 * @param {Object} doc
 * @param {Object} options
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.save = function (id, doc, /*optional*/options, callback) {
    var that = this;

    if (!callback) {
        callback = options;
        options = {};
    }
    var method = id ? 'PUT': 'POST';
    var path = id || '';

    if (options.force && id) {
        // WARNING! this is a brute-force document update
        // updates revision number to latest revision before saving
        this.exists(id, function (err, exists, rev) {
            if (err) {
                return callback(err);
            }
            if (exists) {
                doc._rev = rev;
            }
            that.client(method, path, doc, function (err, d) {
                if (err) {
                    return callback(err);
                }
                doc._id = d.id;
                doc._rev = d.rev;
                callback(null, doc);
            });
        });
    }
    else {
        this.client(method, path, doc, callback);
    }
};

/**
 * Deletes a document from a CouchDB instance.
 *
 * Options:
 *      {Boolean} force - delete document regardless of conflicts!
 *
 * @param {String} id
 * @param {Object} rev
 * @param {Object} options
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.delete = function (id, rev, /*optional*/options, callback) {
    var that = this;

    if (!callback) {
        callback = options;
        options = {};
    }
    var args = {};
    if (rev) {
        args.rev = rev;
    }
    var path = id || '';

    if (options.force) {
        // WARNING! this is a brute-force document delete
        // updates revision number to latest revision before deleting
        this.exists(id, function (err, exists, rev) {
            if (err) {
                return callback(err);
            }
            if (exists) {
                args.rev = rev;
            }
            that.client('DELETE', path, args, callback);
        });
    }
    else {
        this.client('DELETE', path, args, callback);
    }
};

CouchDB.prototype.uuids = function (count, callback) {
    if (!callback) {
        callback = count;
        count = undefined;
    }
    count = count || 1;
    this.client('GET', '_uuids', {count: count}, function (err, data) {
        callback(err, data.uuids);
    });
};
<commit_msg>improved couchdb error messages by including both error and reason<commit_after>/*global Buffer: false */

/**
 * Module dependencies
 */

var url = require('url'),
    http = require('http'),
    logger = require('./logger'),
    querystring = require('querystring');

var STATUS_MSGS = {
    400: '400: Bad Request',
    401: '401: Unauthorized',
    402: '402: Payment Required',
    403: '403: Forbidden',
    404: '404: Not Found',
    405: '405: Method Not Allowed',
    406: '406: Not Acceptable',
    407: '407: Proxy Authentication Required',
    408: '408: Request Timeout',
    409: '409: Conflict',
    410: '410: Gone',
    411: '411: Length Required',
    412: '412: Precondition Failed',
    413: '413: Request Entity Too Large',
    414: '414: Request-URI Too Long',
    415: '415: Unsupported Media Type',
    416: '416: Requested Range Not Satisfiable',
    417: '417: Expectation Failed',
    418: '418: I\'m a teapot',
    422: '422: Unprocessable Entity',
    423: '423: Locked',
    424: '424: Failed Dependency',
    425: '425: Unordered Collection',
    444: '444: No Response',
    426: '426: Upgrade Required',
    449: '449: Retry With',
    450: '450: Blocked by Windows Parental Controls',
    499: '499: Client Closed Request',
    500: '500: Internal Server Error',
    501: '501: Not Implemented',
    502: '502: Bad Gateway',
    503: '503: Service Unavailable',
    504: '504: Gateway Timeout',
    505: '505: HTTP Version Not Supported',
    506: '506: Variant Also Negotiates',
    507: '507: Insufficient Storage',
    509: '509: Bandwidth Limit Exceeded',
    510: '510: Not Extended'
};

var CouchDB;

/**
 * Convenience method for creating a CouchDB object instance.
 *
 * @param {String} db_url
 * @api public
 */

var exports = module.exports = function (db_url) {
    return new CouchDB(db_url);
};

/**
 * The CouchDB object constructor.
 *
 * @class CouchDB
 * @constructor
 * @param {String} db_url
 * @api public
 */

CouchDB = exports.CouchDB = function (db_url) {
    var ins = this.instance = url.parse(db_url);
    if (!ins.port) {
        if (ins.protocol === 'https:') {
            ins.port = 443;
        }
        else {
            ins.port = 80;
        }
    }
};

/**
 * Tests if a database exists, creates it if not.
 *
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.ensureDB = function (callback) {
    var that = this;
    this.exists('', function (err, exists) {
        if (err || exists) {
            return callback(err, that);
        }
        that.createDB(callback);
    });
};

/**
 * Creates a database.
 *
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.createDB = function (callback) {
    this.client('PUT', '', null, callback);
};

/**
 * Convenience HTTP client for querying a CouchDB instance. Buffers and parses
 * JSON responses before passing to callback. JSON.stringify's data before
 * sending.
 *
 * @param {String} method
 * @param {String} path
 * @param data
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.client = function (method, path, data, callback) {
    path = (this.instance.pathname || '') + '/' + path;

    var headers = {
        'Host': this.instance.hostname,
        'Accept': 'application/json'
    };
    if (method === 'POST' || method === 'PUT') {
        if (typeof data !== 'string') {
            try {
                data = JSON.stringify(data);
            }
            catch (e) {
                return callback(e);
            }
        }
        headers['Content-Type'] = 'application/json';
    }
    else if (data) {
        path = url.parse(path).pathname + '?' + querystring.stringify(data);
    }

    if (this.instance.auth) {
        var enc = new Buffer(this.instance.auth).toString('base64');
        headers.Authorization = "Basic " + enc;
    }

    var client = http.createClient(this.instance.port, this.instance.hostname);
    client.on('error', callback);
    var request = client.request(method, path, headers);

    request.on('response', function (response) {
        logger.debug('response:', {
            headers: response.headers,
            url: response.url,
            method: response.method,
            statusCode: response.statusCode
        });
        var buffer = [];
        response.on('data', function (chunk) {
            buffer.push(chunk.toString());
        });
        response.on('end', function () {
            var data = buffer.length ? JSON.parse(buffer.join('')): null;
            logger.debug('data:', data);
            if (response.statusCode >= 300) {
                if (data && data.error) {
                    //var err = new Error(data.reason || data.error);
                    var err = new Error(
                        data.error + (data.reason ? '\n' + data.reason: '')
                    );
                    err.error = data.error;
                    err.reason = data.reason;
                    err.response = response;
                    callback(err, data, response);
                }
                else {
                    var err2 = exports.statusCodeError(response.statusCode);
                    err2.response = response;
                    callback(err2, data, response);
                }
            }
            else {
                process.nextTick(function () {
                    callback(null, data, response);
                });
            }
        });
    });

    if (data && (method === 'POST' || method === 'PUT')) {
        request.write(data, 'utf8');
    }
    request.end();

    logger.debug('request:', request.output[0]);
};

/**
 * Creates an error object with a message depending on the HTTP status code
 * of a response.
 */

exports.statusCodeError = function (code) {
    if (code in STATUS_MSGS) {
        return new Error(STATUS_MSGS[code]);
    }
    return new Error('Status code: ' + code);
};

/**
 * Test if a doc exists in the db by doing a HEAD request - doesn't fetch
 * the whole document.
 *
 * @param {String} id
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.exists = function (id, callback) {
    id = id || '';
    this.client('HEAD', id, null, function (err, data, res) {
        res = res || {};
        if (res.statusCode !== 404 && err) {
            return callback(err);
        }
        var exists = (res.statusCode === 200);
        var etag = res.headers.etag;
        var _rev = etag ? etag.substr(1, etag.length - 2): null;
        callback(null, exists, _rev);
    });
};

/**
 * Retrieve a document from a CouchDB instance.
 *
 * @param {String} id
 * @param {Object} data
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.get = function (id, /*optional*/data, callback) {
    if (arguments.length < 3) {
        callback = data;
        data = null;
    }
    this.client('GET', (id || ''), data, callback);
};

/**
 * Saves a document to a CouchDB instance.
 *
 * Options:
 *      {Boolean} force - write document regardless of conflicts!
 *
 * @param {String} id
 * @param {Object} doc
 * @param {Object} options
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.save = function (id, doc, /*optional*/options, callback) {
    var that = this;

    if (!callback) {
        callback = options;
        options = {};
    }
    var method = id ? 'PUT': 'POST';
    var path = id || '';

    if (options.force && id) {
        // WARNING! this is a brute-force document update
        // updates revision number to latest revision before saving
        this.exists(id, function (err, exists, rev) {
            if (err) {
                return callback(err);
            }
            if (exists) {
                doc._rev = rev;
            }
            that.client(method, path, doc, function (err, d) {
                if (err) {
                    return callback(err);
                }
                doc._id = d.id;
                doc._rev = d.rev;
                callback(null, doc);
            });
        });
    }
    else {
        this.client(method, path, doc, callback);
    }
};

/**
 * Deletes a document from a CouchDB instance.
 *
 * Options:
 *      {Boolean} force - delete document regardless of conflicts!
 *
 * @param {String} id
 * @param {Object} rev
 * @param {Object} options
 * @param {Function} callback
 * @api public
 */

CouchDB.prototype.delete = function (id, rev, /*optional*/options, callback) {
    var that = this;

    if (!callback) {
        callback = options;
        options = {};
    }
    var args = {};
    if (rev) {
        args.rev = rev;
    }
    var path = id || '';

    if (options.force) {
        // WARNING! this is a brute-force document delete
        // updates revision number to latest revision before deleting
        this.exists(id, function (err, exists, rev) {
            if (err) {
                return callback(err);
            }
            if (exists) {
                args.rev = rev;
            }
            that.client('DELETE', path, args, callback);
        });
    }
    else {
        this.client('DELETE', path, args, callback);
    }
};

CouchDB.prototype.uuids = function (count, callback) {
    if (!callback) {
        callback = count;
        count = undefined;
    }
    count = count || 1;
    this.client('GET', '_uuids', {count: count}, function (err, data) {
        callback(err, data.uuids);
    });
};
<|endoftext|>


<commit_before>// Buffer + position + global start position ( used in alignment )
function DBusBuffer(buffer, startPos, options)
{
  this.options = options || { ayBuffer: true };
  this.buffer = buffer;
  this.startPos = startPos ? startPos : 0,
  this.pos = 0;
}

DBusBuffer.prototype.align = function(power) {
  var allbits = (1<<power) - 1;
  var paddedOffset = ((this.pos + this.startPos + allbits) >> power) << power;
  this.pos = paddedOffset - this.startPos;

  // debug: re-enable when dealing with OOB errors
  //if (this.pos >= this.buffer.length) {
  //  console.log(require('./hexy').hexy(this.buffer, {prefix: 'bad pos '}))
  //}
}

DBusBuffer.prototype.readInt8 = function() {
  this.pos++;
  return this.buffer[this.pos - 1];
}

DBusBuffer.prototype.readSInt16 = function() {
  this.align(1);
  var res = this.buffer.readInt16LE(this.pos);
  this.pos += 2;
  return res;
}

DBusBuffer.prototype.readInt16 = function() {
  this.align(1);
  var res = this.buffer.readUInt16LE(this.pos);
  this.pos += 2;
  return res;
}

DBusBuffer.prototype.readSInt32 = function() {
  this.align(2);
  var res = this.buffer.readInt32LE(this.pos);
  this.pos += 4;
  return res;
}

DBusBuffer.prototype.readInt32 = function() {
  this.align(2);
  var res = this.buffer.readUInt32LE(this.pos);
  this.pos += 4;
  return res;
}

DBusBuffer.prototype.readString = function(len) {
  var res = this.buffer.toString('utf8', this.pos, this.pos + len);
  this.pos += len + 1; // dbus strings are always zero-terminated ('s' and 'g' types)
  return res;
}

DBusBuffer.prototype.readTree = function readTree(tree) {
  switch (tree.type) {
  case '(':
  case '{':
    this.align(3);
    return this.readStruct(tree.child);
  case 'a':
    if (!tree.child || tree.child.length != 1)
      throw new Error('Incorrect array element signature');
    var arrayBlobLength = this.readInt32();
    // is initial aligh part of arraySize?
    //if (['x', 't', 'd', '{', '('].indexOf(tree.child[0].type) != -1)
    //  this.align(3);
    return this.readArray(tree.child[0], arrayBlobLength);
    case 'v':
      return this.readVariant();
    default:
      return this.readSimpleType(tree.type);
  }
}

var parseSignature = require('./signature.js');

DBusBuffer.prototype.read = function read(signature) {
  var tree = parseSignature(signature);
  return this.readStruct(tree);
}

DBusBuffer.prototype.readVariant = function readVariant() {
  var signature = this.readSimpleType('g');
  return this.read(signature);
}

DBusBuffer.prototype.readStruct = function readStruct(struct) {
  // TODO: special case for a{sX} - save as JSON obj

  var result = [];
  for (var i = 0; i < struct.length; ++i)
    result.push(this.readTree(struct[i]));
  return result;
}

DBusBuffer.prototype.readArray = function readArray(eleType, arrayBlobSize) {
  var start = this.pos;

  // special case: treat ay as Buffer
  if (eleType.type === 'y' && this.options.ayBuffer) {
    this.pos += arrayBlobSize;
    return this.buffer.slice(start, this.pos);
  }

  var result = [];
  while(this.pos < start + arrayBlobSize)
    result.push(this.readTree(eleType));
  return result;
}

DBusBuffer.prototype.readSimpleType = function readSimpleType(t) {
  switch (t) {
  case 'y':
    return this.readInt8();
  case 'b':
    // TODO: spec says that true is strictly 1 and false is strictly 0
    // shold we error (or warn?) when non 01 values?
    return this.readInt32() ? true : false;
  case 'n':
    return this.readSInt16();
  case 'q':
    return this.readInt16();
  case 'u':
    return this.readInt32();
  case 'i':
    return this.readSInt32();
  case 'g':
    var len = this.readInt8();
    return this.readString(len);
  case 's':
  case 'o':
    var len = this.readInt32();
    return this.readString(len);
    // TODO: validate object path here
    //if (t === 'o' && !isValidObjectPath(str))
    //  throw new Error('string is not a valid object path'));
  case 'x': //signed
    this.align(3);
    this.pos += 8;
    throw new Error('Signed 64 bit (x) not implemented')
    return 0;
  case 't': //unsigned
    this.align(3);
    this.pos += 8;
    throw new Error('Unsigned 64 bit (t) not implemented')
    return 0; // TODO!!!!
  case 'd':
    return this.readDouble();
  default:
    throw new Error('Unsupported type:' + t);
  }
}

module.exports = DBusBuffer;
<commit_msg>64 bit types support; fix in array length calculation<commit_after>var isSimpleType = {};
'ybnquigsoxtd'.split(' ').forEach(function(t) {
  isSimpleType[t] = true;
});

// Buffer + position + global start position ( used in alignment )
function DBusBuffer(buffer, startPos, options)
{
  this.options = options || { ayBuffer: true };
  this.buffer = buffer;
  this.startPos = startPos ? startPos : 0,
  this.pos = 0;
}

DBusBuffer.prototype.align = function(power) {
  var allbits = (1<<power) - 1;
  var paddedOffset = ((this.pos + this.startPos + allbits) >> power) << power;
  this.pos = paddedOffset - this.startPos;
}

DBusBuffer.prototype.readInt8 = function() {
  this.pos++;
  return this.buffer[this.pos - 1];
}

DBusBuffer.prototype.readSInt16 = function() {
  this.align(1);
  var res = this.buffer.readInt16LE(this.pos);
  this.pos += 2;
  return res;
}

DBusBuffer.prototype.readInt16 = function() {
  this.align(1);
  var res = this.buffer.readUInt16LE(this.pos);
  this.pos += 2;
  return res;
}

DBusBuffer.prototype.readSInt32 = function() {
  this.align(2);
  var res = this.buffer.readInt32LE(this.pos);
  this.pos += 4;
  return res;
}

DBusBuffer.prototype.readInt32 = function() {
  this.align(2);
  var res = this.buffer.readUInt32LE(this.pos);
  this.pos += 4;
  return res;
}

DBusBuffer.prototype.readDouble = function() {
  this.align(3);
  var res = this.buffer.readDoubleLE(this.pos);
  this.pos += 8;
  return res;
}

DBusBuffer.prototype.readString = function(len) {
  if (len === 0) {
    this.pos++;
    return '';
  }
  var res = this.buffer.toString('utf8', this.pos, this.pos + len);
  this.pos += len + 1; // dbus strings are always zero-terminated ('s' and 'g' types)
  return res;
}

var parseSignature = require('./signature.js');

DBusBuffer.prototype.readTree = function readTree(tree) {
  switch (tree.type) {
  case '(':
  case '{':
  case 'r':
    this.align(3);
    return this.readStruct(tree.child);
  case 'a':
    if (!tree.child || tree.child.length != 1)
      throw new Error('Incorrect array element signature');
    var arrayBlobLength = this.readInt32();
    return this.readArray(tree.child[0], arrayBlobLength);
    return arr;
    case 'v':
      return this.readVariant();
    default:
      return this.readSimpleType(tree.type);
  }
}

DBusBuffer.prototype.read = function read(signature) {
  var tree = parseSignature(signature);
  return this.readStruct(tree);
}

DBusBuffer.prototype.readVariant = function readVariant() {
  var signature = this.readSimpleType('g');
  //if (isSimpleType[signature])
  //  return this.readSimpleType(signature);

  //if (signature == 'a{sv}')
  //{
  //  var arrayBlobLength = this.readInt32();
  //  return this.readArray(parseSignature(signatur
  //}
  return this.read(signature);
}

DBusBuffer.prototype.readStruct = function readStruct(struct) {
  var result = [];
  for (var i = 0; i < struct.length; ++i) {
    result.push(this.readTree(struct[i]));
  }
  return result;
}

// check if it's a{sX} s -> any type
// return value type
function isHash(t) {
  if (t.type == '{' && t.child[0].type == 's')
    return t.child[1];
  return null;
}

DBusBuffer.prototype.readArray = function readArray(eleType, arrayBlobSize) {
  var result;
  var key, value;

  /*
  if (isHash(eleType)) {
    result = {};
    while(this.pos < start + arrayBlobSize) {
      this.align(3);
      key = this.readTree(eleType.child[0]);
      result[key] = this.readTree(eleType.child[1]);
    }
    console.log('Hash!!!', eleType, parseSignature.fromTree(eleType), result);
    return result;
  }
  */

  // special case: treat ay as Buffer
  if (eleType.type === 'y' && this.options.ayBuffer) {
    this.pos += arrayBlobSize;
    return this.buffer.slice(start, this.pos);
  }

  // end of array is start of first element + array size
  // we need to add 4 bytes if not on 8-byte boundary
  // and array element needs 8 byte alignment
  if (['x', 't', 'd', '{', '(', 'r'].indexOf(eleType.type) != -1)
    this.align(3);
  var end = this.pos + arrayBlobSize;
  result = [];
  while(this.pos < end)
    result.push(this.readTree(eleType));
  return result;
}

DBusBuffer.prototype.readSimpleType = function readSimpleType(t) {
  switch (t) {
  case 'y':
    return this.readInt8();
  case 'b':
    // TODO: spec says that true is strictly 1 and false is strictly 0
    // shold we error (or warn?) when non 01 values?
    return this.readInt32() ? true : false;
  case 'n':
    return this.readSInt16();
  case 'q':
    return this.readInt16();
  case 'u':
    return this.readInt32();
  case 'i':
    return this.readSInt32();
  case 'g':
    var len = this.readInt8();
    return this.readString(len);
  case 's':
  case 'o':
    var len = this.readInt32();
    return this.readString(len);
    // TODO: validate object path here
    //if (t === 'o' && !isValidObjectPath(str))
    //  throw new Error('string is not a valid object path'));
  case 'x': //signed
    this.align(3);
    // TODO: BE 0x100000000*this.readInt32() + this.readInt32();
    // TODO: use bn.js
    return this.readInt32() + 0x100000000*this.readInt32();
  case 't': //unsigned
    this.align(3);
    var word0 = this.readInt32();
    var word1 = this.readInt32();
    if (!(word1 & 0x80000000))
      return word0 + 0x100000000*word1;
    return -((((~word1)>>>0) * 0x100000000) + ((~word0)>>>0) + 1);
  case 'd':
    return this.readDouble();
  default:
    throw new Error('Unsupported type: ' + t);
  }
  return result;
}

module.exports = DBusBuffer;
<|endoftext|>