<commit_before>var scxml = require('scxml');
var uuid = require('uuid');
var fs = require('fs');
var path = require('path');
var validate = require('../../common/validate-scxml').validateCreateScxmlRequest;
var sse = require('../../common/sse');

var definitions = {};
var compiledDefinitions = {};
var definitionToInstances = {};
var instances = {};
var statechartDefinitionSubscriptions = {};

function createStatechartDefinition(req,res,scName){
  validate(req, function(errors, scxmlDoc){

    if(errors) return res.send(400,{name : 'error.create', data : errors});

    var scxmlString = req.body;
    scxml.documentStringToModel(null, scxmlString, function(err, model){
      var chartName = scName || model().name || uuid.v1();

      definitions[chartName] = scxmlString;
      compiledDefinitions[chartName] = model;
      definitionToInstances[chartName] = [];

      broadcastDefinitionChange(chartName, scxmlString);

      res.setHeader('Location', chartName);
      res.send(201);
    });
  });
}

function createInstance(req, res, instanceId){
  //1. fetch statechart definition
  //2. instantiate statechart
  //3. start statechart
  //4. get the initial configuration and set the x-configuration header

  var chartName = req.param('StateChartName');
  var model = compiledDefinitions[chartName];
  var sc = new scxml.scion.Statechart(model);
  var initialConfiguration = sc.start();

  instanceId = chartName  + '/' + (instanceId || uuid.v1());

  //update data stores
  var map =
    definitionToInstances[chartName] =
      definitionToInstances[chartName] || [];
  map.push(instanceId);
  instances[instanceId] = sc;

  res.setHeader('Location', instanceId);
  res.setHeader('X-Configuration',JSON.stringify(initialConfiguration));

  res.send(201);
}

module.exports.createStatechartDefinition = function(req, res){
  createStatechartDefinition(req,res);
};

module.exports.getStatechartDefinitions = function(req, res){
  res.json(Object.keys(definitions));
};

module.exports.getStatechartDefinition = function(req, res){
  //1. fetch statechart definition
  var chartName = req.param('StateChartName');

  var model = definitions[chartName];
  if(model){
      res.send(model);
  }else {
      res.send(404);
  }
};

module.exports.createOrUpdateStatechartDefinition = function(req, res){
  createStatechartDefinition(req,res,req.param('StateChartName'));
};

module.exports.createInstance = function(req, res){
  createInstance(req, res);
};

module.exports.deleteStatechartDefinition = function(req, res){
  var chartName = req.param('StateChartName');

  var success = delete definitions[chartName];
  definitionToInstances[chartName].forEach(function(instanceId){
    //TODO: stop running instances
    delete instances[instanceId];
  });
  delete definitionToInstances[chartName];

  if(success){
    res.send(200);
  }else{
    res.send(404);
  }
};

module.exports.getInstances = function(req, res){
  var chartName = req.param('StateChartName');

  var instances = definitionToInstances[chartName];

  if(instances){
    res.json(instances);
  }else{
    res.send(404);
  }
};

module.exports.getStatechartDefinitionChanges = function(req, res){
  var chartName = req.param('StateChartName');

  var statechartDefinitionSubscription =
    statechartDefinitionSubscriptions[chartName] =
      statechartDefinitionSubscriptions[chartName] || [];
  statechartDefinitionSubscription.push(res);

  sse.initStream(req, res, function(){
    statechartDefinitionSubscription.splice(
      statechartDefinitionSubscription.indexOf(res), 1);
  });
};

module.exports.getInstance = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var sc = instances[instanceId];
  if(sc){
      res.send(sc.getSnapshot());
  }else {
      res.send(404);
  }
};

module.exports.createNamedInstance = function(req, res){
  createInstance(req, res, req.param('InstanceId'));
};

module.exports.sendEvent = function(req, res){

  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var sc = instances[instanceId];

  var event = JSON.parse(req.body);
  var nextConfiguration = sc.gen(event);

  res.setHeader('X-Configuration',JSON.stringify(nextConfiguration));

  res.send(200);
};

module.exports.deleteInstance = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var success = delete instances[instanceId];
  var arr = definitionToInstances[chartName];
  arr.splice(arr.indexOf(instanceId),1);

  if(success){
    res.send(200);
  }else{
    res.send(404);
  }
};

module.exports.getInstanceChanges = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var instance = instances[instanceId];

  if(!instance) return res.send(404);

  var listener = {
    onEntry : function(stateId){
      res.write('event: onEntry\n');
      res.write('data: ' + stateId + '\n\n');
    },
    onExit : function(stateId){
      res.write('event: onExit\n');
      res.write('data: ' + stateId + '\n\n');
    },
    onTransition : function(sourceStateId,targetStatesIds){
      //TODO: spec this out
    }
  };

  instance.registerListener(listener);

  sse.initStream(req, res, function(){
    instance.unregisterListener(listener);
  });
};

module.exports.viz = function (req, res) {
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var instance = instances[instanceId];

  if(!instance) return res.send(404);

  res.render('viz.html');
}

function broadcastDefinitionChange(chartName, scxmlString){
  var statechartDefinitionSubscription = statechartDefinitionSubscriptions[chartName];
  if(statechartDefinitionSubscription) {
    statechartDefinitionSubscription.forEach(function(response) {
      response.write('event: onChange\n');
      response.write('data: ' + scxmlString + '\n\n');
    });
  }
}
<commit_msg>"scxml interact <InstanceId>": Start repl interface to send events<commit_after>var scxml = require('scxml');
var uuid = require('uuid');
var fs = require('fs');
var path = require('path');
var validate = require('../../common/validate-scxml').validateCreateScxmlRequest;
var sse = require('../../common/sse');

var definitions = {};
var compiledDefinitions = {};
var definitionToInstances = {};
var instances = {};
var statechartDefinitionSubscriptions = {};

function createStatechartDefinition(req,res,scName){
  validate(req, function(errors, scxmlDoc){

    if(errors) return res.send(400,{name : 'error.create', data : errors});

    var scxmlString = req.body;
    scxml.documentStringToModel(null, scxmlString, function(err, model){
      var chartName = scName || model().name || uuid.v1();

      definitions[chartName] = scxmlString;
      compiledDefinitions[chartName] = model;
      definitionToInstances[chartName] = [];

      broadcastDefinitionChange(chartName, scxmlString);

      res.setHeader('Location', chartName);
      res.send(201);
    });
  });
}

function createInstance(req, res, instanceId){
  //1. fetch statechart definition
  //2. instantiate statechart
  //3. start statechart
  //4. get the initial configuration and set the x-configuration header

  var chartName = req.param('StateChartName');
  var model = compiledDefinitions[chartName];
  var sc = new scxml.scion.Statechart(model);
  var initialConfiguration = sc.start();

  instanceId = chartName  + '/' + (instanceId || uuid.v1());

  //update data stores
  var map =
    definitionToInstances[chartName] =
      definitionToInstances[chartName] || [];
  map.push(instanceId);
  instances[instanceId] = sc;

  res.setHeader('Location', instanceId);
  res.setHeader('X-Configuration',JSON.stringify(initialConfiguration));

  res.send(201);
}

module.exports.createStatechartDefinition = function(req, res){
  createStatechartDefinition(req,res);
};

module.exports.getStatechartDefinitions = function(req, res){
  res.json(Object.keys(definitions));
};

module.exports.getStatechartDefinition = function(req, res){
  //1. fetch statechart definition
  var chartName = req.param('StateChartName');

  var model = definitions[chartName];
  if(model){
      res.send(model);
  }else {
      res.send(404);
  }
};

module.exports.createOrUpdateStatechartDefinition = function(req, res){
  createStatechartDefinition(req,res,req.param('StateChartName'));
};

module.exports.createInstance = function(req, res){
  createInstance(req, res);
};

module.exports.deleteStatechartDefinition = function(req, res){
  var chartName = req.param('StateChartName');

  var success = delete definitions[chartName];
  definitionToInstances[chartName].forEach(function(instanceId){
    //TODO: stop running instances
    delete instances[instanceId];
  });
  delete definitionToInstances[chartName];

  if(success){
    res.send(200);
  }else{
    res.send(404);
  }
};

module.exports.getInstances = function(req, res){
  var chartName = req.param('StateChartName');

  var instances = definitionToInstances[chartName];

  if(instances){
    res.json(instances);
  }else{
    res.send(404);
  }
};

module.exports.getStatechartDefinitionChanges = function(req, res){
  var chartName = req.param('StateChartName');

  var statechartDefinitionSubscription =
    statechartDefinitionSubscriptions[chartName] =
      statechartDefinitionSubscriptions[chartName] || [];
  statechartDefinitionSubscription.push(res);

  sse.initStream(req, res, function(){
    statechartDefinitionSubscription.splice(
      statechartDefinitionSubscription.indexOf(res), 1);
  });
};

module.exports.getInstance = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var sc = instances[instanceId];
  if(sc){
      res.send(sc.getSnapshot());
  }else {
      res.send(404);
  }
};

module.exports.createNamedInstance = function(req, res){
  createInstance(req, res, req.param('InstanceId'));
};

module.exports.sendEvent = function(req, res){
  console.time('event');

  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var sc = instances[instanceId];

  var event = JSON.parse(req.body);

  console.log('event started', event);

  var nextConfiguration = sc.gen(event);
  res.setHeader('X-Configuration',JSON.stringify(nextConfiguration));

  console.timeEnd('event');
  console.log('event ended', event);
  res.send(200);
};

module.exports.deleteInstance = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var success = delete instances[instanceId];
  var arr = definitionToInstances[chartName];
  arr.splice(arr.indexOf(instanceId),1);

  if(success){
    res.send(200);
  }else{
    res.send(404);
  }
};

module.exports.getInstanceChanges = function(req, res){
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var instance = instances[instanceId];

  if(!instance) return res.send(404);

  var listener = {
    onEntry : function(stateId){
      res.write('event: onEntry\n');
      res.write('data: ' + stateId + '\n\n');
    },
    onExit : function(stateId){
      res.write('event: onExit\n');
      res.write('data: ' + stateId + '\n\n');
    },
    onTransition : function(sourceStateId,targetStatesIds){
      //TODO: spec this out
    }
  };

  instance.registerListener(listener);

  sse.initStream(req, res, function(){
    instance.unregisterListener(listener);
  });
};

module.exports.viz = function (req, res) {
  var chartName = req.param('StateChartName'),
    instanceId = chartName + '/' + req.param('InstanceId');

  var instance = instances[instanceId];

  if(!instance) return res.send(404);

  res.render('viz.html');
}

function broadcastDefinitionChange(chartName, scxmlString){
  var statechartDefinitionSubscription = statechartDefinitionSubscriptions[chartName];
  if(statechartDefinitionSubscription) {
    statechartDefinitionSubscription.forEach(function(response) {
      response.write('event: onChange\n');
      response.write('data: ' + scxmlString + '\n\n');
    });
  }
}
<|endoftext|>


<commit_before>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = {};
    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = Date.now();

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        this.error(error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

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

  ChromeConnection.prototype.source = function source(filename, contents, callback) {
    var script = this._scripts.filter(function(src) {
      return path.basename(src.url) == filename;
    })[0];

    if (script === undefined) {
      return callback('Unknown script ' + filename);
    }

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<commit_msg>Change callbacks to be a sparse array with a counter<commit_after>var http = require('http');
var ws = require('ws');
var path = require('path');
var events = require('events');
var util = require('util');
var async = require('async');

var ChromeConnection = (function() {
  function ChromeConnection(port, host) {
    events.EventEmitter.call(this);

    this._port = port;
    this._host = host;

    this._callbacks = [];
    this._counter = 0;

    this._scripts = [];
    this._console = new events.EventEmitter();
  }

  util.inherits(ChromeConnection, events.EventEmitter);

  Object.defineProperty(ChromeConnection.prototype, 'console', {
    get: function() {
      return this._console;
    },
  });

  ChromeConnection.prototype._send = function(method, params, callback) {
    var id = this._counter++;

    var message = {
      id: id,
      method: method,
      params: params
    };

    this._callbacks[id] = callback;

    try {
      this._socket.send(JSON.stringify(message));
    } catch (error) {
      return callback(error);
    }
  };

  ChromeConnection.prototype._process = function(message) {
    if (message.id !== undefined) {
      var callback = this._callbacks[message.id];

      if (callback) {
        if (message.error) {
          callback(message.error);
        } else if (message.params) {
          callback(null, message.params);
        } else if (message.result) {
          callback(null, message.result);
        }

        delete this._callbacks[message.id];
      }
    } else {
      if (message.method == 'Debugger.scriptParsed') {
        this._scripts.push(message.params);
      } else if (message.method == 'Console.messageAdded') {
        this._console.emit('data', message.params.message);
      }
    }
  };

  ChromeConnection.prototype.targets = function(callback) {
    var options = {
      host: this._host,
      port: this._port,
      path: '/json'
    };

    var self = this;
    var request = http.get(options, function(response) {
      var data = '';

      response.on('data', function(chunk) {
        data += chunk;
      });

      response.on('end', function() {
        var targets = JSON.parse(data);
        callback(targets);
      });
    });

    request.on('error', function(error) {
      self.emit('error', error);
    });
  };

  ChromeConnection.prototype.attach = function attach(target) {
    var socket = ws.connect(target.webSocketDebuggerUrl);

    var self = this;
    var send = self._send.bind(self);

    socket.on('open', function() {
      async.series([
        async.apply(send, 'Debugger.enable', {}),
        async.apply(send, 'Runtime.enable', {}),
        async.apply(send, 'Console.enable', {}),
      ], function(error) {
        if (error) {
          return self.emit('error', error);
        }

        self.emit('attach', target);
      });
    });

    socket.on('close', function() {
      self.emit('detatch', target);
    });

    socket.on('error', function(error) {
      self.emit('error', error);
    });

    socket.on('message', function(data) {
      try {
        var message = JSON.parse(data);
        self._process(message);
      } catch (error) {
        this.error(error);
      }
    });

    this._socket = socket;
  };

  ChromeConnection.prototype.evaluate = function evaluate(expression, callback) {
    var params = {
      expression: expression,
    };

    this._send('Runtime.evaluate', params, function evaluate(error, params) {
      if (error) {
        return callback(error);
      }

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

  ChromeConnection.prototype.source = function source(filename, contents, callback) {
    var script = this._scripts.filter(function(src) {
      return path.basename(src.url) == filename;
    })[0];

    if (script === undefined) {
      return callback('Unknown script ' + filename);
    }

    var params = {
      scriptId: script.scriptId,
      scriptSource: contents
    };

    this._send('Debugger.setScriptSource', params, function(error, params) {
      if (error) {
        return callback(error);
      }

      callback(null, params);
    });
  };

  return ChromeConnection;
}());

function connect(port, host, callback) {
  var client = new ChromeConnection(port, host);

  if (callback) {
    client.on('attach', callback);
  }

  return client;
}

module.exports.connect = connect;
<|endoftext|>


<commit_before>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import sequelize from '../sequelize';
import User from './User';
import UserLogin from './UserLogin';
import UserClaim from './UserClaim';
import UserProfile from './UserProfile';
import Course from './Course';
import Unit from './Unit';
import CourseUnit from './CourseUnit';
import UserCourse from './UserCourse';
import Answer from './Answer';
import Mark from './Mark';
import Group from './Group';
import UserGroup from './UserGroup';
import File from './File';

User.hasMany(UserLogin, {
  foreignKey: 'userId',
  as: 'logins',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

User.hasMany(UserClaim, {
  foreignKey: 'userId',
  as: 'claims',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

User.hasOne(UserProfile, {
  foreignKey: 'userId',
  as: 'profile',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

Course.belongsToMany(Unit, {
  as: 'units',
  through: {
    model: CourseUnit,
  },
  foreignKey: 'courseId',
});

Unit.belongsToMany(Course, {
  as: 'courses',
  through: {
    model: CourseUnit,
  },
  foreignKey: 'unitId',
});

User.belongsToMany(Course, {
  as: 'courses',
  through: {
    model: UserCourse,
  },
  foreignKey: 'userId',
});

User.hasMany(File, {
  as: 'files',
});

Course.belongsToMany(User, {
  as: 'users',
  through: {
    model: UserCourse,
  },
  foreignKey: 'courseId',
});

User.belongsToMany(Group, {
  as: 'groups',
  through: {
    model: UserGroup,
  },
  foreignKey: 'userId',
});

Group.belongsToMany(User, {
  as: 'users',
  through: {
    model: UserGroup,
  },
  foreignKey: 'groupId',
});

User.hasMany(Answer);
Unit.hasMany(Answer);
Unit.hasMany(File, {
  as: 'files',
});
File.belongsTo(Unit);
Course.hasMany(Answer);
CourseUnit.hasMany(Answer);

Answer.belongsTo(User);
Answer.belongsTo(Unit);
Answer.belongsTo(Course);
Answer.hasMany(File, {
  as: 'files',
});
File.belongsTo(Answer);

Answer.hasMany(Mark);
Mark.belongsTo(Answer);
Mark.belongsTo(User, { as: 'author' });

function sync(...args) {
  return sequelize.sync(...args);
}

export default { sync };
export {
  User,
  UserLogin,
  UserClaim,
  UserProfile,
  Course,
  Unit,
  UserCourse,
  Group,
  File,
  UserGroup,
};
<commit_msg>fix courseunit bug<commit_after>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

import sequelize from '../sequelize';
import User from './User';
import UserLogin from './UserLogin';
import UserClaim from './UserClaim';
import UserProfile from './UserProfile';
import Course from './Course';
import Unit from './Unit';
import CourseUnit from './CourseUnit';
import UserCourse from './UserCourse';
import Answer from './Answer';
import Mark from './Mark';
import Group from './Group';
import UserGroup from './UserGroup';
import File from './File';

User.hasMany(UserLogin, {
  foreignKey: 'userId',
  as: 'logins',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

User.hasMany(UserClaim, {
  foreignKey: 'userId',
  as: 'claims',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

User.hasOne(UserProfile, {
  foreignKey: 'userId',
  as: 'profile',
  onUpdate: 'cascade',
  onDelete: 'cascade',
});

Course.belongsToMany(Unit, {
  as: 'units',
  through: {
    model: CourseUnit,
  },
  foreignKey: 'courseId',
});

Unit.belongsToMany(Course, {
  as: 'courses',
  through: {
    model: CourseUnit,
  },
  foreignKey: 'unitId',
});

User.belongsToMany(Course, {
  as: 'courses',
  through: {
    model: UserCourse,
  },
  foreignKey: 'userId',
});

User.hasMany(File, {
  as: 'files',
});

Course.belongsToMany(User, {
  as: 'users',
  through: {
    model: UserCourse,
  },
  foreignKey: 'courseId',
});

User.belongsToMany(Group, {
  as: 'groups',
  through: {
    model: UserGroup,
  },
  foreignKey: 'userId',
});

Group.belongsToMany(User, {
  as: 'users',
  through: {
    model: UserGroup,
  },
  foreignKey: 'groupId',
});

User.hasMany(Answer);
Unit.hasMany(Answer);
Unit.hasMany(File, {
  as: 'files',
});
File.belongsTo(Unit);
Course.hasMany(Answer);

Answer.belongsTo(User);
Answer.belongsTo(Unit);
Answer.belongsTo(Course);
Answer.hasMany(File, {
  as: 'files',
});
File.belongsTo(Answer);

Answer.hasMany(Mark);
Mark.belongsTo(Answer);
Mark.belongsTo(User, { as: 'author' });

function sync(...args) {
  return sequelize.sync(...args);
}

export default { sync };
export {
  User,
  UserLogin,
  UserClaim,
  UserProfile,
  Course,
  Unit,
  UserCourse,
  Group,
  File,
  UserGroup,
};
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */
var request = require('superagent')
  , util = require('util')
  , _ = require('underscore')
  , path = require('JSONPath').eval
  , accounting = require('accounting')
  , currency = require('currency-symbol-map')
  , parse = require('xml2js').parseString;

/**
 * API endpoint
 */
var endpoint = 'http://svcs.ebay.com/services/search/FindingService/v1';

var extractions = [
  { name: 'id', query: '$..itemId[0]' },
  { name: 'name', query: '$..title[0]' },
  { name: 'url', query: '$..viewItemURL[0]' },
  { name: 'offerPrice',
    query: '$..sellingStatus..convertedCurrentPrice[0]',
    transform: formatPrice
  },
  { name: 'listPrice',
    query: '$..discountPriceInfo..originalRetailPrice[0]',
    transform: formatPrice
  },
  {
    name: 'eek',
    query: '$..eekStatus[0]'
  }
];

module.exports = function (itemId) {
  return new Ebay(itemId);
};

var Ebay = function (itemId) {
  this._filters = {};

  if ('object' === typeof itemId) {
    if (itemId.keywords) this.keywords = itemId.keywords, this.mode = 'search';
    if (itemId.itemId) this.itemId = itemId, this.mode = 'lookup';
  } else {
    this.itemId = itemId;
  }
};

Ebay.prototype.id = function (id) {
  return this.id = id, this;
};

var countryId = [
  {countryCode: 'AT', globalId: 'EBAY-AT' },
  {countryCode: 'AU', globalId: 'EBAY-AU'},
  {countryCode: 'CH', globalId: 'EBAY-CH'},
  {countryCode: 'DE', globalId: 'EBAY-DE'},
  {countryCode: 'CA', globalId: 'EBAY-ENCA'},
  {countryCode: 'ES', globalId: 'EBAY-ES'},
  {countryCode: 'FR', globalId: 'EBAY-FR'},
  {countryCode: 'BE', globalId: 'EBAY-FRBE'},
  {countryCode: 'GB', globalId: 'EBAY-GB'},
  {countryCode: 'HK', globalId: 'EBAY-HK'},
  {countryCode: 'IE', globalId: 'EBAY-IE'},
  {countryCode: 'IN', globalId: 'EBAY-IN'},
  {countryCode: 'IT', globalId: 'EBAY-IT'},
  {countryCode: 'MY', globalId: 'EBAY-MY'},
  {countryCode: 'NL', globalId: 'EBAY-NL'},
  {countryCode: 'PH', globalId: 'EBAY-PH'},
  {countryCode: 'PL', globalId: 'EBAY-PL'},
  {countryCode: 'SG', globalId: 'EBAY-SG'},
  {countryCode: 'US', globalId: 'EBAY-US'}
];
var defaultCountry = 'EBAY-US';
Ebay.prototype.country = function (country) {
  // Lookup matching country
  var results = _.where(countryId, {countryCode: country});

  // Set endpoint
  this.globalId = results.length ? results[0].globalId : null;

  // Set country
  this.country = country;

  // Return this for chaining
  return this;
};

Ebay.prototype.location = function (country) {
  this._filters['LocatedIn'] = [country];

  return this;
};

Ebay.prototype.affiliate = function (id) {
  return this.trackingId = id, this;
};

var networks = [
  { id: 2, name: 'be free' },
  { id: 3, name: 'affilinet' },
  { id: 4, name: 'tradedoubler' },
  { id: 5, name: 'mediaplex' },
  { id: 6, name: 'doubleclick' },
  { id: 7, name: 'allyes' },
  { id: 8, name: 'bjmt' },
  { id: 9, name: 'ebay' }
];
var defaultNetwork = 9;
Ebay.prototype.network = function (network) {
  var results = _.where(networks, {name: network.toLowerCase()});

  // Set id
  this.networkId = results.length ? results[0].id : null;

  // Return this for chaining
  return this;
};

var listing = [
  { name: 'auction', id: 'Auction' },
  { name: 'buy it now', id: 'AuctionWithBIN' },
  { name: 'classified', id: 'Classified' },
  { name: 'fixed', id: 'FixedPrice' },
  { name: 'all', id: 'All' }
];

Ebay.prototype.type = function (type) {
  var results = _.where(listing, {name: type.toLowerCase()});

  // Choke on invalid listing type
  if (!results.length) throw new Error('Invalid listing type');

  if (!this._filters['ListingType']) {
    this._filters['ListingType'] = [];
  }

  // Add to filters
  this._filters['ListingType'].push(results[0].id);
  // Chain
  return this;
};

Ebay.prototype.condition = function (cond) {
  return this._filters['Condition'] = [capitalize(cond)], this;
};

Ebay.prototype.price = function (range) {
  // Convert to array if necessary
  range = _.isString(range) ? range.split('..') : range;

  // Add limits to filters
  if (range[0]) this._filters['MinPrice'] = [range[0]];
  if (range[1]) this._filters['MaxPrice'] = [range[1]];

  // Chain
  return this;
};

Ebay.prototype.category = function (id) {
  return this._categoryid = id, this;
};

Ebay.prototype.addExtractionRule = function (rule) {
  extractions.push(rule);
  return this;
};

Ebay.prototype.done = function (cb) {
  var r = request.get(endpoint)
    , that = this;

  // Add easy fields
  r
    .query({'OPERATION-NAME': 'findItemsAdvanced'})
    .query({'SERVICE-VERSION': '1.0.0'})
    .query({'SECURITY-APPNAME': this.id})
    .query({'GLOBAL-ID': this.globalId || defaultCountry})
    .query({'RESPONSE-DATA-FORMAT': 'XML'})
    .query({'REST-PAYLOAD': ''}) // Why, ebay? Why?
    .query({'paginationInput.entriesPerPage': 1})
    .query({keywords: this.keywords})
    .query({'affiliate.trackingId': this.trackingId})
    .query({'affiliate.networkId': this.networkId || defaultNetwork})
    .query({'categoryId': this._categoryid});

  // Add filters
  _
    .chain(this._filters)
    .pairs()
    .map(map)
    .flatten()
    .each(r.query, r);

  function map (x, i) {
    var nameTemplate = 'itemFilter(%d).name=%s'
      , valueTemplate = 'itemFilter(%d).value(%d)=%s';

    // Holy shit what is that
    return [
      util.format(nameTemplate, i, x[0]),
      _.map(x[1], function (y, j) {
        return util.format(valueTemplate, i, j, y);
      })
    ];
  }

  // Run it
  r.end(function (err, res) {
    if (err) return cb(err);

    return parse(res.text, function (err, p) {
      // Parsing errors
      if (err) return cb(err);

      // API errors
      if (err = parseErr(p)) return cb(err);

      // Return result
      return cb(null, parseResults.call(that, p, extractions));
    });
  });
};

var parseErr = function (obj) {
  var result = path(obj, '$..error..message[0]');
  return result.length ? new Error(result[0]) : null;
};

var first = function (obj, query) {
  var result = path(obj, query);
  return result.length ? result[0] : null;
};

var formatPrice = function (obj) {
  var amount = obj._
    , code = obj.$ && obj.$.currencyId
    , thousand, decimal;

  if (!amount || !currency) return null;

  if (~['DE'].indexOf(this.country)) {
    thousand = '.'; decimal = ',';
  } else {
    thousand = ','; decimal = '.';
  }

  return accounting.formatMoney(amount, currency(code), null, thousand, decimal);
};

var parseResults = function (obj, extractions) {
  var that = this;

  var res = _
    .chain(extractions)
    .map(function (x) {
      var key = x.name
        , val = first(obj, x.query);

      // Transform value if we have a transform available
      if (x.transform && val !== null) val = x.transform.call(that, val);

      return [key, val];
    })
    .filter(function (x) {
      return x[1] !== null;
    })
    .object()
    .value();

  return _.keys(res).length ? res : null;
};

var capitalize = function (str) {
  return str.slice(0,1).toUpperCase() + str.slice(1)
};
<commit_msg>fix: price transform bug<commit_after>/**
 * Module dependencies
 */
var request = require('superagent')
  , util = require('util')
  , _ = require('underscore')
  , path = require('JSONPath').eval
  , accounting = require('accounting')
  , currency = require('currency-symbol-map')
  , parse = require('xml2js').parseString;

/**
 * API endpoint
 */
var endpoint = 'http://svcs.ebay.com/services/search/FindingService/v1';

var formatPrice = function (obj) {
  var amount = obj._
    , code = obj.$ && obj.$.currencyId
    , thousand, decimal;

  if (!amount || !currency) return null;

  if (~['DE'].indexOf(this.country)) {
    thousand = '.'; decimal = ',';
  } else {
    thousand = ','; decimal = '.';
  }

  return accounting.formatMoney(amount, currency(code), null, thousand, decimal);
};

var extractions = [
  { name: 'id', query: '$..itemId[0]' },
  { name: 'name', query: '$..title[0]' },
  { name: 'url', query: '$..viewItemURL[0]' },
  { name: 'offerPrice',
    query: '$..sellingStatus..convertedCurrentPrice[0]',
    transform: formatPrice
  },
  { name: 'listPrice',
    query: '$..discountPriceInfo..originalRetailPrice[0]',
    transform: formatPrice
  },
  {
    name: 'eek',
    query: '$..eekStatus[0]'
  }
];

module.exports = function (itemId) {
  return new Ebay(itemId);
};

var Ebay = function (itemId) {
  this._filters = {};

  if ('object' === typeof itemId) {
    if (itemId.keywords) this.keywords = itemId.keywords, this.mode = 'search';
    if (itemId.itemId) this.itemId = itemId, this.mode = 'lookup';
  } else {
    this.itemId = itemId;
  }
};

Ebay.prototype.id = function (id) {
  return this.id = id, this;
};

var countryId = [
  {countryCode: 'AT', globalId: 'EBAY-AT' },
  {countryCode: 'AU', globalId: 'EBAY-AU'},
  {countryCode: 'CH', globalId: 'EBAY-CH'},
  {countryCode: 'DE', globalId: 'EBAY-DE'},
  {countryCode: 'CA', globalId: 'EBAY-ENCA'},
  {countryCode: 'ES', globalId: 'EBAY-ES'},
  {countryCode: 'FR', globalId: 'EBAY-FR'},
  {countryCode: 'BE', globalId: 'EBAY-FRBE'},
  {countryCode: 'GB', globalId: 'EBAY-GB'},
  {countryCode: 'HK', globalId: 'EBAY-HK'},
  {countryCode: 'IE', globalId: 'EBAY-IE'},
  {countryCode: 'IN', globalId: 'EBAY-IN'},
  {countryCode: 'IT', globalId: 'EBAY-IT'},
  {countryCode: 'MY', globalId: 'EBAY-MY'},
  {countryCode: 'NL', globalId: 'EBAY-NL'},
  {countryCode: 'PH', globalId: 'EBAY-PH'},
  {countryCode: 'PL', globalId: 'EBAY-PL'},
  {countryCode: 'SG', globalId: 'EBAY-SG'},
  {countryCode: 'US', globalId: 'EBAY-US'}
];
var defaultCountry = 'EBAY-US';
Ebay.prototype.country = function (country) {
  // Lookup matching country
  var results = _.where(countryId, {countryCode: country});

  // Set endpoint
  this.globalId = results.length ? results[0].globalId : null;

  // Set country
  this.country = country;

  // Return this for chaining
  return this;
};

Ebay.prototype.location = function (country) {
  this._filters['LocatedIn'] = [country];

  return this;
};

Ebay.prototype.affiliate = function (id) {
  return this.trackingId = id, this;
};

var networks = [
  { id: 2, name: 'be free' },
  { id: 3, name: 'affilinet' },
  { id: 4, name: 'tradedoubler' },
  { id: 5, name: 'mediaplex' },
  { id: 6, name: 'doubleclick' },
  { id: 7, name: 'allyes' },
  { id: 8, name: 'bjmt' },
  { id: 9, name: 'ebay' }
];
var defaultNetwork = 9;
Ebay.prototype.network = function (network) {
  var results = _.where(networks, {name: network.toLowerCase()});

  // Set id
  this.networkId = results.length ? results[0].id : null;

  // Return this for chaining
  return this;
};

var listing = [
  { name: 'auction', id: 'Auction' },
  { name: 'buy it now', id: 'AuctionWithBIN' },
  { name: 'classified', id: 'Classified' },
  { name: 'fixed', id: 'FixedPrice' },
  { name: 'all', id: 'All' }
];

Ebay.prototype.type = function (type) {
  var results = _.where(listing, {name: type.toLowerCase()});

  // Choke on invalid listing type
  if (!results.length) throw new Error('Invalid listing type');

  if (!this._filters['ListingType']) {
    this._filters['ListingType'] = [];
  }

  // Add to filters
  this._filters['ListingType'].push(results[0].id);
  // Chain
  return this;
};

Ebay.prototype.condition = function (cond) {
  return this._filters['Condition'] = [capitalize(cond)], this;
};

Ebay.prototype.price = function (range) {
  // Convert to array if necessary
  range = _.isString(range) ? range.split('..') : range;

  // Add limits to filters
  if (range[0]) this._filters['MinPrice'] = [range[0]];
  if (range[1]) this._filters['MaxPrice'] = [range[1]];

  // Chain
  return this;
};

Ebay.prototype.category = function (id) {
  return this._categoryid = id, this;
};

Ebay.prototype.addExtractionRule = function (rule) {
  extractions.push(rule);
  return this;
};

Ebay.prototype.done = function (cb) {
  var r = request.get(endpoint)
    , that = this;

  // Add easy fields
  r
    .query({'OPERATION-NAME': 'findItemsAdvanced'})
    .query({'SERVICE-VERSION': '1.0.0'})
    .query({'SECURITY-APPNAME': this.id})
    .query({'GLOBAL-ID': this.globalId || defaultCountry})
    .query({'RESPONSE-DATA-FORMAT': 'XML'})
    .query({'REST-PAYLOAD': ''}) // Why, ebay? Why?
    .query({'paginationInput.entriesPerPage': 1})
    .query({keywords: this.keywords})
    .query({'affiliate.trackingId': this.trackingId})
    .query({'affiliate.networkId': this.networkId || defaultNetwork})
    .query({'categoryId': this._categoryid});

  // Add filters
  _
    .chain(this._filters)
    .pairs()
    .map(map)
    .flatten()
    .each(r.query, r);

  function map (x, i) {
    var nameTemplate = 'itemFilter(%d).name=%s'
      , valueTemplate = 'itemFilter(%d).value(%d)=%s';

    // Holy shit what is that
    return [
      util.format(nameTemplate, i, x[0]),
      _.map(x[1], function (y, j) {
        return util.format(valueTemplate, i, j, y);
      })
    ];
  }

  // Run it
  r.end(function (err, res) {
    if (err) return cb(err);

    return parse(res.text, function (err, p) {
      // Parsing errors
      if (err) return cb(err);

      // API errors
      if (err = parseErr(p)) return cb(err);

      // Return result
      return cb(null, parseResults.call(that, p, extractions));
    });
  });
};

var parseErr = function (obj) {
  var result = path(obj, '$..error..message[0]');
  return result.length ? new Error(result[0]) : null;
};

var first = function (obj, query) {
  var result = path(obj, query);
  return result.length ? result[0] : null;
};

var parseResults = function (obj, extractions) {
  var that = this;

  var res = _
    .chain(extractions)
    .map(function (x) {
      var key = x.name
        , val = first(obj, x.query);

      // Transform value if we have a transform available
      if (x.transform && val !== null) val = x.transform.call(that, val);

      return [key, val];
    })
    .filter(function (x) {
      return x[1] !== null;
    })
    .object()
    .value();

  return _.keys(res).length ? res : null;
};

var capitalize = function (str) {
  return str.slice(0,1).toUpperCase() + str.slice(1)
};
<|endoftext|>


<commit_before>/* jshint loopfunc: true, evil: true */
'use strict';

const util = require('util');
const EventEmitter = require('events').EventEmitter;

const Session = require('msgpack5rpc');
const _ = require('lodash');
const traverse = require('traverse');

const RE_VERSION = /^\n*NVIM v(\d+)\.(\d+)\.(\d+)(.+)\s*\n/;

function Nvim(session, channel_id) {
    this._session = session;
    this._decode = decode;
    this._channel_id = channel_id;
}
util.inherits(Nvim, EventEmitter);

function decode(obj) {
    traverse(obj).forEach(function(item) {
        if (item instanceof Session) {
            this.update(item, true);
        } else if (Buffer.isBuffer(item)) {
            try { this.update(item.toString('utf8')); } catch (e) {}
        }
    });

    return obj;
}

function equals(other) {
    try {
        return this._data.toString() === other._data.toString();
    } catch (e) {
        return false;
    }
}

function quit() {
    this.command('qa!', true);
}

function getVersion(cmd = 'nvim') {
    return this.commandOutput('silent version').then(out => {
        const m = out.match(RE_VERSION);
        if (m === null) {
            throw new Error(
                `Unexpected output format from '${cmd} --version': ${out}`
            );
        }

        return {
            major: parseInt(m[1], 10),
            minor: parseInt(m[2], 10),
            patch: parseInt(m[3], 10),
            rest: m[4]
        };
    });
}

function generateWrappers(Nvim, types, metadata) {
    for (let i = 0; i < metadata.functions.length; i++) {
        const func = metadata.functions[i];
        const parts = func.name.split('_');
        const typeName = _.capitalize(parts[0]);
        if (typeName === 'Vim') {
            // Skip APIs prefixed with 'vim_' because they exist for compatibility reason.
            continue;
        }
        // The type name is the word before the first dash capitalized. If the type
        // is Vim, then it a editor-global method which will be attached to the Nvim
        // class.
        const methodName = _.camelCase(
            (typeName === 'Ui' ? parts : parts.slice(1)).join('_')
        );
        let args = func.parameters.map(p => p[1]);
        let Type, callArgs;
        if (typeName === 'Nvim' || typeName === 'Ui') {
            Type = Nvim;
            callArgs = args.join(', ');
        } else {
            Type = types[typeName];
            args = args.slice(1);
            // This is a method of one of the ext types, prepend "this" to the call
            // arguments.
            callArgs = ['this'].concat(args).join(', ');
        }
        args.push('notify');
        const params = args.join(', ');
        // XXX:
        // Using string constructor because `notify` argument can't be distinguished
        // if `...args` or `arguments` used.  They need if using anonymous function.
        const method = new Function(`
                return function ${methodName}(${params}) {
                  if (notify) {
                    this._session.notify("${func.name}", [${callArgs}]);
                    return;
                  }
                  return new Promise((resolve, reject) => {
                    this._session.request("${func.name}", [${callArgs}], (err, res) => {
                     if (err) return reject(new Error(err[1]));
                     resolve(this._decode(res));
                   });
                  });
                };
            `)();
        const paramTypes = func.parameters.map(p => p[0]);
        paramTypes.push('Boolean');
        method.metadata = {
            name: methodName,
            deferred: func.deferred,
            returnType: func.return_type,
            parameters: args,
            parameterTypes: paramTypes,
            canFail: func.can_fail
        };
        if (typeName !== 'Nvim' && typeName !== 'Ui') {
            method.metadata.parameterTypes.shift();
        }
        Type.prototype[methodName] = method;
    }
}

// Note: Use callback because it may be called more than once.
module.exports.attach = function attach(writer, reader) {
    let session = new Session([]);
    let calledCallback = false;
    let nvim = new Nvim(session);
    const initSession = session;
    const pendingRPCs = [];

    session.attach(writer, reader);

    return new Promise(function(resolve, reject){
        // register initial RPC handlers to queue non-specs requests until api is generated
        session.on('request', function(method, args, resp) {
            if (method !== 'specs') {
                pendingRPCs.push({
                    type: 'request',
                    args: Array.prototype.slice.call(arguments)
                });
            } else {
                resolve(nvim); // the errback may be called later, but 'specs' must be handled
                calledCallback = true;
                nvim.emit('request', decode(method), decode(args), resp);
            }
        });

        session.on('notification', function(method, args) {
            pendingRPCs.push({
                type: 'notification',
                args: Array.prototype.slice.call( arguments )
            });
        });

        session.on('detach', function() {
            session.removeAllListeners('request');
            session.removeAllListeners('notification');
            nvim.emit('disconnect');
        });

        session.request('nvim_get_api_info', [], function(err, res) {
            if (err) {
                const msg = 'Error at initialization: nvim_get_api_info: ' + err[1];
                return reject(new Error(msg));
            }

            const channel_id = res[0];

            const metadata = decode(res[1]);
            const extTypes = [];
            const types = {};

            Object.keys(metadata.types).forEach(function(name) {
                // Generate a constructor function for each type in metadata.types
                const Type = function(session, data, decode) {
                    this._session = session;
                    this._data = data;
                    this._decode = decode;
                };
                Object.defineProperty(Type, 'name', {value: name});
                Type.prototype.equals = equals;

                // Collect the type information necessary for msgpack5 deserialization
                // when it encounters the corresponding ext code.
                extTypes.push({
                    constructor: Type,
                    code: metadata.types[name].id,
                    decode: function(data) { return new Type(session, data, decode); },
                    encode: function(obj) { return obj._data; }
                });

                types[name] = Type;
                Nvim.prototype[name] = Type;
            });

            generateWrappers(Nvim, types, metadata);
            Nvim.prototype.quit = quit;
            Nvim.prototype.getVersion = getVersion;

            session = new Session(extTypes);
            session.attach(writer, reader);

            nvim = new Nvim(session, channel_id);

            // register the non-queueing handlers
            session.on('request', function(method, args, resp) {
                nvim.emit('request', decode(method), decode(args), resp);
            });

            session.on('notification', function(method, args) {
                nvim.emit('notification', decode(method), decode(args));
            });

            session.on('detach', function() {
                session.removeAllListeners('request');
                session.removeAllListeners('notification');
                nvim.emit('disconnect');
            });

            resolve(nvim);

            // dequeue any pending RPCs
            initSession.detach();
            pendingRPCs.forEach(function(pending) {
                if(pending.type === 'request') {
                    // there's no clean way to change the output channel using the current
                    // Session abstraction
                    pending.args[pending.args.length - 1]._encoder = session._encoder;
                }
                nvim.emit.apply(nvim, [].concat(pending.type, pending.args));
            });
        });
    });
};
<commit_msg>fix error message<commit_after>/* jshint loopfunc: true, evil: true */
'use strict';

const util = require('util');
const EventEmitter = require('events').EventEmitter;

const Session = require('msgpack5rpc');
const _ = require('lodash');
const traverse = require('traverse');

const RE_VERSION = /^\n*NVIM v(\d+)\.(\d+)\.(\d+)(.+)\s*\n/;

function Nvim(session, channel_id) {
    this._session = session;
    this._decode = decode;
    this._channel_id = channel_id;
}
util.inherits(Nvim, EventEmitter);

function decode(obj) {
    traverse(obj).forEach(function(item) {
        if (item instanceof Session) {
            this.update(item, true);
        } else if (Buffer.isBuffer(item)) {
            try { this.update(item.toString('utf8')); } catch (e) {}
        }
    });

    return obj;
}

function equals(other) {
    try {
        return this._data.toString() === other._data.toString();
    } catch (e) {
        return false;
    }
}

function quit() {
    this.command('qa!', true);
}

function getVersion() {
    return this.commandOutput('silent version').then(out => {
        const m = out.match(RE_VERSION);
        if (m === null) {
            throw new Error(
                `Unexpected output format from :version command: '${out}'`
            );
        }

        return {
            major: parseInt(m[1], 10),
            minor: parseInt(m[2], 10),
            patch: parseInt(m[3], 10),
            rest: m[4]
        };
    });
}

function generateWrappers(Nvim, types, metadata) {
    for (let i = 0; i < metadata.functions.length; i++) {
        const func = metadata.functions[i];
        const parts = func.name.split('_');
        const typeName = _.capitalize(parts[0]);
        if (typeName === 'Vim') {
            // Skip APIs prefixed with 'vim_' because they exist for compatibility reason.
            continue;
        }
        // The type name is the word before the first dash capitalized. If the type
        // is Vim, then it a editor-global method which will be attached to the Nvim
        // class.
        const methodName = _.camelCase(
            (typeName === 'Ui' ? parts : parts.slice(1)).join('_')
        );
        let args = func.parameters.map(p => p[1]);
        let Type, callArgs;
        if (typeName === 'Nvim' || typeName === 'Ui') {
            Type = Nvim;
            callArgs = args.join(', ');
        } else {
            Type = types[typeName];
            args = args.slice(1);
            // This is a method of one of the ext types, prepend "this" to the call
            // arguments.
            callArgs = ['this'].concat(args).join(', ');
        }
        args.push('notify');
        const params = args.join(', ');
        // XXX:
        // Using string constructor because `notify` argument can't be distinguished
        // if `...args` or `arguments` used.  They need if using anonymous function.
        const method = new Function(`
                return function ${methodName}(${params}) {
                  if (notify) {
                    this._session.notify("${func.name}", [${callArgs}]);
                    return;
                  }
                  return new Promise((resolve, reject) => {
                    this._session.request("${func.name}", [${callArgs}], (err, res) => {
                     if (err) return reject(new Error(err[1]));
                     resolve(this._decode(res));
                   });
                  });
                };
            `)();
        const paramTypes = func.parameters.map(p => p[0]);
        paramTypes.push('Boolean');
        method.metadata = {
            name: methodName,
            deferred: func.deferred,
            returnType: func.return_type,
            parameters: args,
            parameterTypes: paramTypes,
            canFail: func.can_fail
        };
        if (typeName !== 'Nvim' && typeName !== 'Ui') {
            method.metadata.parameterTypes.shift();
        }
        Type.prototype[methodName] = method;
    }
}

// Note: Use callback because it may be called more than once.
module.exports.attach = function attach(writer, reader) {
    let session = new Session([]);
    let calledCallback = false;
    let nvim = new Nvim(session);
    const initSession = session;
    const pendingRPCs = [];

    session.attach(writer, reader);

    return new Promise(function(resolve, reject){
        // register initial RPC handlers to queue non-specs requests until api is generated
        session.on('request', function(method, args, resp) {
            if (method !== 'specs') {
                pendingRPCs.push({
                    type: 'request',
                    args: Array.prototype.slice.call(arguments)
                });
            } else {
                resolve(nvim); // the errback may be called later, but 'specs' must be handled
                calledCallback = true;
                nvim.emit('request', decode(method), decode(args), resp);
            }
        });

        session.on('notification', function(method, args) {
            pendingRPCs.push({
                type: 'notification',
                args: Array.prototype.slice.call( arguments )
            });
        });

        session.on('detach', function() {
            session.removeAllListeners('request');
            session.removeAllListeners('notification');
            nvim.emit('disconnect');
        });

        session.request('nvim_get_api_info', [], function(err, res) {
            if (err) {
                const msg = 'Error at initialization: nvim_get_api_info: ' + err[1];
                return reject(new Error(msg));
            }

            const channel_id = res[0];

            const metadata = decode(res[1]);
            const extTypes = [];
            const types = {};

            Object.keys(metadata.types).forEach(function(name) {
                // Generate a constructor function for each type in metadata.types
                const Type = function(session, data, decode) {
                    this._session = session;
                    this._data = data;
                    this._decode = decode;
                };
                Object.defineProperty(Type, 'name', {value: name});
                Type.prototype.equals = equals;

                // Collect the type information necessary for msgpack5 deserialization
                // when it encounters the corresponding ext code.
                extTypes.push({
                    constructor: Type,
                    code: metadata.types[name].id,
                    decode: function(data) { return new Type(session, data, decode); },
                    encode: function(obj) { return obj._data; }
                });

                types[name] = Type;
                Nvim.prototype[name] = Type;
            });

            generateWrappers(Nvim, types, metadata);
            Nvim.prototype.quit = quit;
            Nvim.prototype.getVersion = getVersion;

            session = new Session(extTypes);
            session.attach(writer, reader);

            nvim = new Nvim(session, channel_id);

            // register the non-queueing handlers
            session.on('request', function(method, args, resp) {
                nvim.emit('request', decode(method), decode(args), resp);
            });

            session.on('notification', function(method, args) {
                nvim.emit('notification', decode(method), decode(args));
            });

            session.on('detach', function() {
                session.removeAllListeners('request');
                session.removeAllListeners('notification');
                nvim.emit('disconnect');
            });

            resolve(nvim);

            // dequeue any pending RPCs
            initSession.detach();
            pendingRPCs.forEach(function(pending) {
                if(pending.type === 'request') {
                    // there's no clean way to change the output channel using the current
                    // Session abstraction
                    pending.args[pending.args.length - 1]._encoder = session._encoder;
                }
                nvim.emit.apply(nvim, [].concat(pending.type, pending.args));
            });
        });
    });
};
<|endoftext|>


<commit_before>import { without } from 'substance'
import {
  DynamicCollection,
  StringModel, TextModel, FlowContentModel,
  EditorAPI, InternalEditingAPI
} from '../kit'
import renderEntity from './shared/renderEntity'
import TranslateableModel from './models/TranslateableModel'
import { REQUIRED_PROPERTIES } from './ArticleConstants'
import TableEditingAPI from './shared/TableEditingAPI'
import { generateTable } from './shared/tableHelpers'
import { createEmptyElement, importFigure, setContainerSelection } from './articleHelpers'

// TODO: this should come from configuration
const COLLECTIONS = {
  'author': 'authors',
  'editor': 'editors'
}

export default class ArticleAPI extends EditorAPI {
  constructor (articleSession, config, archive) {
    super()
    this.config = config
    this.modelRegistry = config.getModelRegistry()
    this.articleSession = articleSession
    this.article = articleSession.getDocument()
    this.archive = archive
    this._tableApi = new TableEditingAPI(articleSession)
    this._modelCache = new Map()
  }

  /*
    Get corresponding model for a given node. This used for most block content types (e.g. Figure, Heading etc.)
  */
  getModel (type, node) {
    let ModelClass = this.modelRegistry[type]
    // HACK: trying to retrieve the node if it is not given
    if (!node) {
      node = this.article.get(type)
    }
    if (ModelClass) {
      return new ModelClass(this, node)
    } else if (node) {
      return this._getModelForNode(node)
    }
  }

  getModelById (id) {
    let node = this.article.get(id)
    if (node) {
      // caching the model for a node so that we provide the same instance every time
      if (this._modelCache.has(node.id)) return this._modelCache.get(node.id)

      // now check if there is a custom model for this type
      let ModelClass = this.modelRegistry[node.type]
      // TODO: we could go and check if there is a component
      // registered for any of the parent types
      if (!ModelClass) {
        let superTypes = node.getSchema().getSuperTypes()
        for (let superType of superTypes) {
          ModelClass = this.modelRegistry[superType]
          if (ModelClass) break
        }
      }
      if (ModelClass) {
        return new ModelClass(this, node)
      }
      let model = this._getModelForNode(node)
      if (model) {
        this._modelCache.set(node.id, model)
        return model
      }
    }
  }

  _getNode (nodeId) {
    return this.article.get(nodeId)
  }

  // TODO: this should be configurable. As it is similar to HTML conversion
  // we could use the converter registry for this
  renderEntity (model) {
    let entity = this.getArticle().get(model.id)
    let exporter = this.config.getExporter('html')
    return renderEntity(entity, exporter)
  }

  getArticle () {
    return this.article
  }

  getArticleSession () {
    return this.articleSession
  }

  addItemToCollection (item, collection) {
    this.articleSession.transaction(tx => {
      let node = tx.create(item)
      tx.get(collection._node.id).appendChild(node)
      let newSelection = this._selectFirstRequiredProperty(node)
      tx.setSelection(newSelection)
    })
  }

  addItemsToCollection (items, collection) {
    if (items.length === 0) return
    this.articleSession.transaction(tx => {
      for (let i = 0; i < items.length; i++) {
        let item = items[i]
        let node = tx.create(item)
        tx.get(collection._node.id).appendChild(node)
        // put the cursor into the first item
        // TODO: or should it be the last one?
        if (i === 0) {
          let newSelection = this._selectFirstRequiredProperty(node)
          tx.setSelection(newSelection)
        }
      }
    })
  }

  removeItemFromCollection (item, collection) {
    const collectionId = collection.id
    this.articleSession.transaction(tx => {
      tx.get(collectionId).removeChild(tx.get(item.id))
      tx.delete(item.id)
      tx.selection = null
    })
  }

  moveCollectionItem (collection, from, to) {
    this.articleSession.transaction(tx => {
      let colNode = tx.get(collection._node.id)
      let item = colNode.getChildAt(from)
      colNode.removeAt(from)
      colNode.insertAt(to, item)
    })
  }

  getAuthorsModel () {
    return this.getModel('authors')
  }

  addReferences (items, collection) {
    const refContribProps = ['authors', 'editors', 'inventors', 'sponsors', 'translators']
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let refs = tx.get(collection._node.id)
      items.forEach((item, i) => {
        refContribProps.forEach(propName => {
          if (item[propName]) {
            let refContribs = item[propName].map(contrib => {
              contrib.type = 'ref-contrib'
              const node = tx.create(contrib)
              return node.id
            })
            item[propName] = refContribs
          }
        })

        let node = tx.create(item)
        refs.appendChild(tx.get(node.id))
        if (i === 0) {
          let newSelection = this._selectFirstRequiredProperty(node)
          tx.setSelection(newSelection)
        }
      })
    })
  }

  deleteReference (item, collection) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xrefIndex = tx.getIndex('xrefs')
      const xrefs = xrefIndex.get(item.id)
      tx.get(collection._node.id).removeChild(tx.get(item.id))
      xrefs.forEach(xrefId => {
        let xref = tx.get(xrefId)
        let idrefs = xref.attr('rid').split(' ')
        idrefs = without(idrefs, item.id)
        xref.setAttribute('rid', idrefs.join(' '))
      })
      tx.delete(item.id)
      tx.selection = null
    })
  }

  /*
    @return {StringModel} Model for the language code of article's main language
  */
  getOriginalLanguageCode () {
    let article = this.getArticle().getRootNode()
    return new StringModel(this, [article.id, 'attributes', 'xml:lang'])
  }

  getCollectionForType (type) {
    // TODO: need to rethink this.
    // ATM we are registering special model classes for collections,
    // which are named after the single entity they contain,
    // e.g. 'authors' is a the collection of author nodes (which are person nodes essentially).
    // This is currently pretty confusing. It would be better to have
    // specific node in the model for that.
    // Still, we would need a mechanism to specify which node is acting as the parent
    // collection for which node.
    let collectionType = COLLECTIONS[type]
    // I don't like this implicit mapping.
    if (!collectionType) {
      collectionType = type + 's'
    }
    let model = this.getModel(collectionType)
    if (!model) {
      console.error(`No collection specified for type '${type}'. Using DynamicCollection. You should register a collection for this type explicitly.`)
      model = new DynamicCollection(this, type)
    }
    return model
  }

  getSchema (type) {
    return this.article.getSchema().getNodeSchema(type)
  }

  getArticleTitle () {
    let titleNode = this.getArticle().get('title')
    return new TextModel(this, titleNode.getPath())
  }

  getArticleAbstract () {
    let abstract = this.getArticle().get('abstract')
    return new FlowContentModel(this, abstract.getContentPath())
  }

  getArticleBody () {
    let body = this.getArticle().get('body')
    return new FlowContentModel(this, body.getContentPath())
  }

  getFigures () {
    let figs = this.getArticle().get('body').findAll('fig')
    return figs.map(fig => this.getModel(fig.type, fig))
  }

  getTranslatables () {
    const translatableItems = ['title', 'abstract']
    const article = this.getArticle()
    const models = translatableItems.map(item => new TranslateableModel(this, article.get(item)))
    return models
  }

  addTranslation (model, languageCode) {
    const isText = model._node.isText()
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let item = {
        language: languageCode
      }
      if (isText) {
        item.type = 'text-translation'
      } else {
        item.type = 'container-translation'
      }
      let node = tx.create(item)
      // HACK: trying to avoid selection errors of empty container
      if (!isText) node.append(tx.create({type: 'p'}))
      let length = tx.get([model.id, 'translations']).length
      tx.update([model.id, 'translations'], { type: 'insert', pos: length, value: node.id })
      tx.selection = null
    })
  }

  deleteTranslation (translatableModel, translationModel) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let translatable = tx.get(translatableModel.id)
      let pos = translatable.translations.indexOf(translationModel.id)
      if (pos !== -1) {
        tx.update([translatableModel.id, 'translations'], { type: 'delete', pos: pos })
      }
      tx.delete(translationModel.id)
      tx.selection = null
    })
  }

  addXrefTarget (targetId, model) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xref = tx.get(model.id)
      let targets = xref.getAttribute('rid').split(' ')
      targets.push(targetId)
      xref.setAttribute('rid', targets.join(' '))
    })
  }

  removeXrefTarget (targetId, model) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xref = tx.get(model.id)
      let targets = xref.getAttribute('rid').split(' ')
      let idx = targets.indexOf(targetId)
      targets.splice(idx, 1)
      xref.setAttribute('rid', targets.join(' '))
    })
  }

  _getContext () {
    return this._context
  }

  /* Low-level content editing API */

  copy () {
    if (this._tableApi.isTableSelected()) {
      return this._tableApi.copySelection()
    } else {
      return super.copy()
    }
  }

  paste (content, options) {
    if (this._tableApi.isTableSelected()) {
      return this._tableApi.paste(content, options)
    } else {
      return super.paste(content, options)
    }
  }

  insertText (text) {
    if (this._tableApi.isTableSelected()) {
      this._tableApi.insertText(text)
    } else {
      return super.insertText(text)
    }
  }

  _createTextNode (tx, container, text) {
    // TODO: for Container nodes we should define the default text type
    // maybe even via a schema attribute
    return tx.create({ type: 'p', content: text })
  }

  _createListNode (tx, container, params) {
    let el = tx.create({ type: 'list' })
    if (params.listType) {
      el.attr('list-type', params.listType)
    }
    return el
  }

  getTableAPI () {
    return this._tableApi
  }

  /*
    TODO: In the future it should be necessary to expose those managers, instead
    API's should be used to access information.
  */
  getFigureManager () {
    return this.getArticleSession().getFigureManager()
  }

  getFootnoteManager () {
    return this.getArticleSession().getFootnoteManager()
  }

  getReferenceManager () {
    return this.getArticleSession().getReferenceManager()
  }

  getTableManager () {
    return this.getArticleSession().getTableManager()
  }

  get doc () {
    console.error('DEPRECATED: use api.getArticle() instead.')
    return this.getArticle()
  }

  _getDocument () {
    return this.getArticleSession().getDocument()
  }

  _getDocumentSession () {
    return this.getArticleSession()
  }

  _getEditorSession () {
    return this.articleSession
  }

  _isPropertyRequired (type, propertyName) {
    let REQUIRED = REQUIRED_PROPERTIES[type]
    if (REQUIRED) return REQUIRED.has(propertyName)
    return false
  }

  _selectFirstRequiredProperty (node) {
    let requiredProps = REQUIRED_PROPERTIES[node.type]
    if (requiredProps) {
      let propName = Array.from(requiredProps)[0]
      let path = [node.id, propName]
      return {
        type: 'property',
        path,
        startOffset: 0,
        surfaceId: `${path.join('.')}`
      }
    }
  }

  // TODO: can we improve this?
  // Here we would need a transaction on archive level, creating assets, plus placing them inside the article body.
  _insertFigures (files) {
    const articleSession = this.articleSession
    let paths = files.map(file => {
      return this.archive.createFile(file)
    })
    let sel = articleSession.getSelection()
    if (!sel || !sel.containerId) return
    articleSession.transaction(tx => {
      importFigure(tx, sel, files, paths)
    })
  }

  // TODO: Use JATS snippet for table creation
  _createTableFigure (tx, params) {
    let caption = tx.createElement('caption').append(
      tx.createElement('p')
    )
    let permission = tx.create({
      type: 'permission'
    })
    let table = generateTable(tx, params.rows, params.columns)
    let tableFigure = tx.create({
      type: 'table-figure',
      caption: caption.id,
      content: table.id,
      permission: permission.id
    })

    return tableFigure
  }

  _insertFootnote (item, footnotes) {
    const collectionId = footnotes.id
    this.articleSession.transaction(tx => {
      const node = createEmptyElement(tx, 'footnote')
      tx.get(collectionId).appendChild(
        node
      )
      setContainerSelection(tx, node)
    })
  }

  _insertPerson (person, collection) {
    const collectionId = collection.id
    this.articleSession.transaction(tx => {
      let bio = tx.create({type: 'bio'}).append(
        tx.create({type: 'p'})
      )
      person.bio = bio.id
      let node = tx.create(person)
      tx.get(collectionId).appendChild(node)
      let newSelection = this._selectFirstRequiredProperty(node)
      tx.setSelection(newSelection)
    })

    // TODO: for snippet importing we need to register a contrib converter
    // and take care of groups

    // const collectionId = collection.id
    // this.articleSession.transaction(tx => {
    //   const node = createEmptyElement(tx, 'person')
    //   tx.get(collectionId).appendChild(
    //     node
    //   )
    //   let newSelection = this._selectFirstRequiredProperty(node)
    //   tx.setSelection(newSelection)
    // })
  }

  _getSelection () {
    return this.articleSession.editorState.selection
  }

  _setSelection (selData) {
    this.articleSession.setSelection(selData)
  }

  _createInternalEditorAPI () {
    return new InternalArticleEditingAPI()
  }
}

class InternalArticleEditingAPI extends InternalEditingAPI {
  createTextNode (tx, container, text) {
    // TODO: for Container nodes we should define the default text type
    // maybe even via a schema attribute
    return tx.create({ type: 'p', content: text })
  }

  createListNode (tx, container, params) {
    let el = tx.create({ type: 'list' })
    if (params.listType) {
      el.attr('list-type', params.listType)
    }
    return el
  }
}
<commit_msg>Table insertion.<commit_after>import { without } from 'substance'
import {
  DynamicCollection,
  StringModel, TextModel, FlowContentModel,
  EditorAPI, InternalEditingAPI
} from '../kit'
import renderEntity from './shared/renderEntity'
import TranslateableModel from './models/TranslateableModel'
import { REQUIRED_PROPERTIES } from './ArticleConstants'
import TableEditingAPI from './shared/TableEditingAPI'
import {
  createEmptyElement, importFigure,
  insertTableFigure, setContainerSelection
} from './articleHelpers'

// TODO: this should come from configuration
const COLLECTIONS = {
  'author': 'authors',
  'editor': 'editors'
}

export default class ArticleAPI extends EditorAPI {
  constructor (articleSession, config, archive) {
    super()
    this.config = config
    this.modelRegistry = config.getModelRegistry()
    this.articleSession = articleSession
    this.article = articleSession.getDocument()
    this.archive = archive
    this._tableApi = new TableEditingAPI(articleSession)
    this._modelCache = new Map()
  }

  /*
    Get corresponding model for a given node. This used for most block content types (e.g. Figure, Heading etc.)
  */
  getModel (type, node) {
    let ModelClass = this.modelRegistry[type]
    // HACK: trying to retrieve the node if it is not given
    if (!node) {
      node = this.article.get(type)
    }
    if (ModelClass) {
      return new ModelClass(this, node)
    } else if (node) {
      return this._getModelForNode(node)
    }
  }

  getModelById (id) {
    let node = this.article.get(id)
    if (node) {
      // caching the model for a node so that we provide the same instance every time
      if (this._modelCache.has(node.id)) return this._modelCache.get(node.id)

      // now check if there is a custom model for this type
      let ModelClass = this.modelRegistry[node.type]
      // TODO: we could go and check if there is a component
      // registered for any of the parent types
      if (!ModelClass) {
        let superTypes = node.getSchema().getSuperTypes()
        for (let superType of superTypes) {
          ModelClass = this.modelRegistry[superType]
          if (ModelClass) break
        }
      }
      if (ModelClass) {
        return new ModelClass(this, node)
      }
      let model = this._getModelForNode(node)
      if (model) {
        this._modelCache.set(node.id, model)
        return model
      }
    }
  }

  _getNode (nodeId) {
    return this.article.get(nodeId)
  }

  // TODO: this should be configurable. As it is similar to HTML conversion
  // we could use the converter registry for this
  renderEntity (model) {
    let entity = this.getArticle().get(model.id)
    let exporter = this.config.getExporter('html')
    return renderEntity(entity, exporter)
  }

  getArticle () {
    return this.article
  }

  getArticleSession () {
    return this.articleSession
  }

  addItemToCollection (item, collection) {
    this.articleSession.transaction(tx => {
      let node = tx.create(item)
      tx.get(collection._node.id).appendChild(node)
      let newSelection = this._selectFirstRequiredProperty(node)
      tx.setSelection(newSelection)
    })
  }

  addItemsToCollection (items, collection) {
    if (items.length === 0) return
    this.articleSession.transaction(tx => {
      for (let i = 0; i < items.length; i++) {
        let item = items[i]
        let node = tx.create(item)
        tx.get(collection._node.id).appendChild(node)
        // put the cursor into the first item
        // TODO: or should it be the last one?
        if (i === 0) {
          let newSelection = this._selectFirstRequiredProperty(node)
          tx.setSelection(newSelection)
        }
      }
    })
  }

  removeItemFromCollection (item, collection) {
    const collectionId = collection.id
    this.articleSession.transaction(tx => {
      tx.get(collectionId).removeChild(tx.get(item.id))
      tx.delete(item.id)
      tx.selection = null
    })
  }

  moveCollectionItem (collection, from, to) {
    this.articleSession.transaction(tx => {
      let colNode = tx.get(collection._node.id)
      let item = colNode.getChildAt(from)
      colNode.removeAt(from)
      colNode.insertAt(to, item)
    })
  }

  getAuthorsModel () {
    return this.getModel('authors')
  }

  addReferences (items, collection) {
    const refContribProps = ['authors', 'editors', 'inventors', 'sponsors', 'translators']
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let refs = tx.get(collection._node.id)
      items.forEach((item, i) => {
        refContribProps.forEach(propName => {
          if (item[propName]) {
            let refContribs = item[propName].map(contrib => {
              contrib.type = 'ref-contrib'
              const node = tx.create(contrib)
              return node.id
            })
            item[propName] = refContribs
          }
        })

        let node = tx.create(item)
        refs.appendChild(tx.get(node.id))
        if (i === 0) {
          let newSelection = this._selectFirstRequiredProperty(node)
          tx.setSelection(newSelection)
        }
      })
    })
  }

  deleteReference (item, collection) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xrefIndex = tx.getIndex('xrefs')
      const xrefs = xrefIndex.get(item.id)
      tx.get(collection._node.id).removeChild(tx.get(item.id))
      xrefs.forEach(xrefId => {
        let xref = tx.get(xrefId)
        let idrefs = xref.attr('rid').split(' ')
        idrefs = without(idrefs, item.id)
        xref.setAttribute('rid', idrefs.join(' '))
      })
      tx.delete(item.id)
      tx.selection = null
    })
  }

  /*
    @return {StringModel} Model for the language code of article's main language
  */
  getOriginalLanguageCode () {
    let article = this.getArticle().getRootNode()
    return new StringModel(this, [article.id, 'attributes', 'xml:lang'])
  }

  getCollectionForType (type) {
    // TODO: need to rethink this.
    // ATM we are registering special model classes for collections,
    // which are named after the single entity they contain,
    // e.g. 'authors' is a the collection of author nodes (which are person nodes essentially).
    // This is currently pretty confusing. It would be better to have
    // specific node in the model for that.
    // Still, we would need a mechanism to specify which node is acting as the parent
    // collection for which node.
    let collectionType = COLLECTIONS[type]
    // I don't like this implicit mapping.
    if (!collectionType) {
      collectionType = type + 's'
    }
    let model = this.getModel(collectionType)
    if (!model) {
      console.error(`No collection specified for type '${type}'. Using DynamicCollection. You should register a collection for this type explicitly.`)
      model = new DynamicCollection(this, type)
    }
    return model
  }

  getSchema (type) {
    return this.article.getSchema().getNodeSchema(type)
  }

  getArticleTitle () {
    let titleNode = this.getArticle().get('title')
    return new TextModel(this, titleNode.getPath())
  }

  getArticleAbstract () {
    let abstract = this.getArticle().get('abstract')
    return new FlowContentModel(this, abstract.getContentPath())
  }

  getArticleBody () {
    let body = this.getArticle().get('body')
    return new FlowContentModel(this, body.getContentPath())
  }

  getFigures () {
    let figs = this.getArticle().get('body').findAll('fig')
    return figs.map(fig => this.getModel(fig.type, fig))
  }

  getTranslatables () {
    const translatableItems = ['title', 'abstract']
    const article = this.getArticle()
    const models = translatableItems.map(item => new TranslateableModel(this, article.get(item)))
    return models
  }

  addTranslation (model, languageCode) {
    const isText = model._node.isText()
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let item = {
        language: languageCode
      }
      if (isText) {
        item.type = 'text-translation'
      } else {
        item.type = 'container-translation'
      }
      let node = tx.create(item)
      // HACK: trying to avoid selection errors of empty container
      if (!isText) node.append(tx.create({type: 'p'}))
      let length = tx.get([model.id, 'translations']).length
      tx.update([model.id, 'translations'], { type: 'insert', pos: length, value: node.id })
      tx.selection = null
    })
  }

  deleteTranslation (translatableModel, translationModel) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      let translatable = tx.get(translatableModel.id)
      let pos = translatable.translations.indexOf(translationModel.id)
      if (pos !== -1) {
        tx.update([translatableModel.id, 'translations'], { type: 'delete', pos: pos })
      }
      tx.delete(translationModel.id)
      tx.selection = null
    })
  }

  addXrefTarget (targetId, model) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xref = tx.get(model.id)
      let targets = xref.getAttribute('rid').split(' ')
      targets.push(targetId)
      xref.setAttribute('rid', targets.join(' '))
    })
  }

  removeXrefTarget (targetId, model) {
    const articleSession = this.articleSession
    articleSession.transaction(tx => {
      const xref = tx.get(model.id)
      let targets = xref.getAttribute('rid').split(' ')
      let idx = targets.indexOf(targetId)
      targets.splice(idx, 1)
      xref.setAttribute('rid', targets.join(' '))
    })
  }

  _getContext () {
    return this._context
  }

  /* Low-level content editing API */

  copy () {
    if (this._tableApi.isTableSelected()) {
      return this._tableApi.copySelection()
    } else {
      return super.copy()
    }
  }

  paste (content, options) {
    if (this._tableApi.isTableSelected()) {
      return this._tableApi.paste(content, options)
    } else {
      return super.paste(content, options)
    }
  }

  insertText (text) {
    if (this._tableApi.isTableSelected()) {
      this._tableApi.insertText(text)
    } else {
      return super.insertText(text)
    }
  }

  _createTextNode (tx, container, text) {
    // TODO: for Container nodes we should define the default text type
    // maybe even via a schema attribute
    return tx.create({ type: 'p', content: text })
  }

  _createListNode (tx, container, params) {
    let el = tx.create({ type: 'list' })
    if (params.listType) {
      el.attr('list-type', params.listType)
    }
    return el
  }

  getTableAPI () {
    return this._tableApi
  }

  /*
    TODO: In the future it should be necessary to expose those managers, instead
    API's should be used to access information.
  */
  getFigureManager () {
    return this.getArticleSession().getFigureManager()
  }

  getFootnoteManager () {
    return this.getArticleSession().getFootnoteManager()
  }

  getReferenceManager () {
    return this.getArticleSession().getReferenceManager()
  }

  getTableManager () {
    return this.getArticleSession().getTableManager()
  }

  get doc () {
    console.error('DEPRECATED: use api.getArticle() instead.')
    return this.getArticle()
  }

  _getDocument () {
    return this.getArticleSession().getDocument()
  }

  _getDocumentSession () {
    return this.getArticleSession()
  }

  _getEditorSession () {
    return this.articleSession
  }

  _isPropertyRequired (type, propertyName) {
    let REQUIRED = REQUIRED_PROPERTIES[type]
    if (REQUIRED) return REQUIRED.has(propertyName)
    return false
  }

  _selectFirstRequiredProperty (node) {
    let requiredProps = REQUIRED_PROPERTIES[node.type]
    if (requiredProps) {
      let propName = Array.from(requiredProps)[0]
      let path = [node.id, propName]
      return {
        type: 'property',
        path,
        startOffset: 0,
        surfaceId: `${path.join('.')}`
      }
    }
  }

  // TODO: can we improve this?
  // Here we would need a transaction on archive level, creating assets, plus placing them inside the article body.
  _insertFigures (files) {
    const articleSession = this.articleSession
    let paths = files.map(file => {
      return this.archive.createFile(file)
    })
    let sel = articleSession.getSelection()
    if (!sel || !sel.containerId) return
    articleSession.transaction(tx => {
      importFigure(tx, sel, files, paths)
    })
  }

  _createTableFigure (tx, params) {
    return insertTableFigure(tx, params.rows, params.columns)
  }

  _insertFootnote (item, footnotes) {
    const collectionId = footnotes.id
    this.articleSession.transaction(tx => {
      const node = createEmptyElement(tx, 'footnote')
      tx.get(collectionId).appendChild(
        node
      )
      setContainerSelection(tx, node)
    })
  }

  _insertPerson (person, collection) {
    const collectionId = collection.id
    this.articleSession.transaction(tx => {
      let bio = tx.create({type: 'bio'}).append(
        tx.create({type: 'p'})
      )
      person.bio = bio.id
      let node = tx.create(person)
      tx.get(collectionId).appendChild(node)
      let newSelection = this._selectFirstRequiredProperty(node)
      tx.setSelection(newSelection)
    })

    // TODO: for snippet importing we need to register a contrib converter
    // and take care of groups

    // const collectionId = collection.id
    // this.articleSession.transaction(tx => {
    //   const node = createEmptyElement(tx, 'person')
    //   tx.get(collectionId).appendChild(
    //     node
    //   )
    //   let newSelection = this._selectFirstRequiredProperty(node)
    //   tx.setSelection(newSelection)
    // })
  }

  _getSelection () {
    return this.articleSession.editorState.selection
  }

  _setSelection (selData) {
    this.articleSession.setSelection(selData)
  }

  _createInternalEditorAPI () {
    return new InternalArticleEditingAPI()
  }
}

class InternalArticleEditingAPI extends InternalEditingAPI {
  createTextNode (tx, container, text) {
    // TODO: for Container nodes we should define the default text type
    // maybe even via a schema attribute
    return tx.create({ type: 'p', content: text })
  }

  createListNode (tx, container, params) {
    let el = tx.create({ type: 'list' })
    if (params.listType) {
      el.attr('list-type', params.listType)
    }
    return el
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * @param {Object} obj - The object.
 * @param {Array<Array<commandArray>>} q - An array of command arrays.
 */
function applyq(obj, q, methods) {
  if (!(q instanceof Array)) {
    throw new Error('q has to be an instance of Array');
  }

  /**
   * @function Command executer.
   * @param {Array} command - The first element in a command array is the name
   *   of the object method you want to call. It must be a string. The rest of
   *   the elements are the arguments you want to pass to the object method.
   *   These can be any JavaScript value.
   */
  q.push = obj.push = function(command) {
    if (arguments.length !== 1 ||
      !(arguments[0] instanceof Array) ||
      typeof obj[command[0]] !== 'function' ||
      methods instanceof Array && methods.indexOf(command[0]) === -1) {
      return Array.prototype.push.apply(q, arguments);
    }

    obj[command[0]].apply(obj, command.splice(1));
  };

  var len = q.length;
  while (len--) {
    q.push(q.shift());
  }
}

module.exports = applyq;
<commit_msg>splice substituted with slice.<commit_after>'use strict';

/**
 * @param {Object} obj - The object.
 * @param {Array<Array<commandArray>>} q - An array of command arrays.
 */
function applyq(obj, q, methods) {
  if (!(q instanceof Array)) {
    throw new Error('q has to be an instance of Array');
  }

  /**
   * @function Command executer.
   * @param {Array} command - The first element in a command array is the name
   *   of the object method you want to call. It must be a string. The rest of
   *   the elements are the arguments you want to pass to the object method.
   *   These can be any JavaScript value.
   */
  q.push = obj.push = function(command) {
    if (arguments.length !== 1 ||
      !(arguments[0] instanceof Array) ||
      typeof obj[command[0]] !== 'function' ||
      methods instanceof Array && methods.indexOf(command[0]) === -1) {
      return Array.prototype.push.apply(q, arguments);
    }

    obj[command[0]].apply(obj, command.slice(1));
  };

  var len = q.length;
  while (len--) {
    q.push(q.shift());
  }
}

module.exports = applyq;
<|endoftext|>


<commit_before>const match_section = /^\[([^\]]*)\]/;
const match_line = /^([^\[=]*)(?:\[([-\w@]*)\])?=(.*)$/;
const match_comment = /^[;#]/;
const cleanup = function(val){ //remove leading spaces, trailing spaces and ";"
  return val.replace(/([\s;]+$)|(^\s*)/g,"");
}
function parse(content,locale){
  if(typeof content !== "string"){
    throw new Error(`data must be a string. got ${typeof content}`)
  }
  var lines = content.split(/\r?\n/);
  var obj = {};
  var currentSection;
  var tmp;
  lines.forEach(function(line, lineNumber){
    if(match_comment.test(line) || line.length == 0){
      return ; //Comment or empty line
    }else if(match_section.test(line)){
      //Open new section
      currentSection = cleanup(match_section.exec(line)[1]);
      obj[currentSection] = {};
    }else{
      //Add a line to current section
      tmp = match_line.exec(line);
      if(tmp && tmp[1] && tmp[3]){
        tmp[1] = cleanup(tmp[1]);
        tmp[3] = cleanup(tmp[3]);
        if(typeof obj[currentSection][tmp[1]] == "undefined" || !tmp[2] && !locale){
          //re-definition of a line is sort-of permitted
          obj[currentSection][tmp[1]] = tmp[3]
        } else if(tmp[2] && locale && cleanup(tmp[2]).toUpperCase() == locale.toUpperCase()){
          obj[currentSection][tmp[1]] = tmp[3]
        } else {
          // line's locale is set, but no locale was requested
          // we don't care about this value
        }
      }else{
        throw new Error(`Malformed data (line ${lineNumber}) : ${line} is not a valid line`);
      }
    }
  });
  return obj;
}

function serialize(obj){
  const top_keys = Object.keys(obj);
  const lines = [];
  for(let top_key of top_keys){
    const items = obj[top_key]
    if(!items) continue;
    const int_keys = Object.keys(items);
    lines.push(`[${top_key}]`, ... int_keys.map((key)=> `${key}=${items[key]}`))
  }
  return lines.join("\n");
}

module.exports = {parse, serialize};
<commit_msg>Adds support to empty values<commit_after>const match_section = /^\[([^\]]*)\]/;
const match_line = /^([^\[=]*)(?:\[([-\w@]*)\])?=(.*)$/;
const match_comment = /^[;#]/;
const cleanup = function(val){ //remove leading spaces, trailing spaces and ";"
  return val.replace(/([\s;]+$)|(^\s*)/g,"");
}
function parse(content,locale){
  if(typeof content !== "string"){
    throw new Error(`data must be a string. got ${typeof content}`)
  }
  var lines = content.split(/\r?\n/);
  var obj = {};
  var currentSection;
  var tmp;
  lines.forEach(function(line, lineNumber){
    if(match_comment.test(line) || line.length == 0){
      return ; //Comment or empty line
    }else if(match_section.test(line)){
      //Open new section
      currentSection = cleanup(match_section.exec(line)[1]);
      obj[currentSection] = {};
    }else{
      //Add a line to current section
      tmp = match_line.exec(line);
      if(tmp && tmp[1]){
        tmp[1] = cleanup(tmp[1]);
        tmp[3] = cleanup(tmp[3] || "");
        if(typeof obj[currentSection][tmp[1]] == "undefined" || !tmp[2] && !locale){
          //re-definition of a line is sort-of permitted
          obj[currentSection][tmp[1]] = tmp[3]
        } else if(tmp[2] && locale && cleanup(tmp[2]).toUpperCase() == locale.toUpperCase()){
          obj[currentSection][tmp[1]] = tmp[3]
        } else {
          // line's locale is set, but no locale was requested
          // we don't care about this value
        }
      }else{
        throw new Error(`Malformed data (line ${lineNumber}) : ${line} is not a valid line`);
      }
    }
  });
  return obj;
}

function serialize(obj){
  const top_keys = Object.keys(obj);
  const lines = [];
  for(let top_key of top_keys){
    const items = obj[top_key]
    if(!items) continue;
    const int_keys = Object.keys(items);
    lines.push(`[${top_key}]`, ... int_keys.map((key)=> `${key}=${items[key]}`))
  }
  return lines.join("\n");
}

module.exports = {parse, serialize};
<|endoftext|>


<commit_before>import { jest } from '@jest/globals';
import request from 'supertest';
import app from '../app.js';
import { getLogin } from './user.js';

describe('user/approve', () => {
  it('returns appropriate error message on invalid guid', async () => {
    return request(app)
      .get('/user/approval/bad-guid/agency')
      .expect(401)
      .expect(/invalid/);
  });
});

describe('login', () => {
  it('calls update user if there is an existing user in the db', async () => {
    const userId = 1;
    const body = {
      auth_id: 123,
      auth_provider: 'facebook',
      email: 'test@test.com',
      first_name: 'Test',
      last_name: 'User',
    };
    const getUser = jest.fn().mockReturnValue(new Promise((resolve) => resolve({ id: userId })));
    const updateUser = jest.fn().mockReturnValue(new Promise((resolve) => resolve()));

    const requestMock = {
      body,
    };
    const getResponseMock = () => {
      const response = {};
      response.status = jest.fn().mockReturnValue(response);
      response.json = jest.fn();

      return response;
    };
    const responseMock = getResponseMock();
    await getLogin(getUser, updateUser)(requestMock, responseMock);

    expect(getUser).toHaveBeenCalled();
    expect(updateUser).toHaveBeenCalledWith(body);
    expect(responseMock.status).toHaveBeenCalledWith(200);
  });
});
<commit_msg>fix(back): try different imports<commit_after>import { jest } from '@jest/globals';
import request from 'supertest';
import app from '../app';
import { getLogin } from './user';

describe('user/approve', () => {
  it('returns appropriate error message on invalid guid', async () => {
    return request(app)
      .get('/user/approval/bad-guid/agency')
      .expect(401)
      .expect(/invalid/);
  });
});

describe('login', () => {
  it('calls update user if there is an existing user in the db', async () => {
    const userId = 1;
    const body = {
      auth_id: 123,
      auth_provider: 'facebook',
      email: 'test@test.com',
      first_name: 'Test',
      last_name: 'User',
    };
    const getUser = jest.fn().mockReturnValue(new Promise((resolve) => resolve({ id: userId })));
    const updateUser = jest.fn().mockReturnValue(new Promise((resolve) => resolve()));

    const requestMock = {
      body,
    };
    const getResponseMock = () => {
      const response = {};
      response.status = jest.fn().mockReturnValue(response);
      response.json = jest.fn();

      return response;
    };
    const responseMock = getResponseMock();
    await getLogin(getUser, updateUser)(requestMock, responseMock);

    expect(getUser).toHaveBeenCalled();
    expect(updateUser).toHaveBeenCalledWith(body);
    expect(responseMock.status).toHaveBeenCalledWith(200);
  });
});
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';
var fs = require('fs');
var path = require('path');
var MergeTrees = require('broccoli-merge-trees');
var Vulcanize = require('broccoli-vulcanize');

const defaultVulcanizeOptions = {
  inlineCss: true
};

module.exports = {
  name: 'ember-polymer',

  included: function(app) {
    this._super.included.apply(this, arguments);

    this.addonOptions = this.app.options['ember-polymer'] || {};
    this.htmlImportsFile = this.addonOptions.htmlImportsFile ||
      path.join('app', 'elements.html');
    this.vulcanizeOutput = this.addonOptions.vulcanizeOutput ||
      path.join('assets', 'vulcanized.html');
    this.vulcanizeOptions = Object.assign(defaultVulcanizeOptions,
      this.addonOptions.vulcanizeOptions);
    this.projectTree = app.trees.app;

    app.import(app.bowerDirectory + '/webcomponentsjs/webcomponents.min.js');
  },

  // insert polymer and vulcanized elements
  contentFor: function(type) {
    if (type === 'head') {
      return `<script>
                window.Polymer = window.Polymer || {};
                window.Polymer.dom = "shadow";
              </script>
              <link rel="import" href="${this.vulcanizeOutput}">`;
    }
  },

  postprocessTree: function(type, tree) {
    if (type === 'all') {
      if (path.extname(this.vulcanizeOutput) !== '.html') {
        throw new Error('[ember-polymer] The `vulcanizeOutput` file ' +
          `is not a .html file. You specified '${this.vulcanizeOutput}'`);
      }

      var filePath = path.join(this.app.project.root,
                               this.htmlImportsFile);
      if (fs.existsSync(filePath)) {
        // vulcanize html files, starting at specified html imports file
        let options = Object.assign(this.vulcanizeOptions, {
          input: path.basename(this.htmlImportsFile),
          output: this.vulcanizeOutput
        });

        var vulcanized = new Vulcanize(this.projectTree, options);

        return new MergeTrees([ tree, vulcanized ], {
          overwrite: true,
          annotation: 'Merge (ember-polymer merge vulcanized with addon tree)'
        });
      } else {
        this.ui.writeWarnLine('[ember-polymer] No `htmlImports` file ' +
          `exists at '${this.htmlImportsFile}'`);
      }
    }

    return tree;
  }
};
<commit_msg>Use let instead of var<commit_after>/* jshint node: true */
'use strict';
let fs = require('fs');
let path = require('path');
let MergeTrees = require('broccoli-merge-trees');
let Vulcanize = require('broccoli-vulcanize');

const defaultVulcanizeOptions = {
  inlineCss: true
};

module.exports = {
  name: 'ember-polymer',

  included: function(app) {
    this._super.included.apply(this, arguments);

    this.addonOptions = this.app.options['ember-polymer'] || {};
    this.htmlImportsFile = this.addonOptions.htmlImportsFile ||
      path.join('app', 'elements.html');
    this.vulcanizeOutput = this.addonOptions.vulcanizeOutput ||
      path.join('assets', 'vulcanized.html');
    this.vulcanizeOptions = Object.assign(defaultVulcanizeOptions,
      this.addonOptions.vulcanizeOptions);
    this.projectTree = app.trees.app;

    app.import(app.bowerDirectory + '/webcomponentsjs/webcomponents.min.js');
  },

  // insert polymer and vulcanized elements
  contentFor: function(type) {
    if (type === 'head') {
      return `<script>
                window.Polymer = window.Polymer || {};
                window.Polymer.dom = "shadow";
              </script>
              <link rel="import" href="${this.vulcanizeOutput}">`;
    }
  },

  postprocessTree: function(type, tree) {
    if (type === 'all') {
      if (path.extname(this.vulcanizeOutput) !== '.html') {
        throw new Error('[ember-polymer] The `vulcanizeOutput` file ' +
          `is not a .html file. You specified '${this.vulcanizeOutput}'`);
      }

      let filePath = path.join(this.app.project.root,
                               this.htmlImportsFile);
      if (fs.existsSync(filePath)) {
        // vulcanize html files, starting at specified html imports file
        let options = Object.assign(this.vulcanizeOptions, {
          input: path.basename(this.htmlImportsFile),
          output: this.vulcanizeOutput
        });

        let vulcanized = new Vulcanize(this.projectTree, options);

        return new MergeTrees([ tree, vulcanized ], {
          overwrite: true,
          annotation: 'Merge (ember-polymer merge vulcanized with addon tree)'
        });
      } else {
        this.ui.writeWarnLine('[ember-polymer] No `htmlImports` file ' +
          `exists at '${this.htmlImportsFile}'`);
      }
    }

    return tree;
  }
};
<|endoftext|>