<commit_before>/*The MIT License (MIT)

Copyright (c) 2016 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

'use strict';

var vec2 = require('/gl-matrix/vec2.js');
var vec3 = require('/gl-matrix/vec3.js');
var vec4 = require('/gl-matrix/vec4.js');
var mat4 = require('/gl-matrix/mat4.js');
var quat = require('/gl-matrix/quat.js');

class Vector2 extends Float32Array {
  constructor(x, y) {
    var vec = vec2.fromValues(x||0, y||0);
    vec.__proto__ = Vector2.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }
}

class Vector3 extends Float32Array {
  constructor(x, y, z) {
    var vec = vec3.fromValues(x||0, y||0, z||0);
    vec.__proto__ = Vector3.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }

  get z() {
    return this[2];
  }

  set z(value) {
    this[2] = value;
  }

  set(x, y, z) {
    if (arguments.length === 1) {
      vec3.copy(this, arguments[0]);
    } else if (arguments.length === 3) {
      this[0] = x;
      this[1] = y;
      this[2] = z;
    } else {
      throw new TypeError('Wrong number of arguments.');
    }
    return this;
  }

  add(value, result) {
    return vec3.add(result || new Vector3(), this, value);
  }

  subtract(value, result) {
    return vec3.sub(result || new Vector3(), this, value);
  }

  multiply(value, result) {
    return vec3.mul(result || new Vector3(), this, value);
  }

  divide(value, result) {
    return vec3.divide(result || new Vector3(), this, value);
  }

  dot(value) {
    return vec3.dot(this, value);
  }

  cross(value, result) {
    return vec3.cross(result || new Vector3(), this, value);
  }

  scale(value, result) {
    return vec3.scale(result || new Vector3(), this, value);
  }

  transform(value, result) {
    if (value instanceof Matrix) {
      return vec3.transformMat4(result || new Vector3(), this, value);
    }
    if (value instanceof Quaternion) {
      return vec3.transformQuat(result || new Vector3(), this, value);
    }
  }

  negate(result) {
    return vec3.negate(result || new Vector3(), this);
  }

  lerp(value, t, result) {
    return vec3.lerp(result || new Vector3(), value, this, t);
  }

  normalize(result) {
    return vec3.normalize(result || new Vector3(), this);
  }

  len() {
    return vec3.length(this);
  }

  squaredDistance(value) {
    return vec3.squaredDistance(this, value);
  }

  toString() {
    return vec3.str(this);
  }
}

Vector3.prototype.sub = Vector3.prototype.subtract;

var _temp3 = new Vector3();

class Vector4 extends Float32Array {
  constructor(x, y, z, w) {
    var vec = vec4.fromValues(x||0, y||0, z||0, w||0);
    vec.__proto__ = Vector4.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }

  get z() {
    return this[2];
  }

  set z(value) {
    this[2] = value;
  }

  get w() {
    return this[3];
  }

  set w(value) {
    this[3] = value;
  }
}

class Matrix extends Float32Array {
  constructor() {
    var mat = mat4.create();
    mat.__proto__ = Matrix.prototype;
    return mat;
  }

  set(value) {
    return mat4.copy(this, value);
  }

  translate(x, y, z) {
    _temp3.set(x, y, z);
    mat4.translate(this, this, _temp3);
    return this;
  }

  rotate(x, y, z) {
    if (x) {
      mat4.rotateX(this, this, x);
    }
    if (y) {
      mat4.rotateY(this, this, y);
    }
    if (z) {
      mat4.rotateZ(this, this, z);
    }
  }

  scale(x, y, z) {
    _temp3.set(x, y, z);
    mat4.scale(this, this, _temp3);
    return this;
  }

  multiply(value, result) {
    return mat4.multiply(result || new Matrix(), this, value);
  }

  invert(result) {
    return mat4.invert(result || new Matrix(), this);
  }

  toString() {
    return mat4.str(this);
  }

  static perspective(fieldOfView, aspectRatio, near, far, result) {
    return mat4.perspective(
      result || new Matrix(), fieldOfView, aspectRatio, near, far);
  }

  static ortho(left, right, bottom, top, near, far, result) {
    return mat4.ortho(
        result || new Matrix(), left, right, bottom, top, near, far);
  }

  static lookAt(eye, center, up, result) {
    return mat4.lookAt(result || new Matrix(), eye, center, up);
  }
}

class Quaternion extends Float32Array {
  constructor() {
    var qua = quat.create();
    qua.__proto__ = Quaternion.prototype;
    return qua;
  }

  add(value, result) {
    return quat.add(result || new Quaternion(), this, value);
  }

  mul(value, result) {
    return quat.mul(result || new Quaternion(), this, value);
  }

  invert(result) {
    return quat.invert(result || new Quaternion(), this);
  }

  rotateX(value) {
    return quat.rotateX(this, this, value);
  }

  rotateY(value) {
    return quat.rotateY(this, this, value);
  }

  rotateZ(value) {
    return quat.rotateZ(this, this, value);
  }

  toString() {
    return quat.str(this);
  }
}

module.exports.Vector2 = Vector2;
module.exports.Vector3 = Vector3;
module.exports.Vector4 = Vector4;
module.exports.Matrix = Matrix;
module.exports.Quaternion = Quaternion;<commit_msg>Bugfix.<commit_after>/*The MIT License (MIT)

Copyright (c) 2016 Jens Malmborg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/

'use strict';

var vec2 = require('/gl-matrix/vec2.js');
var vec3 = require('/gl-matrix/vec3.js');
var vec4 = require('/gl-matrix/vec4.js');
var mat4 = require('/gl-matrix/mat4.js');
var quat = require('/gl-matrix/quat.js');

class Vector2 extends Float32Array {
  constructor(x, y) {
    var vec = vec2.fromValues(x||0, y||0);
    vec.__proto__ = Vector2.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }
}

class Vector3 extends Float32Array {
  constructor(x, y, z) {
    var vec = vec3.fromValues(x||0, y||0, z||0);
    vec.__proto__ = Vector3.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }

  get z() {
    return this[2];
  }

  set z(value) {
    this[2] = value;
  }

  set(x, y, z) {
    if (arguments.length === 1) {
      vec3.copy(this, arguments[0]);
    } else if (arguments.length === 3) {
      this[0] = x;
      this[1] = y;
      this[2] = z;
    } else {
      throw new TypeError('Wrong number of arguments.');
    }
    return this;
  }

  add(value, result) {
    return vec3.add(result || new Vector3(), this, value);
  }

  subtract(value, result) {
    return vec3.sub(result || new Vector3(), this, value);
  }

  multiply(value, result) {
    return vec3.mul(result || new Vector3(), this, value);
  }

  divide(value, result) {
    return vec3.divide(result || new Vector3(), this, value);
  }

  dot(value) {
    return vec3.dot(this, value);
  }

  cross(value, result) {
    return vec3.cross(result || new Vector3(), this, value);
  }

  scale(value, result) {
    return vec3.scale(result || new Vector3(), this, value);
  }

  transform(value, result) {
    if (value instanceof Matrix) {
      return vec3.transformMat4(result || new Vector3(), this, value);
    }
    if (value instanceof Quaternion) {
      return vec3.transformQuat(result || new Vector3(), this, value);
    }
  }

  negate(result) {
    return vec3.negate(result || new Vector3(), this);
  }

  lerp(value, t, result) {
    return vec3.lerp(result || new Vector3(), value, this, t);
  }

  normalize(result) {
    return vec3.normalize(result || new Vector3(), this);
  }

  len() {
    return vec3.length(this);
  }

  squaredDistance(value) {
    return vec3.squaredDistance(this, value);
  }

  toString() {
    return vec3.str(this);
  }
}

Vector3.prototype.sub = Vector3.prototype.subtract;

var _temp3 = new Vector3();

class Vector4 extends Float32Array {
  constructor(x, y, z, w) {
    var vec = vec4.fromValues(x||0, y||0, z||0, w||0);
    vec.__proto__ = Vector4.prototype;
    return vec;
  }

  get x() {
    return this[0];
  }

  set x(value) {
    this[0] = value;
  }

  get y() {
    return this[1];
  }

  set y(value) {
    this[1] = value;
  }

  get z() {
    return this[2];
  }

  set z(value) {
    this[2] = value;
  }

  get w() {
    return this[3];
  }

  set w(value) {
    this[3] = value;
  }
}

class Matrix extends Float32Array {
  constructor() {
    var mat = mat4.create();
    mat.__proto__ = Matrix.prototype;
    return mat;
  }

  set(value) {
    return mat4.copy(this, value);
  }

  translate(x, y, z) {
    _temp3.set(x, y, z);
    mat4.translate(this, this, _temp3);
    return this;
  }

  rotate(x, y, z) {
    if (x) {
      mat4.rotateX(this, this, x);
    }
    if (y) {
      mat4.rotateY(this, this, y);
    }
    if (z) {
      mat4.rotateZ(this, this, z);
    }
  }

  scale(x, y, z) {
    _temp3.set(x, y, z);
    mat4.scale(this, this, _temp3);
    return this;
  }

  multiply(value, result) {
    return mat4.multiply(result || new Matrix(), this, value);
  }

  invert(result) {
    return mat4.invert(result || new Matrix(), this);
  }

  toString() {
    return mat4.str(this);
  }

  static perspective(fieldOfView, aspectRatio, near, far, result) {
    return mat4.perspective(
      result || new Matrix(), fieldOfView, aspectRatio, near, far);
  }

  static ortho(left, right, bottom, top, near, far, result) {
    return mat4.ortho(
        result || new Matrix(), left, right, bottom, top, near, far);
  }

  static lookAt(eye, center, up, result) {
    return mat4.lookAt(result || new Matrix(), eye, center, up);
  }
}

class Quaternion extends Float32Array {
  constructor() {
    var qua = quat.create();
    qua.__proto__ = Quaternion.prototype;
    return qua;
  }

  add(value, result) {
    return quat.add(result || new Quaternion(), this, value);
  }

  multiply(value, result) {
    return quat.mul(result || new Quaternion(), this, value);
  }

  invert(result) {
    return quat.invert(result || new Quaternion(), this);
  }

  rotateX(value) {
    return quat.rotateX(this, this, value);
  }

  rotateY(value) {
    return quat.rotateY(this, this, value);
  }

  rotateZ(value) {
    return quat.rotateZ(this, this, value);
  }

  toString() {
    return quat.str(this);
  }
}

module.exports.Vector2 = Vector2;
module.exports.Vector3 = Vector3;
module.exports.Vector4 = Vector4;
module.exports.Matrix = Matrix;
module.exports.Quaternion = Quaternion;<|endoftext|>


<commit_before>/**
 * @file rule: doctype
 * @author nighca<nighca@live.cn>
 */

module.exports = {

    name: 'doctype',

    desc: 'DOCTYPE needed.',

    target: 'parser',

    lint: function (getCfg, parser, reporter) {

        var doctype = false;
        var html = false;

        parser.on('processinginstruction', function (name, data) {

            if (name === '!doctype') {
                if (!getCfg()) {
                    return;
                }

                var index = parser.tokenizer._index - 1;

                data = data.split(' ');
                doctype = (data[0]).trim();
                html = (data[1]).trim();

                if (getCfg('doctype') === 'upper' && doctype !== '!DOCTYPE') {
                    reporter.warn(index, '041', 'DOCTYPE must be uppercase.');
                }

                if (html !== 'html5') {
                    reporter.warn(index, '041', 'DOCTYPE must be html5.');
                }
            }
        });

        parser.on('end', function () {
            if (!doctype) {
                reporter.warn(0, '009', 'DOCTYPE needed.');
            }
        });
    },

    format: function (getCfg, document, options) {
        var doctype = document.doctype;
        var html = document.querySelector('html');

        if (!html || !getCfg(html) || doctype && doctype.name === 'html') {
            return;
        }

        if (doctype) {
            doctype.name = 'html';
        }
        else {
            doctype = document.implementation.createDocumentType('html', '', '');
            document.insertBefore(doctype, document.firstElementChild);
            document.doctype = doctype;
        }
    }

};
<commit_msg>Fixing: Rule for '<!doctype html>' rather than '<!DOCTYPE html>' #57<commit_after>/**
 * @file rule: doctype
 * @author nighca<nighca@live.cn>
 */

module.exports = {

    name: 'doctype',

    desc: 'DOCTYPE needed.',

    target: 'parser',

    lint: function (getCfg, parser, reporter) {

        var doctype = false;
        var html = false;

        parser.on('processinginstruction', function (name, data) {

            if (name === '!doctype') {
                if (!getCfg()) {
                    return;
                }

                var index = parser.tokenizer._index - 1;

                data = data.split(' ');
                doctype = (data[0]).trim();
                html = (data[1]).trim();

                if (getCfg('doctype') === 'upper' && doctype !== '!DOCTYPE') {
                    reporter.warn(index, '041', 'DOCTYPE must be uppercase.');
                }

                if (html !== 'html5') {
                    reporter.warn(index, '041', 'DOCTYPE must be html5.');
                }
            }
        });

        parser.on('end', function () {
            if (!doctype) {
                reporter.warn(0, '009', 'DOCTYPE needed.');
            }
        });
    },

    format: function (getCfg, document, options) {
        var doctype = document.doctype;
        var html = document.querySelector('html');

        if (!html || !getCfg(html) || doctype && doctype.name === 'html') {
            return;
        }

        if (doctype) {
            doctype.name = 'html';
        }
        else {
            doctype = document.implementation.createDocumentType('html', '', '');
            document.insertBefore(doctype, document.firstElementChild);
            document.doctype = doctype;
        }
    }

};
<|endoftext|>


<commit_before>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || {};
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  fse.ensureDirSync(dir);

  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files).forEach(function(file) {
    if (!/^(\d+)\.(.+)$/.test(file)) {
      return;
    }
    try {
      var index = parseInt(RegExp.$1, 10);
      var filename = decodeURIComponent(RegExp.$2);
      if (index > maxIndex) {
        maxIndex = index;
      }
      files[filename] = {
        index: index,
        name: filename,
        data: readFileSafe(path.join(self._files, file))
      };
    } catch(e) {
      logger.error(e);
    }
  });

  var properties = readJsonSafe(self._properties);
  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: properties
  };

  var filesOrder = properties.filesOrder;
  if (!Array.isArray(filesOrder)) {
    filesOrder = null;
  }
  filesOrder = Object.keys(files).sort(function(cur, next) {
    if (filesOrder) {
      var curIndex = filesOrder.indexOf(cur);
      if (curIndex !== -1) {
        var nextIndex = filesOrder.indexOf(next);
        if (nextIndex !== -1) {
          return curIndex > nextIndex ? 1 : -1;
        }
      }
    }
    cur = files[cur];
    next = files[next];
    return cur.index > next.index ? 1 : -1;
  });
  self.setProperty('filesOrder', filesOrder);
}

var proto = Storage.prototype;

proto._writeProperties = function _writeProperties() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (self._writePropertiesWaiting) {
      self._writePropertiesWaiting = false;
      self._writeProperties();
    } else if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._writeFile = function _writeFile(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (file._waiting) {
      file._waiting = false;
      self._writeFile(file.name);
    } else if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._getFilePath = function _getFilePath(file) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  return file && path.join(this._files, file.index + '.' + name);
};

proto.count = function count() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function existsFile(file) {
  return this._cache.files[file];
};

proto.getFileList = function getFileList(origObj) {
  var cache = this._cache;
  var files = cache.files;
  var filesOrder = cache.properties.filesOrder;
  return filesOrder.map(function(file) {
    return origObj ? files[file] : copyFileObj(files[file]);
  });
};

proto.writeFile = function writeFile(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  var hasChanged = true;
  data = data == null ? '' : data;
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
    cache.properties.filesOrder.push(file);
    self._writeProperties();
  } else if (fileData.data === data) {
    hasChanged = false;
  }
  fileData.data = data;
  self._writeFile(file);
  return hasChanged;
};

proto.updateFile = function updateFile(file, data) {
  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function removeFile(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder.splice(filesOrder.indexOf(file.name), 1);
  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  this._writeProperties();
  return true;
};

proto.renameFile = function renameFile(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file])
|| cache.files[newFile]) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder[filesOrder.indexOf(file.name)] = newFile;
  var path = this._getFilePath(file);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), noop); //不考虑并发
  this._writeProperties();
  return true;
};

proto.moveTo = function(fromName, toName) {
  var filesOrder = this._cache.properties.filesOrder;
  var fromIndex = filesOrder.indexOf(fromName);
  if (fromIndex === -1) {
    return false;
  }
  var toIndex = filesOrder.indexOf(toName);
  if (toIndex === -1) {
    return false;
  }
  filesOrder.splice(fromIndex, 1);
  filesOrder.splice(toIndex, 0, fromName);
  this._writeProperties();
  return true;
};

proto.setProperty = function setProperty(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function hasProperty(name) {
  return name in this._cache.properties;
};

proto.setProperties = function setProperties(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function getProperty(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function removeProperty(name) {
  if (this.hasProperty(name) && name !== 'filesOrder') {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<commit_msg>refactor: Ensure data is string<commit_after>var fs  = require('fs');
var fse = require('fs-extra');
var path = require('path');
var logger = require('../util/logger');
var ENCODING = {encoding: 'utf8'};
var RETRY_INTERVAL = 16000;

function readFileSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || '';
}

function readJsonSafe(file) {
  try {
    file = fs.readFileSync(file, ENCODING);
    file = file && JSON.parse(file);
  } catch(e) {
    file = null;
    logger.error(e);
  }

  return file || {};
}

function copyFileObj(file) {
  if (!file) {
    return file;
  }

  return {
    index: file.index,
    name: file.name,
    data: file.data,
    selected: file.selected
  };
}

function noop() {}

function Storage(dir) {
  var self = this;
  if (!(self instanceof Storage)) {
    return new Storage(dir);
  }

  fse.ensureDirSync(dir);

  self._files = path.join(dir, 'files');
  self._properties = path.join(dir, 'properties');
  fse.ensureDirSync(self._files);
  fse.ensureFileSync(self._properties);

  var maxIndex = -1;
  var files = {};
  fs.readdirSync(self._files).forEach(function(file) {
    if (!/^(\d+)\.(.+)$/.test(file)) {
      return;
    }
    try {
      var index = parseInt(RegExp.$1, 10);
      var filename = decodeURIComponent(RegExp.$2);
      if (index > maxIndex) {
        maxIndex = index;
      }
      files[filename] = {
        index: index,
        name: filename,
        data: readFileSafe(path.join(self._files, file))
      };
    } catch(e) {
      logger.error(e);
    }
  });

  var properties = readJsonSafe(self._properties);
  self._cache = {
    maxIndex: maxIndex,
    files: files,
    properties: properties
  };

  var filesOrder = properties.filesOrder;
  if (!Array.isArray(filesOrder)) {
    filesOrder = null;
  }
  filesOrder = Object.keys(files).sort(function(cur, next) {
    if (filesOrder) {
      var curIndex = filesOrder.indexOf(cur);
      if (curIndex !== -1) {
        var nextIndex = filesOrder.indexOf(next);
        if (nextIndex !== -1) {
          return curIndex > nextIndex ? 1 : -1;
        }
      }
    }
    cur = files[cur];
    next = files[next];
    return cur.index > next.index ? 1 : -1;
  });
  self.setProperty('filesOrder', filesOrder);
}

var proto = Storage.prototype;

proto._writeProperties = function _writeProperties() {
  var self = this;
  if (self._writePropertiesPending) {
    self._writePropertiesWaiting = true;
    return;
  }
  clearTimeout(self._writePropertiesTimeout);
  self._writePropertiesPending = true;
  fse.outputJson(self._properties, self._cache.properties, function(err) {
    self._writePropertiesPending = false;
    if (self._writePropertiesWaiting) {
      self._writePropertiesWaiting = false;
      self._writeProperties();
    } else if (err) {
      self._writePropertiesTimeout = setTimeout(self._writeProperties.bind(self), RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._writeFile = function _writeFile(file) {
  var self = this;
  if (!(file = self._cache.files[file])) {
    return;
  }
  if (file._pending) {
    file._waiting = true;
    return;
  }
  clearTimeout(file._timeout);
  file._pending = true;
  fs.writeFile(self._getFilePath(file), file.data, function(err) {
    file._pending = false;
    if (file._waiting) {
      file._waiting = false;
      self._writeFile(file.name);
    } else if (err) {
      file._timeout = setTimeout(function() {
        self._writeFile(file.name);
      }, RETRY_INTERVAL);
      logger.error(err);
    }
  });
};

proto._getFilePath = function _getFilePath(file) {
  file = typeof file == 'string' ? this._cache.files[file] : file;
  var name = file.name;
  try {
    name = encodeURIComponent(file.name);
  } catch(e) {
    logger.error(e);
  }
  return file && path.join(this._files, file.index + '.' + name);
};

proto.count = function count() {
  return Object.keys(this._cache.files).length;
};

proto.existsFile = function existsFile(file) {
  return this._cache.files[file];
};

proto.getFileList = function getFileList(origObj) {
  var cache = this._cache;
  var files = cache.files;
  var filesOrder = cache.properties.filesOrder;
  return filesOrder.map(function(file) {
    return origObj ? files[file] : copyFileObj(files[file]);
  });
};

proto.writeFile = function writeFile(file, data) {
  if (!file) {
    return;
  }

  var self = this;
  var cache = self._cache;
  var fileData = cache.files[file];
  var hasChanged = true;
  data = typeof data == 'string' ? data : '';
  if (!fileData) {
    fileData = cache.files[file] = {
      index: ++cache.maxIndex,
      name: file
    };
    cache.properties.filesOrder.push(file);
    self._writeProperties();
  } else if (fileData.data === data) {
    hasChanged = false;
  }
  fileData.data = data;
  self._writeFile(file);
  return hasChanged;
};

proto.updateFile = function updateFile(file, data) {
  return this.existsFile(file) && this.writeFile(file, data);
};

proto.readFile = function(file) {
  file = file && this._cache.files[file];
  return file && file.data;
};

proto.removeFile = function removeFile(file) {
  var files = this._cache.files;
  file = file && files[file];
  if (!file) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder.splice(filesOrder.indexOf(file.name), 1);
  delete files[file.name];
  fs.unlink(this._getFilePath(file), noop);
  this._writeProperties();
  return true;
};

proto.renameFile = function renameFile(file, newFile) {
  var cache = this._cache;
  if (!newFile || !(file = cache.files[file])
|| cache.files[newFile]) {
    return;
  }
  var filesOrder = this._cache.properties.filesOrder;
  filesOrder[filesOrder.indexOf(file.name)] = newFile;
  var path = this._getFilePath(file);
  delete cache.files[file.name];
  file.name = newFile;
  cache.files[newFile] = file;
  fs.rename(path, this._getFilePath(file), noop); //不考虑并发
  this._writeProperties();
  return true;
};

proto.moveTo = function(fromName, toName) {
  var filesOrder = this._cache.properties.filesOrder;
  var fromIndex = filesOrder.indexOf(fromName);
  if (fromIndex === -1) {
    return false;
  }
  var toIndex = filesOrder.indexOf(toName);
  if (toIndex === -1) {
    return false;
  }
  filesOrder.splice(fromIndex, 1);
  filesOrder.splice(toIndex, 0, fromName);
  this._writeProperties();
  return true;
};

proto.setProperty = function setProperty(name, value) {
  this._cache.properties[name] = value;
  this._writeProperties();
};

proto.hasProperty = function hasProperty(name) {
  return name in this._cache.properties;
};

proto.setProperties = function setProperties(obj) {
  if (!obj) {
    return;
  }

  var props = this._cache.properties;
  Object.keys(obj).forEach(function(key) {
    props[key] = obj[key];
  });
  this._writeProperties();
  return true;
};

proto.getProperty = function getProperty(name) {
  return this._cache.properties[name];
};

proto.removeProperty = function removeProperty(name) {
  if (this.hasProperty(name) && name !== 'filesOrder') {
    delete this._cache.properties[name];
    this._writeProperties();
  }
};

module.exports = Storage;
<|endoftext|>


<commit_before>"use strict";

// This module should be compatible with PhantomJS v1, just like the other files
// in reify/lib/runtime. Node 4+ features like const/let and arrow functions are
// not acceptable here, and importing any npm packages should be contemplated
// with extreme skepticism.

var utils = require("./utils.js");

var GETTER_ERROR = {};
var NAN = {};
var UNDEFINED = {};
var hasOwn = Object.prototype.hasOwnProperty;
var keySalt = 0;

function Entry(id) {
  // The canonical absolute module ID of the module this Entry manages.
  this.id = id;
  // The Module object this Entry manages, unknown until module.export or
  // module.link is called for the first time.
  this.module = null;
  // Getters for local variables exported from the managed module.
  this.getters = Object.create(null);
  // Setters for assigning to local variables in parent modules.
  this.setters = Object.create(null);
  // The normalized namespace object that importers receive when they use
  // `import * as namespace from "..."` syntax.
  this.namespace = utils.createNamespace();
}

var Ep = utils.setPrototypeOf(Entry.prototype, null);
var entryMap = Object.create(null);

Entry.getOrCreate = function (id, mod) {
  var entry = hasOwn.call(entryMap, id)
    ? entryMap[id]
    : entryMap[id] = new Entry(id);

  if (utils.isObject(mod) &&
      mod.id === entry.id) {
    entry.module = mod;
  }

  return entry;
};

function safeKeys(obj) {
  var keys = Object.keys(obj);
  var esModuleIndex = keys.indexOf("__esModule");
  if (esModuleIndex >= 0) {
    keys.splice(esModuleIndex, 1);
  }
  return keys;
}

Ep.addGetters = function (getters, constant) {
  var names = safeKeys(getters);
  var nameCount = names.length;
  constant = !! constant;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var getter = getters[name];

    if (typeof getter === "function" &&
        // Should this throw if this.getters[name] exists?
        ! (name in this.getters)) {
      this.getters[name] = getter;
      getter.constant = constant;
      getter.runCount = 0;
    }
  }
};

Ep.addSetters = function (parent, setters, key) {
  var names = safeKeys(setters);
  var nameCount = names.length;

  if (! nameCount) {
    return;
  }

  // If no key is provided, make a unique key. Otherwise, make sure the key is
  // distinct from keys provided by other parent modules.
  key = key === void 0
    ? makeUniqueKey()
    : parent.id + ":" + key;

  var entry = this;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var setter = normalizeSetterValue(parent, setters[name]);

    if (typeof setter === "function") {
      setter.parent = parent;
      if (! (name in entry.setters)) {
        entry.setters[name] = Object.create(null);
      }
      entry.setters[name][key] = setter;
    }
  }

  entry.runSetters(names);
};

function normalizeSetterValue(module, setter) {
  if (typeof setter === "function") {
    return setter;
  }

  if (typeof setter === "string") {
    // If the value of the setter property is a string, the setter will
    // re-export the imported value using that string as the name of the
    // exported value. If the string is "*", all properties of the value
    // object will be re-exported as individual exports, except for the
    // "default" property (use "*+" instead of "*" to include it).
    return module.exportAs(setter);
  }

  if (Array.isArray(setter)) {
    switch (setter.length) {
    case 0: return null;
    case 1: return normalizeSetterValue(module, setter[0]);
    default:
      var setterFns = setter.map(function (elem) {
        return normalizeSetterValue(module, elem);
      });

      // Return a combined function that calls all of the nested setter
      // functions with the same value.
      return function (value) {
        setterFns.forEach(function (fn) {
          fn(value);
        });
      };
    }
  }

  return null;
}

Ep.runGetters = function (names) {
  // Before running getters, copy anything added to the exports object
  // over to the namespace. Values returned by getters take precedence
  // over these values, but we don't want to miss anything.
  syncExportsToNamespace(this, names);

  if (names === void 0 ||
      names.indexOf("*") >= 0) {
    names = Object.keys(this.getters);
  }

  var nameCount = names.length;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var value = runGetter(this, name);

    // If the getter is run without error, update both entry.namespace and
    // module.exports with the current value so that CommonJS require
    // calls remain consistent with module.watch.
    if (value !== GETTER_ERROR) {
      this.namespace[name] = value;
      this.module.exports[name] = value;
    }
  }
};

function syncExportsToNamespace(entry, names) {
  var setDefault = false;

  if (entry.module === null) return;
  var exports = entry.module.exports;

  if (! utils.getESModule(exports)) {
    // If the module entry is managing overrides module.exports, that
    // value should be exposed as the .default property of the namespace,
    // unless module.exports is marked as an ECMASCript module.
    entry.namespace.default = exports;
    setDefault = true;
  }

  if (! utils.isObjectLike(exports)) {
    return;
  }

  if (names === void 0 ||
      names.indexOf("*") >= 0) {
    names = Object.keys(exports);
  }

  names.forEach(function (key) {
    // Don't set any properties for which a getter function exists in
    // entry.getters, don't accidentally override entry.namespace.default,
    // and only copy own properties from entry.module.exports.
    if (! hasOwn.call(entry.getters, key) &&
        ! (setDefault && key === "default") &&
        hasOwn.call(exports, key)) {
      utils.copyKey(key, entry.namespace, exports);
    }
  });
}

// Called whenever module.exports might have changed, to trigger any
// setters associated with the newly exported values. The names parameter
// is optional; without it, all getters and setters will run.
Ep.runSetters = function (names) {
  // Make sure entry.namespace and module.exports are up to date before we
  // call getExportByName(entry, name).
  this.runGetters(names);

  // Lazily-initialized object mapping parent module identifiers to parent
  // module objects whose setters we might need to run.
  var parents;

  forEachSetter(this, names, function (setter, name, value) {
    if (parents === void 0) {
      parents = Object.create(null);
    }
    parents[setter.parent.id] = setter.parent;

    // The param order for setters is `value` then `name` because the `name`
    // param is only used by namespace exports.
    setter(value, name);
  });

  if (! parents) {
    return;
  }

  // If any of the setters updated the module.exports of a parent module,
  // or updated local variables that are exported by that parent module,
  // then we must re-run any setters registered by that parent module.
  var parentIDs = Object.keys(parents);
  var parentIDCount = parentIDs.length;

  for (var i = 0; i < parentIDCount; ++i) {
    // What happens if parents[parentIDs[id]] === module, or if
    // longer cycles exist in the parent chain? Thanks to our setter.last
    // bookkeeping above, the runSetters broadcast will only proceed
    // as far as there are any actual changes to report.
    var parent = parents[parentIDs[i]];
    var parentEntry = entryMap[parent.id];
    if (parentEntry) {
      parentEntry.runSetters();
    }
  }
};

function callSetterIfNecessary(setter, name, value, callback) {
  if (name === "__esModule") {
    // Ignore setters asking for module.exports.__esModule.
    return;
  }

  var shouldCall = false;

  if (setter.last === void 0) {
    setter.last = Object.create(null);
    // Always call the setter if it has never been called before.
    shouldCall = true;
  }

  function changed(name, value) {
    var valueToCompare = value;
    if (valueToCompare !== valueToCompare) {
      valueToCompare = NAN;
    } else if (valueToCompare === void 0) {
      valueToCompare = UNDEFINED;
    }

    if (setter.last[name] === valueToCompare) {
      return false;
    }

    setter.last[name] = valueToCompare;
    return true;
  }

  if (name === "*") {
    var keys = safeKeys(value);
    var keyCount = keys.length;
    for (var i = 0; i < keyCount; ++i) {
      var key = keys[i];
      // Evaluating value[key] is risky because the property might be
      // defined by a getter function that logs a deprecation warning (or
      // worse) when evaluated. For example, Node uses this trick to
      // display a deprecation warning whenever crypto.createCredentials
      // is accessed. Fortunately, when value[key] is defined by a getter
      // function, it's enough to check whether the getter function itself
      // has changed, since we are careful elsewhere to preserve getters
      // rather than prematurely evaluating them.
      if (changed(key, utils.valueOrGetter(value, key))) {
        shouldCall = true;
      }
    }
  } else if (changed(name, value)) {
    shouldCall = true;
  }

  if (shouldCall) {
    // Only invoke the callback if we have not called this setter
    // (with a value of this name) before, or the current value is
    // different from the last value we passed to this setter.
    return callback(setter, name, value);
  }
}

// Invoke the given callback once for every (setter, name, value) that needs to
// be called. Note that forEachSetter does not call any setters itself, only the
// given callback.
function forEachSetter(entry, names, callback) {
  var needToCheckNames = true;

  if (names === void 0) {
    names = Object.keys(entry.setters);
    needToCheckNames = false;
  }

  var nameCount = names.length;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];

    if (needToCheckNames &&
        ! hasOwn.call(entry.setters, name)) {
      continue;
    }

    var setters = entry.setters[name];
    var keys = Object.keys(setters);
    var keyCount = keys.length;

    for (var j = 0; j < keyCount; ++j) {
      var key = keys[j];
      var value = getExportByName(entry, name);

      callSetterIfNecessary(setters[key], name, value, callback);

      var getter = entry.getters[name];
      if (typeof getter === "function" &&
          // Sometimes a getter function will throw because it's called
          // before the variable it's supposed to return has been
          // initialized, so we need to know that the getter function has
          // run to completion at least once.
          getter.runCount > 0 &&
          getter.constant) {
        // If we happen to know that this getter function has run
        // successfully, and will never return a different value, then we
        // can forget the corresponding setter, because we've already
        // reported that constant value. Note that we can't forget the
        // getter, because we need to remember the original value in case
        // anyone tampers with entry.module.exports[name].
        delete setters[key];
      }
    }
  }
}

function getExportByName(entry, name) {
  if (name === "*") {
    return entry.namespace;
  }

  if (hasOwn.call(entry.namespace, name)) {
    return entry.namespace[name];
  }

  if (entry.module === null) return;
  var exports = entry.module.exports;

  if (name === "default" &&
      ! (utils.getESModule(exports) &&
         "default" in exports)) {
    return exports;
  }

  if (exports == null) {
    return;
  }

  return exports[name];
}

function makeUniqueKey() {
  return Math.random()
    .toString(36)
    // Add an incrementing salt to help track key ordering and also
    // absolutely guarantee we never return the same key twice.
    .replace("0.", ++keySalt + "$");
}

function runGetter(entry, name) {
  var getter = entry.getters[name];
  try {
    var result = getter();
    ++getter.runCount;
    return result;
  } catch (e) {}
  return GETTER_ERROR;
}

module.exports = Entry;
<commit_msg>Improve runGetter performance by avoiding preventable exceptions (#242)<commit_after>"use strict";

// This module should be compatible with PhantomJS v1, just like the other files
// in reify/lib/runtime. Node 4+ features like const/let and arrow functions are
// not acceptable here, and importing any npm packages should be contemplated
// with extreme skepticism.

var utils = require("./utils.js");

var GETTER_ERROR = {};
var NAN = {};
var UNDEFINED = {};
var hasOwn = Object.prototype.hasOwnProperty;
var keySalt = 0;

function Entry(id) {
  // The canonical absolute module ID of the module this Entry manages.
  this.id = id;
  // The Module object this Entry manages, unknown until module.export or
  // module.link is called for the first time.
  this.module = null;
  // Getters for local variables exported from the managed module.
  this.getters = Object.create(null);
  // Setters for assigning to local variables in parent modules.
  this.setters = Object.create(null);
  // The normalized namespace object that importers receive when they use
  // `import * as namespace from "..."` syntax.
  this.namespace = utils.createNamespace();
}

var Ep = utils.setPrototypeOf(Entry.prototype, null);
var entryMap = Object.create(null);

Entry.getOrCreate = function (id, mod) {
  var entry = hasOwn.call(entryMap, id)
    ? entryMap[id]
    : entryMap[id] = new Entry(id);

  if (utils.isObject(mod) &&
      mod.id === entry.id) {
    entry.module = mod;
  }

  return entry;
};

function safeKeys(obj) {
  var keys = Object.keys(obj);
  var esModuleIndex = keys.indexOf("__esModule");
  if (esModuleIndex >= 0) {
    keys.splice(esModuleIndex, 1);
  }
  return keys;
}

Ep.addGetters = function (getters, constant) {
  var names = safeKeys(getters);
  var nameCount = names.length;
  constant = !! constant;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var getter = getters[name];

    if (typeof getter === "function" &&
        // Should this throw if this.getters[name] exists?
        ! (name in this.getters)) {
      this.getters[name] = getter;
      getter.constant = constant;
      getter.runCount = 0;
    }
  }
};

Ep.addSetters = function (parent, setters, key) {
  var names = safeKeys(setters);
  var nameCount = names.length;

  if (! nameCount) {
    return;
  }

  // If no key is provided, make a unique key. Otherwise, make sure the key is
  // distinct from keys provided by other parent modules.
  key = key === void 0
    ? makeUniqueKey()
    : parent.id + ":" + key;

  var entry = this;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var setter = normalizeSetterValue(parent, setters[name]);

    if (typeof setter === "function") {
      setter.parent = parent;
      if (! (name in entry.setters)) {
        entry.setters[name] = Object.create(null);
      }
      entry.setters[name][key] = setter;
    }
  }

  entry.runSetters(names);
};

function normalizeSetterValue(module, setter) {
  if (typeof setter === "function") {
    return setter;
  }

  if (typeof setter === "string") {
    // If the value of the setter property is a string, the setter will
    // re-export the imported value using that string as the name of the
    // exported value. If the string is "*", all properties of the value
    // object will be re-exported as individual exports, except for the
    // "default" property (use "*+" instead of "*" to include it).
    return module.exportAs(setter);
  }

  if (Array.isArray(setter)) {
    switch (setter.length) {
    case 0: return null;
    case 1: return normalizeSetterValue(module, setter[0]);
    default:
      var setterFns = setter.map(function (elem) {
        return normalizeSetterValue(module, elem);
      });

      // Return a combined function that calls all of the nested setter
      // functions with the same value.
      return function (value) {
        setterFns.forEach(function (fn) {
          fn(value);
        });
      };
    }
  }

  return null;
}

Ep.runGetters = function (names) {
  // Before running getters, copy anything added to the exports object
  // over to the namespace. Values returned by getters take precedence
  // over these values, but we don't want to miss anything.
  syncExportsToNamespace(this, names);

  if (names === void 0 ||
      names.indexOf("*") >= 0) {
    names = Object.keys(this.getters);
  }

  var nameCount = names.length;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];
    var value = runGetter(this, name);

    // If the getter is run without error, update both entry.namespace and
    // module.exports with the current value so that CommonJS require
    // calls remain consistent with module.watch.
    if (value !== GETTER_ERROR) {
      this.namespace[name] = value;
      this.module.exports[name] = value;
    }
  }
};

function syncExportsToNamespace(entry, names) {
  var setDefault = false;

  if (entry.module === null) return;
  var exports = entry.module.exports;

  if (! utils.getESModule(exports)) {
    // If the module entry is managing overrides module.exports, that
    // value should be exposed as the .default property of the namespace,
    // unless module.exports is marked as an ECMASCript module.
    entry.namespace.default = exports;
    setDefault = true;
  }

  if (! utils.isObjectLike(exports)) {
    return;
  }

  if (names === void 0 ||
      names.indexOf("*") >= 0) {
    names = Object.keys(exports);
  }

  names.forEach(function (key) {
    // Don't set any properties for which a getter function exists in
    // entry.getters, don't accidentally override entry.namespace.default,
    // and only copy own properties from entry.module.exports.
    if (! hasOwn.call(entry.getters, key) &&
        ! (setDefault && key === "default") &&
        hasOwn.call(exports, key)) {
      utils.copyKey(key, entry.namespace, exports);
    }
  });
}

// Called whenever module.exports might have changed, to trigger any
// setters associated with the newly exported values. The names parameter
// is optional; without it, all getters and setters will run.
Ep.runSetters = function (names) {
  // Make sure entry.namespace and module.exports are up to date before we
  // call getExportByName(entry, name).
  this.runGetters(names);

  // Lazily-initialized object mapping parent module identifiers to parent
  // module objects whose setters we might need to run.
  var parents;

  forEachSetter(this, names, function (setter, name, value) {
    if (parents === void 0) {
      parents = Object.create(null);
    }
    parents[setter.parent.id] = setter.parent;

    // The param order for setters is `value` then `name` because the `name`
    // param is only used by namespace exports.
    setter(value, name);
  });

  if (! parents) {
    return;
  }

  // If any of the setters updated the module.exports of a parent module,
  // or updated local variables that are exported by that parent module,
  // then we must re-run any setters registered by that parent module.
  var parentIDs = Object.keys(parents);
  var parentIDCount = parentIDs.length;

  for (var i = 0; i < parentIDCount; ++i) {
    // What happens if parents[parentIDs[id]] === module, or if
    // longer cycles exist in the parent chain? Thanks to our setter.last
    // bookkeeping above, the runSetters broadcast will only proceed
    // as far as there are any actual changes to report.
    var parent = parents[parentIDs[i]];
    var parentEntry = entryMap[parent.id];
    if (parentEntry) {
      parentEntry.runSetters();
    }
  }
};

function callSetterIfNecessary(setter, name, value, callback) {
  if (name === "__esModule") {
    // Ignore setters asking for module.exports.__esModule.
    return;
  }

  var shouldCall = false;

  if (setter.last === void 0) {
    setter.last = Object.create(null);
    // Always call the setter if it has never been called before.
    shouldCall = true;
  }

  function changed(name, value) {
    var valueToCompare = value;
    if (valueToCompare !== valueToCompare) {
      valueToCompare = NAN;
    } else if (valueToCompare === void 0) {
      valueToCompare = UNDEFINED;
    }

    if (setter.last[name] === valueToCompare) {
      return false;
    }

    setter.last[name] = valueToCompare;
    return true;
  }

  if (name === "*") {
    var keys = safeKeys(value);
    var keyCount = keys.length;
    for (var i = 0; i < keyCount; ++i) {
      var key = keys[i];
      // Evaluating value[key] is risky because the property might be
      // defined by a getter function that logs a deprecation warning (or
      // worse) when evaluated. For example, Node uses this trick to
      // display a deprecation warning whenever crypto.createCredentials
      // is accessed. Fortunately, when value[key] is defined by a getter
      // function, it's enough to check whether the getter function itself
      // has changed, since we are careful elsewhere to preserve getters
      // rather than prematurely evaluating them.
      if (changed(key, utils.valueOrGetter(value, key))) {
        shouldCall = true;
      }
    }
  } else if (changed(name, value)) {
    shouldCall = true;
  }

  if (shouldCall) {
    // Only invoke the callback if we have not called this setter
    // (with a value of this name) before, or the current value is
    // different from the last value we passed to this setter.
    return callback(setter, name, value);
  }
}

// Invoke the given callback once for every (setter, name, value) that needs to
// be called. Note that forEachSetter does not call any setters itself, only the
// given callback.
function forEachSetter(entry, names, callback) {
  var needToCheckNames = true;

  if (names === void 0) {
    names = Object.keys(entry.setters);
    needToCheckNames = false;
  }

  var nameCount = names.length;

  for (var i = 0; i < nameCount; ++i) {
    var name = names[i];

    if (needToCheckNames &&
        ! hasOwn.call(entry.setters, name)) {
      continue;
    }

    var setters = entry.setters[name];
    var keys = Object.keys(setters);
    var keyCount = keys.length;

    for (var j = 0; j < keyCount; ++j) {
      var key = keys[j];
      var value = getExportByName(entry, name);

      callSetterIfNecessary(setters[key], name, value, callback);

      var getter = entry.getters[name];
      if (typeof getter === "function" &&
          // Sometimes a getter function will throw because it's called
          // before the variable it's supposed to return has been
          // initialized, so we need to know that the getter function has
          // run to completion at least once.
          getter.runCount > 0 &&
          getter.constant) {
        // If we happen to know that this getter function has run
        // successfully, and will never return a different value, then we
        // can forget the corresponding setter, because we've already
        // reported that constant value. Note that we can't forget the
        // getter, because we need to remember the original value in case
        // anyone tampers with entry.module.exports[name].
        delete setters[key];
      }
    }
  }
}

function getExportByName(entry, name) {
  if (name === "*") {
    return entry.namespace;
  }

  if (hasOwn.call(entry.namespace, name)) {
    return entry.namespace[name];
  }

  if (entry.module === null) return;
  var exports = entry.module.exports;

  if (name === "default" &&
      ! (utils.getESModule(exports) &&
         "default" in exports)) {
    return exports;
  }

  if (exports == null) {
    return;
  }

  return exports[name];
}

function makeUniqueKey() {
  return Math.random()
    .toString(36)
    // Add an incrementing salt to help track key ordering and also
    // absolutely guarantee we never return the same key twice.
    .replace("0.", ++keySalt + "$");
}

function runGetter(entry, name) {
  var getter = entry.getters[name];
  if (!getter) return GETTER_ERROR;
  try {
    var result = getter();
    ++getter.runCount;
    return result;
  } catch (e) {}
  return GETTER_ERROR;
}

module.exports = Entry;
<|endoftext|>


<commit_before>import { EventTargetAssembler } from './eventtarget'

const { Node } = window
const {
    ATTRIBUTE_NODE,
    COMMENT_NODE,
    DOCUMENT_NODE,
    DOCUMENT_TYPE_NODE,
    DOCUMENT_FRAGMENT_NODE,
    ELEMENT_NODE,
    TEXT_NODE
} = Node
const { getTargetOf } = EventTargetAssembler
const NODE_PROPERTY_NAME = 'node'

/**
 * @see https://www.w3.org/TR/dom/#interface-node
 * @abstract
 */
export class NodeAssembler extends EventTargetAssembler {
    /**
     * @param {boolean} [deep]
     * @returns {NodeAssembler|*}
     */
    clone(deep) {
        return this.constructor.getInstanceOf(this.node.cloneNode(deep))
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @returns {NodeAssembler|*|null}
     */
    getInstanceOf(node) {
        return node?
            this.constructor.getAssemblerOf(node).getInstanceOf(node) :
            null
    }

    /**
     * @returns {Node|*}
     */
    get node() {
        return getTargetOf(this)
    }

    /**
     * @returns {DocumentAssembler}
     */
    get ownerDocument() {
        return this.getInstanceOf(this.node.ownerDocument)
    }

    /**
     * @param {Node|*} node
     * @returns {class|*}
     */
    static getAssemblerOf(node) {
        switch(node.nodeType) {
            case ATTRIBUTE_NODE:
                return this.attrAssembler.resolve(node)
            case COMMENT_NODE:
                return this.commentAssembler.resolve(node)
            case DOCUMENT_NODE:
                return this.documentAssembler.resolve(node)
            case DOCUMENT_TYPE_NODE:
                return this.doctypeAssembler.resolve(node)
            case DOCUMENT_FRAGMENT_NODE:
                return this.documentFragmentAssembler.resolve(node)
            case ELEMENT_NODE:
                return this.elementAssembler.resolve(node)
            case TEXT_NODE:
                return this.textAssembler.resolve(node)
            default:
                return NodeAssembler.CharacterDataAssembler
                    || NodeAssembler.ChildNodeAssembler
                    || NodeAssembler
        }
    }

    /**
     * @param {NodeAssembler|Node|*} object
     * @returns {Node|*|null}
     */
    static getNodeOf(object) {
        const result = getTargetOf(object)
        return result instanceof Node || result instanceof window.Attr? // https://github.com/jsdom/jsdom/issues/1641
            result :
            null
    }

    /**
     * @param {Node|*} node
     * @returns {NodeAssembler|*}
     * @abstract
     */
    static resolve(node) {
        return this
    }

    /**
     * @returns {class|*} AttrAssembler or fallback
     */
    static get attrAssembler() {
        return NodeAssembler.AttrAssembler || NodeAssembler
    }

    /**
     * @returns {class|*} CommentAssembler or fallback
     */
    static get commentAssembler() {
        return NodeAssembler.CommentAssembler
            || NodeAssembler.CharacterDataAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentTypeAssembler or fallback
     */
    static get doctypeAssembler() {
        return NodeAssembler.DocumentTypeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentAssembler or fallback
     */
    static get documentAssembler() {
        return NodeAssembler.DocumentAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentFragmentAssembler or fallback
     */
    static get documentFragmentAssembler() {
        return NodeAssembler.DocumentFragmentAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} ElementAssembler or fallback
     */
    static get elementAssembler() {
        return NodeAssembler.ElementAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} TextAssembler or fallback
     */
    static get textAssembler() {
        return NodeAssembler.TextAssembler
            || NodeAssembler.CharacterDataAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {string}
     * @override
     */
    static get targetPropertyName() {
        return NODE_PROPERTY_NAME
    }

    /**
     * @returns {interface} Node
     * @override
     */
    static get interface() {
        return Node
    }
}
<commit_msg>NodeAssembler.documentAssembler: remove the ChildNodeAssembler from the fallback chain<commit_after>import { EventTargetAssembler } from './eventtarget'

const { Node } = window
const {
    ATTRIBUTE_NODE,
    COMMENT_NODE,
    DOCUMENT_NODE,
    DOCUMENT_TYPE_NODE,
    DOCUMENT_FRAGMENT_NODE,
    ELEMENT_NODE,
    TEXT_NODE
} = Node
const { getTargetOf } = EventTargetAssembler
const NODE_PROPERTY_NAME = 'node'

/**
 * @see https://www.w3.org/TR/dom/#interface-node
 * @abstract
 */
export class NodeAssembler extends EventTargetAssembler {
    /**
     * @param {boolean} [deep]
     * @returns {NodeAssembler|*}
     */
    clone(deep) {
        return this.constructor.getInstanceOf(this.node.cloneNode(deep))
    }

    /**
     * @param {Node|NodeAssembler|*|null} node
     * @returns {NodeAssembler|*|null}
     */
    getInstanceOf(node) {
        return node?
            this.constructor.getAssemblerOf(node).getInstanceOf(node) :
            null
    }

    /**
     * @returns {Node|*}
     */
    get node() {
        return getTargetOf(this)
    }

    /**
     * @returns {DocumentAssembler}
     */
    get ownerDocument() {
        return this.getInstanceOf(this.node.ownerDocument)
    }

    /**
     * @param {Node|*} node
     * @returns {class|*}
     */
    static getAssemblerOf(node) {
        switch(node.nodeType) {
            case ATTRIBUTE_NODE:
                return this.attrAssembler.resolve(node)
            case COMMENT_NODE:
                return this.commentAssembler.resolve(node)
            case DOCUMENT_NODE:
                return this.documentAssembler.resolve(node)
            case DOCUMENT_TYPE_NODE:
                return this.doctypeAssembler.resolve(node)
            case DOCUMENT_FRAGMENT_NODE:
                return this.documentFragmentAssembler.resolve(node)
            case ELEMENT_NODE:
                return this.elementAssembler.resolve(node)
            case TEXT_NODE:
                return this.textAssembler.resolve(node)
            default:
                return NodeAssembler.CharacterDataAssembler
                    || NodeAssembler.ChildNodeAssembler
                    || NodeAssembler
        }
    }

    /**
     * @param {NodeAssembler|Node|*} object
     * @returns {Node|*|null}
     */
    static getNodeOf(object) {
        const result = getTargetOf(object)
        return result instanceof Node || result instanceof window.Attr? // https://github.com/jsdom/jsdom/issues/1641
            result :
            null
    }

    /**
     * @param {Node|*} node
     * @returns {NodeAssembler|*}
     * @abstract
     */
    static resolve(node) {
        return this
    }

    /**
     * @returns {class|*} AttrAssembler or fallback
     */
    static get attrAssembler() {
        return NodeAssembler.AttrAssembler || NodeAssembler
    }

    /**
     * @returns {class|*} CommentAssembler or fallback
     */
    static get commentAssembler() {
        return NodeAssembler.CommentAssembler
            || NodeAssembler.CharacterDataAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentTypeAssembler or fallback
     */
    static get doctypeAssembler() {
        return NodeAssembler.DocumentTypeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentAssembler or fallback
     */
    static get documentAssembler() {
        return NodeAssembler.DocumentAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} DocumentFragmentAssembler or fallback
     */
    static get documentFragmentAssembler() {
        return NodeAssembler.DocumentFragmentAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} ElementAssembler or fallback
     */
    static get elementAssembler() {
        return NodeAssembler.ElementAssembler
            || NodeAssembler.ParentNodeAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {class|*} TextAssembler or fallback
     */
    static get textAssembler() {
        return NodeAssembler.TextAssembler
            || NodeAssembler.CharacterDataAssembler
            || NodeAssembler.ChildNodeAssembler
            || NodeAssembler
    }

    /**
     * @returns {string}
     * @override
     */
    static get targetPropertyName() {
        return NODE_PROPERTY_NAME
    }

    /**
     * @returns {interface} Node
     * @override
     */
    static get interface() {
        return Node
    }
}
<|endoftext|>


<commit_before>var async = require('async');
var naan = require('naan');
var _ = require('underscore');
var util = require('util');

var node = module.exports = {
  /**
   * Save or update an object. If a new object is passed, the callback will
   * return a copy of that object with the <options.id> key set to the id of the
   * created object.
   */
  save: function(obj, key, val, callback) {
    if (typeof key == 'function') {
      callback = key;
      key = val = undefined;
    } else if (typeof val == 'function') {
      callback = val;
      val = undefined;
    }

    var label = undefined;
    if (key != undefined && val == undefined) label = key;

    if (label) {
      if (this.isBatch) {
        return callback(new Error("node.save with labels is not compatible with "
                + "batch mode. Use db.save & db.label instead."));
      }
      var txn = this._safeBatch();
      var node = txn.save(obj);
      txn.label(node, label);
      return this._safeBatchCommit(txn, function(err, result) {
        if (err) callback(err);
        else callback(null, result[node]);
      });
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [key, val];
      async.map(obj, naan.ecurry(txn.save, args, [1, 2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(obj, !key);

    if (!this._isValidId(id)) {
      if (typeof obj !== 'object') {
        return callback(new Error("No data to save"));
      }

      this.node._create(obj, callback);
    } else {
      if (val) this.node._updateProp(obj, key, val, callback);
      else this.node._update(obj, callback);
    }
  },

  /**
   * Create a new object. Maps to /node.
   */
  _create: function(obj, callback) {
    var op = this.operation('node', obj);
    this.call(op, function(err, body, location) {
      if (err) {
        return callback(err);
      }

      var result = _.clone(obj);
      result[this.options.id] = this._extractId(location);

      callback(null, result);
    });
  },

  /**
   * Save the properties of an object. Maps to PUT /node/{id}/properties.
   */
  _update: function(obj, callback) {
    var id = this._getId(obj, true);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var untouchedObj = _.clone(obj);
    if (untouchedObj[this.options.id] != null) {
      delete untouchedObj[this.options.id];
    }

    var endpoint = util.format('%s/properties', this._nodeRoot(id));
    var op = this.operation(endpoint, 'PUT', untouchedObj);
    this.call(op, function(err, body, response) {
      if (err) {
        return callback(err);
      } else {
        return callback(null, obj);
      }
    });
  },

  _updateProp: function(obj, key, val, callback) {
    var id = this._getId(obj);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint = util.format('%s/properties/%s', this._nodeRoot(id), key);
    var op = this.operation(endpoint, 'PUT', val);
    this.call(op, function(err, body) {
      if (err) return callback(err);
      else {
        obj = _.clone(obj);
        obj[key] = val;
        callback(null, obj);
      }
    });
  },

  /**
   * Read an object's properties. Maps to GET node/{id}/properties.
   */
  read: function(id, property, callback) {
    if (typeof property == 'function') {
      callback = property;
      property = null;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.read, property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint;
    if (!property)
      endpoint = util.format('%s/properties', this._nodeRoot(id));
    else
      endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
    var op = this.operation(endpoint);
    this.call(op, function(err, body) {
      if (err) {
        return callback(err);
      } else {
        if (!property) body[this.options.id] = id;
        return callback(null, body);
      }
    });
  },

  /**
   * Delete an object. Maps to DELETE node/{id}
   * If force is truthy, delete node and all its relations.
   */
  delete: function(id, force, callback) {
    var property = null;
    if (typeof force == 'function') {
      callback = force;
      force = false;
    } else if (typeof force == 'string') {
      property = force;
      force = false;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.node.delete, force || property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var object = id;
    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    if (!force) {
      var endpoint;
      if (!property) endpoint = this._nodeRoot(id);
      else
        endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
      var op = this.operation(endpoint, 'DELETE');
      this.call(op, function(err) {
        if (!property || err || typeof object != 'object') callback(err);
        else {
          delete object[property];
          callback(null, object);
        }
      });
    } else {
      this.query([
        'START node=node({root})',
        'OPTIONAL MATCH node-[rel]-()',
        'DELETE node, rel'
      ].join(' '), {root: id}, function(err) { callback(err) });
    }
  },

  /**
   * Relate objects together. maps to POST node/{first}/relationships
   */
  relate: function(startNode, type, endNode, properties, callback) {
    this.rel.create(startNode, type, endNode, properties, callback);
  },

  /**
   * Retrieve a set of relationships for the given node. Optionally specify the
   * direction and relationship name.
   *
   * `direction` must be one of "all", "in" or "out".
   *
   * db.relationships(obj|id, [direction, [relationshipName]], callback)
   */
  relationships: function(obj, direction, relName, callback) {
    if (typeof direction === 'function') {
      callback = direction;
      direction = 'all';
      relName = '';
    } else {
      if (typeof relName === 'function') {
        callback = relName;
        relName = '';
      }

      if (typeof direction !== 'string') {
        return callback(new Error('Invalid direction - ' + direction));
      } else if (typeof relName !== 'string') {
        return callback(new Error('Invalid relationship name - ' + relName));
      }

      direction = direction.toLowerCase();
      if (['in', 'all', 'out'].indexOf(direction) === -1) {
        return callback(new Error('Invalid direction - ' + direction));
      }
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [ direction, relName ];
      var rels = naan.ecurry(txn.node.relationships, args, [1, 2]);
      async.map(obj, rels, callback);
      return this._safeBatchCommit(txn);
    }

    var objId = this._getId(obj);

    if (!this._isValidId(objId)) {
      callback(new Error("Invalid ID"));
    }

    var nodeRoot = this._nodeRoot(objId);
    var endpoint = util.format('%s/relationships/%s', nodeRoot, direction);
    if (relName) {
      endpoint += "/" + relName;
    }
    var op = this.operation(endpoint);
    var self = this;
    this.call(op, function(err, rels) {
      if (err) {
        callback(err);
      } else {
        rels = rels.map(function(rel) {
          return self._createRelationshipObject(rel);
        });
        callback(null, rels);
      }
    });
  },

  /**
   * Perform a query based on a predicate. The predicate is translated to a
   * cypher query.
   */
  find: function(predicate, any, label, callback) {
    if (typeof any == 'function') {
      callback = any;
      any = false;
      label = null;
    } else if (typeof label == 'function') {
      callback = label;
      if (typeof any == 'string') {
        label = any;
        any = false;
      } else {
        label = null;
      }
    }

    if (Array.isArray(predicate)) {
      var txn = this._safeBatch();
      var finder = naan.ecurry(txn.node.find, [any, label], [1, 2]);
      async.map(predicate, finder, callback);
      return this._safeBatchCommit(txn);
    }

    if (typeof predicate !== 'object') callback(new Error('Invalid Predicate'));

    var matchers = Object.keys(predicate).reduce(function(matchers, key) {
      return matchers.push(util.format("n.%s = {%s}", key, key)), matchers;
    }, []);

    var cypher = [
      label == null ? "MATCH n" : util.format("MATCH (n:%s)", label),
      matchers.length > 0 ? "WHERE" : "",
      matchers.join(any ? " or " : " and "),
      "RETURN n"
    ].join(" ");

    this.query(cypher, predicate, callback);
  },

  label: function(node, label, replace, callback) {
    if (typeof replace == 'function') {
      callback = replace;
      replace = false;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ecurry(txn.label, [label, replace], [1,2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels', this._nodeRoot(id));
    var op = this.operation(endpoint, replace ? 'PUT' : 'POST', label);
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  removeLabel: function(node, label, callback) {
    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ncurry(txn.removeLabel, label, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels/%s', this._nodeRoot(id), label);
    var op = this.operation(endpoint, 'DELETE');
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  nodesWithLabel: function(label, callback) {
    var endpoint = util.format('label/%s/nodes', label);
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, nodes) {
      if (err || !nodes) return callback(err, nodes);
      nodes = nodes.map(this._createNodeObject);
      callback(null, nodes);
    });
  },

  readLabels: function(node, callback) {
    if (typeof node == 'function') {
      callback = node;
      node = undefined;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, txn.readLabels, function(err, allLabels) {
        if (err) return callback(err);
        callback(null, _.uniq(_.flatten(allLabels)));
      });
      return this._safeBatchCommit(txn);
    }

    var endpoint;
    if (node) {
      var id = this._getId(node);
      if (!this._isValidId(id)) return callback(new Error("Invalid ID"));
      endpoint = util.format('%s/labels', this._nodeRoot(id));
    } else {
      endpoint = 'labels';
    }
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, labels) {
      if (err) callback(err);
      else callback(null, labels);
    });
  }
}
<commit_msg>escape labels in seraph.find. closes #144<commit_after>var async = require('async');
var naan = require('naan');
var _ = require('underscore');
var util = require('util');

var node = module.exports = {
  /**
   * Save or update an object. If a new object is passed, the callback will
   * return a copy of that object with the <options.id> key set to the id of the
   * created object.
   */
  save: function(obj, key, val, callback) {
    if (typeof key == 'function') {
      callback = key;
      key = val = undefined;
    } else if (typeof val == 'function') {
      callback = val;
      val = undefined;
    }

    var label = undefined;
    if (key != undefined && val == undefined) label = key;

    if (label) {
      if (this.isBatch) {
        return callback(new Error("node.save with labels is not compatible with "
                + "batch mode. Use db.save & db.label instead."));
      }
      var txn = this._safeBatch();
      var node = txn.save(obj);
      txn.label(node, label);
      return this._safeBatchCommit(txn, function(err, result) {
        if (err) callback(err);
        else callback(null, result[node]);
      });
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [key, val];
      async.map(obj, naan.ecurry(txn.save, args, [1, 2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(obj, !key);

    if (!this._isValidId(id)) {
      if (typeof obj !== 'object') {
        return callback(new Error("No data to save"));
      }

      this.node._create(obj, callback);
    } else {
      if (val) this.node._updateProp(obj, key, val, callback);
      else this.node._update(obj, callback);
    }
  },

  /**
   * Create a new object. Maps to /node.
   */
  _create: function(obj, callback) {
    var op = this.operation('node', obj);
    this.call(op, function(err, body, location) {
      if (err) {
        return callback(err);
      }

      var result = _.clone(obj);
      result[this.options.id] = this._extractId(location);

      callback(null, result);
    });
  },

  /**
   * Save the properties of an object. Maps to PUT /node/{id}/properties.
   */
  _update: function(obj, callback) {
    var id = this._getId(obj, true);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var untouchedObj = _.clone(obj);
    if (untouchedObj[this.options.id] != null) {
      delete untouchedObj[this.options.id];
    }

    var endpoint = util.format('%s/properties', this._nodeRoot(id));
    var op = this.operation(endpoint, 'PUT', untouchedObj);
    this.call(op, function(err, body, response) {
      if (err) {
        return callback(err);
      } else {
        return callback(null, obj);
      }
    });
  },

  _updateProp: function(obj, key, val, callback) {
    var id = this._getId(obj);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint = util.format('%s/properties/%s', this._nodeRoot(id), key);
    var op = this.operation(endpoint, 'PUT', val);
    this.call(op, function(err, body) {
      if (err) return callback(err);
      else {
        obj = _.clone(obj);
        obj[key] = val;
        callback(null, obj);
      }
    });
  },

  /**
   * Read an object's properties. Maps to GET node/{id}/properties.
   */
  read: function(id, property, callback) {
    if (typeof property == 'function') {
      callback = property;
      property = null;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.read, property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    var endpoint;
    if (!property)
      endpoint = util.format('%s/properties', this._nodeRoot(id));
    else
      endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
    var op = this.operation(endpoint);
    this.call(op, function(err, body) {
      if (err) {
        return callback(err);
      } else {
        if (!property) body[this.options.id] = id;
        return callback(null, body);
      }
    });
  },

  /**
   * Delete an object. Maps to DELETE node/{id}
   * If force is truthy, delete node and all its relations.
   */
  delete: function(id, force, callback) {
    var property = null;
    if (typeof force == 'function') {
      callback = force;
      force = false;
    } else if (typeof force == 'string') {
      property = force;
      force = false;
    }

    if (Array.isArray(id)) {
      var txn = this._safeBatch();
      async.map(id, naan.ncurry(txn.node.delete, force || property, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var object = id;
    id = this._getId(id);
    if (!this._isValidId(id)) {
      return callback(new Error("Invalid ID"));
    }

    if (!force) {
      var endpoint;
      if (!property) endpoint = this._nodeRoot(id);
      else
        endpoint = util.format('%s/properties/%s', this._nodeRoot(id), property);
      var op = this.operation(endpoint, 'DELETE');
      this.call(op, function(err) {
        if (!property || err || typeof object != 'object') callback(err);
        else {
          delete object[property];
          callback(null, object);
        }
      });
    } else {
      this.query([
        'START node=node({root})',
        'OPTIONAL MATCH node-[rel]-()',
        'DELETE node, rel'
      ].join(' '), {root: id}, function(err) { callback(err) });
    }
  },

  /**
   * Relate objects together. maps to POST node/{first}/relationships
   */
  relate: function(startNode, type, endNode, properties, callback) {
    this.rel.create(startNode, type, endNode, properties, callback);
  },

  /**
   * Retrieve a set of relationships for the given node. Optionally specify the
   * direction and relationship name.
   *
   * `direction` must be one of "all", "in" or "out".
   *
   * db.relationships(obj|id, [direction, [relationshipName]], callback)
   */
  relationships: function(obj, direction, relName, callback) {
    if (typeof direction === 'function') {
      callback = direction;
      direction = 'all';
      relName = '';
    } else {
      if (typeof relName === 'function') {
        callback = relName;
        relName = '';
      }

      if (typeof direction !== 'string') {
        return callback(new Error('Invalid direction - ' + direction));
      } else if (typeof relName !== 'string') {
        return callback(new Error('Invalid relationship name - ' + relName));
      }

      direction = direction.toLowerCase();
      if (['in', 'all', 'out'].indexOf(direction) === -1) {
        return callback(new Error('Invalid direction - ' + direction));
      }
    }

    if (Array.isArray(obj)) {
      var txn = this._safeBatch();
      var args = [ direction, relName ];
      var rels = naan.ecurry(txn.node.relationships, args, [1, 2]);
      async.map(obj, rels, callback);
      return this._safeBatchCommit(txn);
    }

    var objId = this._getId(obj);

    if (!this._isValidId(objId)) {
      callback(new Error("Invalid ID"));
    }

    var nodeRoot = this._nodeRoot(objId);
    var endpoint = util.format('%s/relationships/%s', nodeRoot, direction);
    if (relName) {
      endpoint += "/" + relName;
    }
    var op = this.operation(endpoint);
    var self = this;
    this.call(op, function(err, rels) {
      if (err) {
        callback(err);
      } else {
        rels = rels.map(function(rel) {
          return self._createRelationshipObject(rel);
        });
        callback(null, rels);
      }
    });
  },

  /**
   * Perform a query based on a predicate. The predicate is translated to a
   * cypher query.
   */
  find: function(predicate, any, label, callback) {
    if (typeof any == 'function') {
      callback = any;
      any = false;
      label = null;
    } else if (typeof label == 'function') {
      callback = label;
      if (typeof any == 'string') {
        label = any;
        any = false;
      } else {
        label = null;
      }
    }

    if (Array.isArray(predicate)) {
      var txn = this._safeBatch();
      var finder = naan.ecurry(txn.node.find, [any, label], [1, 2]);
      async.map(predicate, finder, callback);
      return this._safeBatchCommit(txn);
    }

    if (typeof predicate !== 'object') callback(new Error('Invalid Predicate'));

    var matchers = Object.keys(predicate).reduce(function(matchers, key) {
      return matchers.push(util.format("n.%s = {%s}", key, key)), matchers;
    }, []);

    var cypher = [
      label == null ? "MATCH n" : util.format("MATCH (n:`%s`)", label),
      matchers.length > 0 ? "WHERE" : "",
      matchers.join(any ? " or " : " and "),
      "RETURN n"
    ].join(" ");

    this.query(cypher, predicate, callback);
  },

  label: function(node, label, replace, callback) {
    if (typeof replace == 'function') {
      callback = replace;
      replace = false;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ecurry(txn.label, [label, replace], [1,2]), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels', this._nodeRoot(id));
    var op = this.operation(endpoint, replace ? 'PUT' : 'POST', label);
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  removeLabel: function(node, label, callback) {
    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, naan.ncurry(txn.removeLabel, label, 1), callback);
      return this._safeBatchCommit(txn);
    }

    var id = this._getId(node);
    if (!this._isValidId(id)) return callback(new Error("Invalid ID"));

    var endpoint = util.format('%s/labels/%s', this._nodeRoot(id), label);
    var op = this.operation(endpoint, 'DELETE');
    this.call(op, function(err) {
      if (err) callback(err);
      else callback();
    });
  },

  nodesWithLabel: function(label, callback) {
    var endpoint = util.format('label/%s/nodes', label);
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, nodes) {
      if (err || !nodes) return callback(err, nodes);
      nodes = nodes.map(this._createNodeObject);
      callback(null, nodes);
    });
  },

  readLabels: function(node, callback) {
    if (typeof node == 'function') {
      callback = node;
      node = undefined;
    }

    if (Array.isArray(node)) {
      var txn = this._safeBatch();
      async.map(node, txn.readLabels, function(err, allLabels) {
        if (err) return callback(err);
        callback(null, _.uniq(_.flatten(allLabels)));
      });
      return this._safeBatchCommit(txn);
    }

    var endpoint;
    if (node) {
      var id = this._getId(node);
      if (!this._isValidId(id)) return callback(new Error("Invalid ID"));
      endpoint = util.format('%s/labels', this._nodeRoot(id));
    } else {
      endpoint = 'labels';
    }
    var op = this.operation(endpoint, 'GET');
    this.call(op, function(err, labels) {
      if (err) callback(err);
      else callback(null, labels);
    });
  }
}
<|endoftext|>