<commit_before>// NML VM
// Runs NML AST, stored in a MObject
// A VM would exist for each script/verb, and would be reset/replaced with a
// new VM when those scripts/verbs are updated/edited.

var async = require('async');

var OPERATORS = ['+', '-', '/', '*'];
var TYPE_COMPATIBILITY = {
  '+': {
    'number': ['string', 'number'],
    'string': ['string'],
    'array': ['string'],
    'null': ['string']
  },
  '-': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  },
  '*': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  },
  '/': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  }
}

var VM = function(app, mobj, ast) {
  this._app = app;
  this.mobj = mobj; // MOO Object

  // Initialize the state
  this.state = {
    ast: ast,
    localVars: {},
    ip: [0] // Instruction pointer/path; this is an array as the AST is a tree with many levels
            // Example: [2, 4, 10] for the second object, then fourth, then tenth object
  }
}

VM.findIns = function(ip, ast) {
  // Find the instruction specified by instruction path IP
  if(ip.length <= 1) {
    return ast[ip[0]];
  } else {
    if(typeof(ast[ip[0]]) === 'object') {
      return VM.findIns(ip.slice(1, ip.length), ast[ip[0]].block);
    } else {
      return ast; // Path not valid, just return the ast at it's current state
    }
  }
}

var err = function(str) {
  throw 'runtime error: ' + str;
}

// Resolve possible indirect values, like variables, properties, etc
// Async because it _might_ hit the DB
VM.prototype.resolveValue = function(value, callback) {
  if(typeof(value) !== 'object') return callback(null, value); // This is a direct value, just return it.
  switch(value.type) {
    case 'var': // Local variable, return it from our state.
      return callback(null, this.state.localVars[value.name]);
    case 'prop': // This is a property, fetch it from the object
      return this.mobj.getProp(value.name, callback);
    case 'objectIdAlias': // This is an object id alias, ask the app for what it means
      return callback(null, this._app.resolveObjIdAlias(value.value));
    default:
      return callback(null, null);
  }
}

// Evaluate simple expresisons, like 4 * 5
// Async because resolveValue _might_ hit the DB
VM.prototype.evalExpr = function(expr, callback) {
  // First, if the second component is not an operator and the whole expression
  // has no JS objects/hashes, then just return the expression joined by space
  // UNLESS, the first component is a number
  if(OPERATORS.indexOf(expr[1]) < 0 && typeof(expr[0]) !== 'number') {
    var noObjects = true;
    for(var obj of expr) { if(typeof(obj) === 'object') { noObjects = false; break; }}
    if(noObjects) return callback(null, expr.join(' '));
  }

  // Loop over every component in the expression
  // Every second component is an operator (+ - / *)
  var output = null;
  var parseNextComponent = function(i) {
    if(i >= expr.length) {
      callback(null, output);
    } else if(i > 0) {
      var op = expr[i - 1];
      // Resolve the value so the correct value is used for the type checking
      this.resolveValue(expr[i], function(err, value) {
        if(err) return callback(err);
        if(OPERATORS.indexOf(op) < 0) return callback(op + ' is not a valid operator (' + OPERATORS.join(' ') + ')');
        if(TYPE_COMPATIBILITY[op][typeof(value)].indexOf(typeof(output)) < 0) return callback('type ' + typeof(value) + ' cannot be opped ' + op + ' with ' + typeof(output));
        switch(op) {
          case '+':
            output += value;
            return parseNextComponent(i + 2);
          case '-':
            output -= value;
            return parseNextComponent(i + 2);
          case '/':
            output /= value;
            return parseNextComponent(i + 2);
          case '*':
            output *= value;
            return parseNextComponent(i + 2);
        }
      }.bind(this));
    } else {
      this.resolveValue(expr[i], function(err, value) {
        output = value;
        parseNextComponent(i + 2);
      });
    }
  }.bind(this);

  parseNextComponent(0);
}

VM.prototype.stepOnce = function(callback) {
  // First find the Instruction
  var ins = VM.findIns(this.state.ip, this.state.ast); // _that was easy_

  // Finishes job by incrementing the IP accordingly
  var jobDone = function(err, results) {
    if(err) return callback(err);
    // TODO increment IP
    // If this is a branching instruction and we need to branch (results[0]),
    // branch instead of incrementing the IP
    if((ins.type === 'if' || ins.type === 'while') && results[0] && ins.block.length > 0) {
      this.state.ip.push(0); // This simply bumps us into the next instruction to the right on the AST
    } else {
      // Otherwise, we need to decide what to do

      // While we are at the end of the current block (if we are)
      while(this.state.ip.length > 1 &&
            this.state.ip.slice(-1)[0] ===
            VM.findIns(this.state.ip.slice(0, -1)).block.length - 1) {
        if(VM.findIns(this.state.ip.slice(0, -1)).type === 'while') {
          // TODO Consider looping around
        } else { // Probably an if statement, just pop off the stack
          this.state.ip.pop(); // Pop off the current state
        }
      }

      // Are we at the end of the script
      if(this.state.ip.length === 1 && this.state.ip[0] === this.state.ast.length - 1)
        return callback('endOfScript'); // Error with endOfScript

      // Carry on, increment by one
      this.state.ip[this.state.ip.length - 1]++;
    }
    callback();
  }.bind(this);

  switch(ins.type) {
    case 'while':
      break;
    case 'if':
      break;
    case 'assign':
      async.waterfall([
      function(c) { this.evalExpr(ins.src, c); }.bind(this),
      function(value, c) { // We have evaluated the right side of the assignment, set the value.
        if(ins.op === '=') {
          // Simple assignment
          if(ins.dst.type === 'var') {
            // Local variable, just set it here
            this.state.localVars[ins.dst.name] = value;
            c();
          } else if(ins.dst.type === 'prop') {
            // Object prop, set it and pass it the stepOnce callback.
            this.mobj.setProp(ins.dst.name, value, c);
          } else {
            c('unspecifiable error (cac96aa5-fade-4263-a56c-e4cd883447cc)');
          }
        } else {
          // Complex assignment, runs an expression.
          // We need to resolve the dst so we can type check
          this.resolveValue(ins.dst, function(err, currentValue) {
            if(TYPE_COMPATIBILITY[ins.op][typeof(value)].indexOf(typeof(currentValue)) < 0) return c('type ' + typeof(value) + ' cannot be opped ' + ins.op + ' with ' + typeof(currentValue));
            // It's all valid, do the assignment
            switch(ins.op) {
              case '+':
                currentValue += value;
                break;
              case '-':
                currentValue -= value;
                break;
              case '/':
                currentValue /= value;
                break;
              case '*':
                currentValue *= value;
                break;
              default:
                return c('unspecifiable error (16d7eef1-7b5c-419d-b747-b7392de419d2)');
            }
            // Set currentValue back to where it came from
            if(ins.dst.type === 'prop') {
              this.mobj.setProp(ins.dst.name, currentValue, c);
            } else if(ins.dst.type === 'var') {
              this.state.localVars[ins.dst.name] = currentValue;
              return c();
            } else {
              return c('unspecifiable error (13041b5b-7392-400d-9623-44347db5f81e)');
            }
          }.bind(this));
        }
      }.bind(this)], jobDone);
      break;
    case 'verbcall':
      break;
  }
}

VM.prototype.step = function(times, callback) {
  async.times(times, function(i, next) {
    this.stepOnce(next);
  }.bind(this), callback);
}

module.exports = VM;
<commit_msg>remove duplication by moving expr ops into functions and calling them from the table by key<commit_after>// NML VM
// Runs NML AST, stored in a MObject
// A VM would exist for each script/verb, and would be reset/replaced with a
// new VM when those scripts/verbs are updated/edited.

var async = require('async');

var OPERATORS = {'+': function(left, right) { return left + right; },
                 '-': function(left, right) { return left - right; },
                 '/': function(left, right) { return left / right; },
                 '*': function(left, right) { return left * right; }};
var TYPE_COMPATIBILITY = {
  '+': {
    'number': ['string', 'number'],
    'string': ['string'],
    'array': ['string'],
    'null': ['string']
  },
  '-': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  },
  '*': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  },
  '/': {
    'number': ['number'],
    'string': [],
    'array': [],
    'null': []
  }
}

var VM = function(app, mobj, ast) {
  this._app = app;
  this.mobj = mobj; // MOO Object

  // Initialize the state
  this.state = {
    ast: ast,
    localVars: {},
    ip: [0] // Instruction pointer/path; this is an array as the AST is a tree with many levels
            // Example: [2, 4, 10] for the second object, then fourth, then tenth object
  }
}

VM.findIns = function(ip, ast) {
  // Find the instruction specified by instruction path IP
  if(ip.length <= 1) {
    return ast[ip[0]];
  } else {
    if(typeof(ast[ip[0]]) === 'object') {
      return VM.findIns(ip.slice(1, ip.length), ast[ip[0]].block);
    } else {
      return ast; // Path not valid, just return the ast at it's current state
    }
  }
}

var err = function(str) {
  throw 'runtime error: ' + str;
}

// Resolve possible indirect values, like variables, properties, etc
// Async because it _might_ hit the DB
VM.prototype.resolveValue = function(value, callback) {
  if(typeof(value) !== 'object') return callback(null, value); // This is a direct value, just return it.
  switch(value.type) {
    case 'var': // Local variable, return it from our state.
      return callback(null, this.state.localVars[value.name]);
    case 'prop': // This is a property, fetch it from the object
      return this.mobj.getProp(value.name, callback);
    case 'objectIdAlias': // This is an object id alias, ask the app for what it means
      return callback(null, this._app.resolveObjIdAlias(value.value));
    default:
      return callback(null, null);
  }
}

// Evaluate simple expresisons, like 4 * 5
// Async because resolveValue _might_ hit the DB
VM.prototype.evalExpr = function(expr, callback) {
  // First, if the second component is not an operator and the whole expression
  // has no JS objects/hashes, then just return the expression joined by space
  // UNLESS, the first component is a number
  if(OPERATORS[expr[1]] === undefined && typeof(expr[0]) !== 'number') {
    var noObjects = true;
    for(var obj of expr) { if(typeof(obj) === 'object') { noObjects = false; break; }}
    if(noObjects) return callback(null, expr.join(' '));
  }

  // Loop over every component in the expression
  // Every second component is an operator (+ - / *)
  var output = null;
  var parseNextComponent = function(i) {
    if(i >= expr.length) {
      callback(null, output);
    } else if(i > 0) {
      var op = expr[i - 1];
      // Resolve the value so the correct value is used for the type checking
      this.resolveValue(expr[i], function(err, value) {
        if(err) return callback(err);
        if(OPERATORS[op] === undefined) return callback(op + ' is not a valid operator (' + Object.keys(OPERATORS).join(' ') + ')');
        if(TYPE_COMPATIBILITY[op][typeof(value)].indexOf(typeof(output)) < 0) return callback('type ' + typeof(value) + ' cannot be opped ' + op + ' with ' + typeof(output));
        output = OPERATORS[op](output, value);
        return parseNextComponent(i + 2);
      }.bind(this));
    } else {
      this.resolveValue(expr[i], function(err, value) {
        output = value;
        parseNextComponent(i + 2);
      });
    }
  }.bind(this);

  parseNextComponent(0);
}

VM.prototype.stepOnce = function(callback) {
  // First find the Instruction
  var ins = VM.findIns(this.state.ip, this.state.ast); // _that was easy_

  // Finishes job by incrementing the IP accordingly
  var jobDone = function(err, results) {
    if(err) return callback(err);
    // TODO increment IP
    // If this is a branching instruction and we need to branch (results[0]),
    // branch instead of incrementing the IP
    if((ins.type === 'if' || ins.type === 'while') && results[0] && ins.block.length > 0) {
      this.state.ip.push(0); // This simply bumps us into the next instruction to the right on the AST
    } else {
      // Otherwise, we need to decide what to do

      // While we are at the end of the current block (if we are)
      while(this.state.ip.length > 1 &&
            this.state.ip.slice(-1)[0] ===
            VM.findIns(this.state.ip.slice(0, -1)).block.length - 1) {
        if(VM.findIns(this.state.ip.slice(0, -1)).type === 'while') {
          // TODO Consider looping around
        } else { // Probably an if statement, just pop off the stack
          this.state.ip.pop(); // Pop off the current state
        }
      }

      // Are we at the end of the script
      if(this.state.ip.length === 1 && this.state.ip[0] === this.state.ast.length - 1)
        return callback('endOfScript'); // Error with endOfScript

      // Carry on, increment by one
      this.state.ip[this.state.ip.length - 1]++;
    }
    callback();
  }.bind(this);

  switch(ins.type) {
    case 'while':
      break;
    case 'if':
      break;
    case 'assign':
      async.waterfall([
      function(c) { this.evalExpr(ins.src, c); }.bind(this),
      function(value, c) { // We have evaluated the right side of the assignment, set the value.
        if(ins.op === '=') {
          // Simple assignment
          if(ins.dst.type === 'var') {
            // Local variable, just set it here
            this.state.localVars[ins.dst.name] = value;
            c();
          } else if(ins.dst.type === 'prop') {
            // Object prop, set it and pass it the stepOnce callback.
            this.mobj.setProp(ins.dst.name, value, c);
          } else {
            c('unspecifiable error (cac96aa5-fade-4263-a56c-e4cd883447cc)');
          }
        } else {
          // Complex assignment, runs an expression.
          // We need to resolve the dst so we can type check
          this.resolveValue(ins.dst, function(err, currentValue) {
            if(TYPE_COMPATIBILITY[ins.op][typeof(value)].indexOf(typeof(currentValue)) < 0) return c('type ' + typeof(value) + ' cannot be opped ' + ins.op + ' with ' + typeof(currentValue));
            // It's all valid, do the assignment
            currentValue = OPERATORS[ins.op](currentValue, value);
            // Set currentValue back to where it came from
            if(ins.dst.type === 'prop') {
              this.mobj.setProp(ins.dst.name, currentValue, c);
            } else if(ins.dst.type === 'var') {
              this.state.localVars[ins.dst.name] = currentValue;
              return c();
            } else {
              return c('unspecifiable error (13041b5b-7392-400d-9623-44347db5f81e)');
            }
          }.bind(this));
        }
      }.bind(this)], jobDone);
      break;
    case 'verbcall':
      break;
  }
}

VM.prototype.step = function(times, callback) {
  async.times(times, function(i, next) {
    this.stepOnce(next);
  }.bind(this), callback);
}

module.exports = VM;
<|endoftext|>


<commit_before>var http = require('http');
var https = require('https');
var net = require('net');
var connect = require('connect');
var fs = require('fs');
var path = require('path');
var log = require('./log');
var utils = require('./utils');

var DEFAULT_PORT = 8989;
var INTERNAL_HTTPS_PORT = 0;
var app;
var httpServer;
var httpsServer;
var privateKeyFile = path.join(__dirname, '..', 'keys', 'privatekey.pem');
var certificateFile = path.join(__dirname, '..', 'keys', 'certificate.pem');

/**
 * Start up nproxy server on the specified port
 * and combine the processors defined as connect middlewares into it.
 *
 * @param {String} port the port proxy server will listen on
 * @param {Object} options options for the middlewares
 */
function nproxy(port, options){
  var nm;

  if(typeof options.timeout === 'number'){
    utils.reqTimeout = options.timeout;
    utils.resTimeout = options.timeout;
  }

  if(typeof options.debug === 'boolean'){
    log.isDebug = options.debug;
  }

  nm = require('./middlewares'); //nproxy middles

  port = typeof port === 'number' ? port : DEFAULT_PORT;

  app = connect();
  if(typeof options.responderListFilePath !== 'undefined'){
    app.use(nm.respond(options.responderListFilePath));
  }
  app.use(nm.forward());

  httpServer = http.createServer(function(req, res){
    req.type = 'http';
    app(req, res);
  }).listen(port);
  httpsServer = https.createServer({
    key: fs.readFileSync(privateKeyFile),
    cert: fs.readFileSync(certificateFile)
  }, function(req, res){
    req.type = 'https';
    app(req, res);
  });
  httpsServer.on('listening', function(){
    INTERNAL_HTTPS_PORT = httpsServer.address().port;
  });
  httpsServer = httpsServer.listen(INTERNAL_HTTPS_PORT);

  proxyHttps();

  log.info('NProxy started on ' + port + '!');

  if (options.networks) {
    log.info('Network interfaces:');
    var interfaces = require('os').networkInterfaces();
    for (var key in interfaces) {
      log.info(key);
      interfaces[key].forEach(function (item) {
        log.info('  ' + item.address + '\t' + item.family);
      });
    }
  }

  return {
    httpServer: httpServer,
    httpsServer: httpsServer
  };
}

/**
 * Listen the CONNECTION method and forward the https request to internal https server
 */
function proxyHttps(){
  httpServer.on('connect', function(req, socket, upgradeHead){
    var netClient = net.createConnection(INTERNAL_HTTPS_PORT);

    netClient.on('connect', function(){
      log.info('connect to https server successfully!');
      socket.write( "HTTP/1.1 200 Connection established\r\nProxy-agent: Netscape-Proxy/1.1\r\n\r\n");
    });

    socket.on('data', function(chunk){
      netClient.write(chunk);
    });
    socket.on('end', function(){
      netClient.end();
    });
    socket.on('close', function(){
      netClient.end();
    });
    socket.on('error', function(err){
      log.error('socket error ' + err.message);
      netClient.end();
    });

    netClient.on('data', function(chunk){
      socket.write(chunk);
    });
    netClient.on('end', function(){
      socket.end();
    });
    netClient.on('close', function(){
      socket.end();
    });
    netClient.on('error', function(err){
      log.error('netClient error ' + err.message);
      socket.end();
    });

  });
};

process.on('uncaughtException', function(err){
  log.error('uncaughtException: ' + err.message);
});

module.exports = nproxy;
<commit_msg>使用express代替connect<commit_after>var http = require('http');
var https = require('https');
var net = require('net');
var express = require('express');
var fs = require('fs');
var path = require('path');
var log = require('./log');
var utils = require('./utils');

var DEFAULT_PORT = 8989;
var INTERNAL_HTTPS_PORT = 0;
var app;
var httpServer;
var httpsServer;
var privateKeyFile = path.join(__dirname, '..', 'keys', 'privatekey.pem');
var certificateFile = path.join(__dirname, '..', 'keys', 'certificate.pem');

/**
 * Start up nproxy server on the specified port
 * and combine the processors defined as express middlewares into it.
 *
 * @param {String} port the port proxy server will listen on
 * @param {Object} options options for the middlewares
 */
function nproxy(port, options){
  var nm;

  if(typeof options.timeout === 'number'){
    utils.reqTimeout = options.timeout;
    utils.resTimeout = options.timeout;
  }

  if(typeof options.debug === 'boolean'){
    log.isDebug = options.debug;
  }

  nm = require('./middlewares'); //nproxy middles

  port = typeof port === 'number' ? port : DEFAULT_PORT;

  app = connect();
  if(typeof options.responderListFilePath !== 'undefined'){
    app.use(nm.respond(options.responderListFilePath));
  }
  app.use(nm.forward());

  httpServer = http.createServer(function(req, res){
    req.type = 'http';
    app(req, res);
  }).listen(port);
  httpsServer = https.createServer({
    key: fs.readFileSync(privateKeyFile),
    cert: fs.readFileSync(certificateFile)
  }, function(req, res){
    req.type = 'https';
    app(req, res);
  });
  httpsServer.on('listening', function(){
    INTERNAL_HTTPS_PORT = httpsServer.address().port;
  });
  httpsServer = httpsServer.listen(INTERNAL_HTTPS_PORT);

  proxyHttps();

  log.info('NProxy started on ' + port + '!');

  if (options.networks) {
    log.info('Network interfaces:');
    var interfaces = require('os').networkInterfaces();
    for (var key in interfaces) {
      log.info(key);
      interfaces[key].forEach(function (item) {
        log.info('  ' + item.address + '\t' + item.family);
      });
    }
  }

  return {
    httpServer: httpServer,
    httpsServer: httpsServer
  };
}

/**
 * Listen the CONNECTION method and forward the https request to internal https server
 */
function proxyHttps(){
  httpServer.on('connect', function(req, socket, upgradeHead){
    var netClient = net.createConnection(INTERNAL_HTTPS_PORT);

    netClient.on('connect', function(){
      log.info('connect to https server successfully!');
      socket.write( "HTTP/1.1 200 Connection established\r\nProxy-agent: Netscape-Proxy/1.1\r\n\r\n");
    });

    socket.on('data', function(chunk){
      netClient.write(chunk);
    });
    socket.on('end', function(){
      netClient.end();
    });
    socket.on('close', function(){
      netClient.end();
    });
    socket.on('error', function(err){
      log.error('socket error ' + err.message);
      netClient.end();
    });

    netClient.on('data', function(chunk){
      socket.write(chunk);
    });
    netClient.on('end', function(){
      socket.end();
    });
    netClient.on('close', function(){
      socket.end();
    });
    netClient.on('error', function(err){
      log.error('netClient error ' + err.message);
      socket.end();
    });

  });
};

process.on('uncaughtException', function(err){
  log.error('uncaughtException: ' + err.message);
});

module.exports = nproxy;
<|endoftext|>


<commit_before>/** ++++++++++++++++++++
 *  Application Server
 *  ++++++++++++++++++++
 *
 *
 The MIT License (MIT)

 Copyright (c) 2014 Cah Angon

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

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

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

 */
var express = require('express'),
    server = express(),
    api = require('./routes/api'),
    routes = require('./routes'),
    config = require('./config');

server.get('/', routes.home);
server.get('/api', api.version);
server.post('/api/search/:kata', api.search);

server.listen(config.get('server:port'), function () {
    console.log('Server berjalan pada port: ' + config.get('server:port'));
});
<commit_msg>route untuk init search index engine<commit_after>/** ++++++++++++++++++++
 *  Application Server
 *  ++++++++++++++++++++
 *
 *
 The MIT License (MIT)

 Copyright (c) 2014 Cah Angon

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

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

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

 */
var express = require('express'),
    server = express(),
    api = require('./routes/api'),
    routes = require('./routes'),
    config = require('./config');

server.configure('development',function(){
    server.use(express.errorHandler());
});

// Routes
server.get('/', routes.home);

/**
 * Inisialisasi dari Search Index
 */
server.get('/initSearchEngine/:flag', routes.initSearchIndex);

/**++++++++++++
 *  API
 * ++++++++++++
 */

server.get('/api', api.version);

server.get('/api/search/:kata', api.search);

server.listen(config.get('server:port'), function () {
    console.log('Server berjalan pada port: ' + config.get('server:port'));
});
<|endoftext|>


<commit_before>'use strict';
// Author: tngan
// This parser is to do generic post-processing
const _ = require('lodash');
const flatten = require('flat');
const ent = require('ent');
const schema = require('./schema');
const count = require('word-count');

const mapping = {
  questions: {
    arrayJoin: {
      'tags': {
        delimiter: ';',
        isCount: true
      }
    },
    pick: Object.keys(schema.questions),
    flatten: ['owner'],
    decode: ['title'],
    schema: schema.questions,
    wordCount: ['title']
  },
  tags: {
    pick: Object.keys(schema.tags),
    schema: schema.tags
  }
};

function parseValue(value, flag) {
  // 1. HTML decode
  // 2. Quote embed
  if(flag) {
    value = ent.decode(value);
    return `"${value}"`;
  }
  return value;
}

module.exports = function(items, category) {
  let resItems = [];
  if(mapping.hasOwnProperty(category)) {
    // TODO Use harmony version to do destruction
    let cmap = mapping[category];
    let arrayJoin = cmap.arrayJoin;
    let pick = cmap.pick;
    let rename = cmap.rename;
    let schema = cmap.schema;
    let wordCount = cmap.wordCount || [];
    let decode = cmap.decode || [];

    _.forEach(items ,function(item) {
      let resItem = {};
      // Step 1: Flatten
      let flatJson = flatten(item, { delimiter: '_', safe: true });
      // Step 2: Filter
      flatJson = _.pick(flatJson, pick);
      // Deeper postprocessing
      _.forEach(flatJson, function(value, key) {
        // Step 3: ArrayJoin
        if(arrayJoin && arrayJoin.hasOwnProperty(key)) {
          resItem[key] = value.join(arrayJoin[key].delimiter);
          // Step 3.1 Add tag count
          if(arrayJoin[key].isCount) {
            resItem[`${key}_count`] = value.length;
          }
        } else if(rename && rename.hasOwnProperty(key)) {
          // Step 4: Rename
          key = rename[key];
          resItem[key] = parseValue(value, _.includes(decode, key));
        } else {
          // Step 5: For the rest, just assign
          resItem[key] = parseValue(value, _.includes(decode, key));
        }

        if(_.includes(wordCount, key)) {
          resItem[`${key}_word_count`] = count(value || '');
        }
      });
      // Step 6: Make sure all item has same schema, default value is an empty string
      resItem = Object.assign({}, schema, resItem);
      // Step 7: Push the parsed item object back to the return array
      resItems.push(resItem);
    });
  } else {
    console.log(`There is no mapping of ${category}`);
  }
  return resItems;
};
<commit_msg>Fix multiple double quotes problem<commit_after>'use strict';
// Author: tngan
// This parser is to do generic post-processing
const _ = require('lodash');
const flatten = require('flat');
const ent = require('ent');
const schema = require('./schema');
const count = require('word-count');

const mapping = {
  questions: {
    arrayJoin: {
      'tags': {
        delimiter: ';',
        isCount: true
      }
    },
    pick: Object.keys(schema.questions),
    flatten: ['owner'],
    decode: ['title'],
    schema: schema.questions,
    wordCount: ['title']
  },
  tags: {
    pick: Object.keys(schema.tags),
    schema: schema.tags
  }
};

function parseValue(value, flag) {
  // 1. HTML decode
  // 2. Quote embed
  if(flag) {
    value = ent.decode(value).replace(/"/g, '""');
    return `"${value}"`;
  }
  return value;
}

module.exports = function(items, category) {
  let resItems = [];
  if(mapping.hasOwnProperty(category)) {
    // TODO Use harmony version to do destruction
    let cmap = mapping[category];
    let arrayJoin = cmap.arrayJoin;
    let pick = cmap.pick;
    let rename = cmap.rename;
    let schema = cmap.schema;
    let wordCount = cmap.wordCount || [];
    let decode = cmap.decode || [];

    _.forEach(items ,function(item) {
      let resItem = {};
      // Step 1: Flatten
      let flatJson = flatten(item, { delimiter: '_', safe: true });
      // Step 2: Filter
      flatJson = _.pick(flatJson, pick);
      // Deeper postprocessing
      _.forEach(flatJson, function(value, key) {
        // Step 3: ArrayJoin
        if(arrayJoin && arrayJoin.hasOwnProperty(key)) {
          resItem[key] = value.join(arrayJoin[key].delimiter);
          // Step 3.1 Add tag count
          if(arrayJoin[key].isCount) {
            resItem[`${key}_count`] = value.length;
          }
        } else if(rename && rename.hasOwnProperty(key)) {
          // Step 4: Rename
          key = rename[key];
          resItem[key] = parseValue(value, _.includes(decode, key));
        } else {
          // Step 5: For the rest, just assign
          resItem[key] = parseValue(value, _.includes(decode, key));
        }

        if(_.includes(wordCount, key)) {
          resItem[`${key}_word_count`] = count(value || '');
        }
      });
      // Step 6: Make sure all item has same schema, default value is an empty string
      resItem = Object.assign({}, schema, resItem);
      // Step 7: Push the parsed item object back to the return array
      resItems.push(resItem);
    });
  } else {
    console.log(`There is no mapping of ${category}`);
  }
  return resItems;
};
<|endoftext|>


<commit_before>
/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , error:    5
  , upgrade:  6
};

var packetslist = Object.keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' }

/**
 * Encodes a packet.
 *
 *     <packet type id> [ `:` <data> ]
 *
 * Example:
 *
 *     5:hello world
 *     3
 *     4
 *
 * @api private
 */

exports.encodePacket = function (packet) {
  var encoded = packets[packet.type]

  // data fragment is optional
  if (undefined !== packet.data) {
    encoded += ':' + packet.data;
  }

  return '' + encoded;
};

/**
 * Decodes a packet.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data) {
  if (':' === data[1]) {
    var pieces = data.match(/([0-9]+):(.*)?/)

    if (!pieces) {
      // parser error - ignoring packet
      return err;
    }

    var type = pieces[1]
      , data = pieces[2]

    if (!packetslist[type]) {
      // parser error - ignoring packet
      return err;
    }

    return { type: packetslist[type], data: undefined === data ? '' : data };
  } else {
    if (Number(data) != data || !packetslist[data]) return err;
    return { type: packetslist[data] };
  }
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets) {
  if (!packets.length) {
    return '0:';
  }

  var encoded = ''
    , message

  for (var i = 0, l = packets.length; i < l; i++) {
    message = exports.encodePacket(packets[i]);
    encoded += message.length + ':' + message;
  }

  return encoded;
};

/*
 * Decodes data when a payload is maybe expected.
 *
 * @param {String} data
 * @return {Array} packets
 * @api public
 */

exports.decodePayload = function (data) {
  if (data == '') {
    // parser error - ignoring payload
    return [err];
  }

  var packets = []
    , length = ''
    , n, msg, packet

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data[i]

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return [err];
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return [err];
      }

      if (msg.length) {
        packet = exports.decodePacket(msg);

        if (err.type == packet.type && err.data == packet.data) {
          // parser error in individual packet - ignoring payload
          return [err];
        }

        packets.push(packet);
      }

      // advance cursor
      i += n;
      length = ''
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return [err];
  }

  return packets;
};
<commit_msg>parser simplified<commit_after>
/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , error:    5
  , upgrade:  6
};

var packetslist = Object.keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' }

/**
 * Encodes a packet.
 *
 *     <packet type id> [ `:` <data> ]
 *
 * Example:
 *
 *     5:hello world
 *     3
 *     4
 *
 * @api private
 */

exports.encodePacket = function (packet) {
  var encoded = packets[packet.type]

  // data fragment is optional
  if (undefined !== packet.data) {
    encoded += ':' + packet.data;
  }

  return '' + encoded;
};

/**
 * Decodes a packet.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data) {
  var type = data.charAt(0)
  if (Number(type) != type || !packetslist[type]) return err;
  if (data.charAt(1) === ':') {
    data = data.substring(2)
    return { type: packetslist[type], data: undefined === data ? '' : data };
  } else {
    return { type: packetslist[type] };
  }
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets) {
  if (!packets.length) {
    return '0:';
  }

  var encoded = ''
    , message

  for (var i = 0, l = packets.length; i < l; i++) {
    message = exports.encodePacket(packets[i]);
    encoded += message.length + ':' + message;
  }

  return encoded;
};

/*
 * Decodes data when a payload is maybe expected.
 *
 * @param {String} data
 * @return {Array} packets
 * @api public
 */

exports.decodePayload = function (data) {
  if (data == '') {
    // parser error - ignoring payload
    return [err];
  }

  var packets = []
    , length = ''
    , n, msg, packet

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data[i]

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return [err];
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return [err];
      }

      if (msg.length) {
        packet = exports.decodePacket(msg);

        if (err.type == packet.type && err.data == packet.data) {
          // parser error in individual packet - ignoring payload
          return [err];
        }

        packets.push(packet);
      }

      // advance cursor
      i += n;
      length = ''
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return [err];
  }

  return packets;
};
<|endoftext|>


<commit_before>'use strict';

var parser = module.exports = {};

var glob        = require('glob');
var node_path   = require('path');
var sep         = node_path.sep;
var node_fs     = require('fs');
var lang        = require('./lang');
var queue       = require('async');

// parse the tree structure of the document
// basic infomations
// @param {path} dir absolute dir of the document root

// tree:
// {
//     index: 'README.md',
//     depth: 0,
//     pages: {
//         '1_a.md': {
//             parent: <recursive>
//             depth: 1,
//             // empty array indicates there're no sub pages
//             pages: {}
//         }
//     }
// }
parser.raw_tree = function (dir, callback) {
    glob('**/*.md', {
        cwd: dir,
        strict: true

    }, function (err, files) {
        if ( err ) {
            return callback(err);
        }

        var tree = {
            depth: 0,
            type: 'dir',
            pages: {}
        };

        files.forEach(function (file) {
            parser._save_item(tree, file, dir);
        });

        callback(null, tree);
    });
};

parser._save_item = function (tree, path, root) {
    var parent = tree;
    var abs = root;

    path.split(sep).some(function (slice, i) {
        abs = node_path.join(abs, slice);

        if ( slice === 'README.md' ) {
            parent.path = abs;
            return true;
        }

        if ( !(slice in parent.pages) ) {
            var current = parent.pages[slice] = {
                // duplicate data for more easily parsing
                name: slice,
                // parent: parent,
                depth: i + 1,
                path: abs
            };

            if ( node_fs.statSync(abs).isDirectory() ) {
                current.pages = {};
                current.type = 'dir';
                parent = current;

            } else {
                current.type = 'file';
                // if file occurs, skip parsing
                return true;
            }
        }
    });
};

// @param {Object=} languages
// {
//     "zh-CN": {
//     }
// }
parser.language_tree = function (raw_tree, languages) {
    if ( lang.isEmptyObject(languages) ) {
        languages = null;
    }

    var ret = {};

    if ( languages ) {
        var root = raw_tree.pages || {};

        lang.each(languages, function (name, language) {
            ret[language] = {
                name: name,
                tree: root[language] || {}
            };
        });

    } else {
        return {
            default: {
                tree: raw_tree
            }
        };
    }
};

// options.
parser.markdown = function (code, callback) {
    marked(code, callback);
};

// @param {Object} options
// - define
parser.sort_tree = function (tree, options, callback) {

};

// create a cache
parser.create_data = function (language_tree, callback) {
    var counter = Object.keys(language_tree).length;
    var error;

    function iterator (node, done) {
        if ( node.type === 'file' ) {
            var file = node.path;

            node_fs.readFile(file, function (err, content) {
                if ( err ) {
                    return done(err);
                }

                parser.markdown(content, function (err, html) {
                    if ( err ) {
                        return done(err);
                    }

                    node.html = html;
                    done(null);
                });
            });

        } else {
            done(null);
        }
    };

    lang.each(language_tree, function (tree, language) {
        parser.walk(tree, function (err) {
            if ( err ) {
                error = err;
                return callback(err);

            } else if (error) {
                return;

            } else if ( -- counter === 0 ) {
                return callback(null, language_tree);
            }
        });
    });
};

// walk a tree
parser.walk = function (tree, iterator, callback) {
    var q = async.queue(iterator);
    var errors = [];

    q.drain = function () {
        callback( errors.length ? errors : null );
    };

    function walk (node) {
        q.push(node, function (err){
            errors.push(err);
        });

        if ( node.pages ) {
            lang.each(node.pages, function (sub_node, name) {
                walk(sub_node);
            });
        }
    }
};

<commit_msg>finish parsers<commit_after>'use strict';

var parser = module.exports = {};

var glob        = require('glob');
var marked      = require('marked');
var highlight   = require('highlight.js');
var node_path   = require('path');
var sep         = node_path.sep;
var node_fs     = require('fs');
var lang        = require('./lang');
var async       = require('async');

// parse the tree structure of the document
// basic infomations
// @param {path} dir absolute dir of the document root

// tree:
// {
//     index: 'README.md',
//     // relative depth
//     rel_depth: 0,
//     pages: {
//         '1_a.md': {
//             parent: <recursive>
//             rel_depth: 1,
//             // empty array indicates there're no sub pages
//             pages: {}
//         }
//     }
// }
parser.raw_tree = function (dir, callback) {
    glob('**/*.md', {
        cwd: dir,
        strict: true

    }, function (err, files) {
        if ( err ) {
            return callback(err);
        }

        var tree = {
            rel_depth: 0,
            pages: {},
            type: 'dir'
        };

        files.forEach(function (file) {
            parser._save_item(tree, file, dir);
        });

        callback(null, tree);
    });
};

parser._save_item = function (tree, path, root) {
    var parent = tree;
    var abs = root;

    path.split(sep).some(function (slice, i) {
        abs = node_path.join(abs, slice);

        if ( slice === 'README.md' ) {
            parent.path = abs;
            return true;
        }

        var current = parent.pages[slice];

        if ( !current ) {
            current = parent.pages[slice] = {
                // duplicate data for more easily parsing
                name: slice,
                // parent: parent,
                rel_depth: i + 1,
                path: abs
            };

            if ( node_fs.statSync(abs).isDirectory() ) {
                if ( !current.pages ) {
                    current.pages = {};
                }

                current.type = 'dir';
                parent = current;

            } else {
                current.type = 'file';
                // if file occurs, skip parsing
                return true;
            }
        }

        parent = current;
    });
};

// @param {Object=} languages
// {
//     "zh-CN": {
//     }
// }
parser.language_tree = function (raw_tree, languages) {
    if ( lang.isEmptyObject(languages) ) {
        languages = null;
    }

    var ret = {};

    if ( languages ) {
        var root = raw_tree.pages || {};

        lang.each(languages, function (name, language) {
            ret[language.toLowerCase()] = {
                name: name,
                tree: root[language] || {}
            };
        });

        return ret;

    } else {
        return {
            default: {
                tree: raw_tree
            }
        };
    }
};

var SUPPORTED_LANG = Object.keys(highlight.LANGUAGES);

var marked_opt = {
    breaks: false,
    gfm: true,
    langPrefix: 'language-',
    pedantic: false,
    sanitize: false,
    silent: false,
    smartLists: false,
    smartypants: false,
    tables: true,
    highlight: function (code, lang) {
        if ( ~ SUPPORTED_LANG.indexOf(lang) ) {
            return highlight.highlight(lang, code).value;
        } else {
            return highlight.highlightAuto(code).value;
        }
    }
};

// options.
parser.markdown = function (code, callback) {
    marked(code, marked_opt, callback);
};

// @param {Object} options
// - define
parser.sort_tree = function (tree, options, callback) {

};

// create a cache
parser.create_data = function (language_tree, callback) {
    var counter = Object.keys(language_tree).length;
    var error;

    function iterator (node, done) {
        if ( node.type === 'file' ) {
            var file = node.path;

            node_fs.readFile(file, function (err, content) {
                if ( err ) {
                    return done(err);
                }

                parser.markdown(content.toString(), function (err, html) {
                    if ( err ) {
                        return done(err);
                    }

                    node.html = html;
                    done(null);
                });
            });

        } else {
            done(null);
        }
    };

    lang.each(language_tree, function (l, language) {
        parser.walk(l.tree, iterator, function (err) {
            if ( err ) {
                error = err;
                return callback(err);

            } else if (error) {
                return;

            } else if ( -- counter === 0 ) {
                return callback(null, language_tree);
            }
        });
    });
};

// walk a tree
parser.walk = function (tree, iterator, callback) {
    var q = async.queue(iterator);
    var errors = [];

    q.drain = function () {
        callback( errors.length ? errors : null );
    };

    function walk (node) {
        q.push(node, function (err){
            if ( err ) {
                errors.push(err);
            }
        });

        if ( node.pages ) {
            lang.each(node.pages, function (sub_node, name) {
                walk(sub_node);
            });
        }
    }

    walk(tree);
};

<|endoftext|>


<commit_before>var fs = require("fs");
var path = require("path");
var util = require("util");
var _ = require("underscore");
var markdown = require("marked");
var findFiles = require("./utils/find_files");

var app = {};

function Parser(_app)
{
	var self = this;

	// Global Variables
	app = _app;

	// Class Variables
	self.parsers = {};
	self.parsedFileElements = [];
	self.parsedFiles = [];

	// Markdown settings
	markdown.setOptions(app.options.marked);

	// Parser laden
	var parsers = Object.keys(app.parsers);
	parsers.forEach(function(parser) {
		var filename = app.parsers[parser];
		app.debug("load parser: " + parser + ", " + filename);
		self.addParser(parser, require(filename));
	});
} // Parser

/**
 * Inherit
 */
util.inherits(Parser, Object);

/**
 * Exports
 */
module.exports = Parser;

/**
 * Add a Parser
 */
Parser.prototype.addParser = function(name, parser)
{
	this.parsers[name] = parser;
}; // addParser

/**
 * Parse files in specified folder.
 * @param {Object} options The options used to parse and filder the files.
 * @param {Object[]} parsedFiles List of parsed files.
 * @param {String[]} parsedFilenames List of parsed files, with full path.
 */
Parser.prototype.parseFiles = function(options, parsedFiles, parsedFilenames)
{
    var self = this;

    var files = findFiles(options);
    // Parser
    for(var i = 0; i < files.length; i += 1) {
        var filename = options.src + files[i];
        var parsedFile = self.parseFile(filename);
        if(parsedFile) {
            app.log("parse file: " + filename);
            parsedFiles.push(parsedFile);
            parsedFilenames.push(filename);
        }
    } // for
}; // parseFiles

/**
 * Execute Fileparsing
 */
Parser.prototype.parseFile = function(filename)
{
    var self = this;

    app.debug("inspect file: " + filename);

    self.filename = filename;
    self.extension = path.extname(filename).toLowerCase();
    self.src = fs.readFileSync(filename, "utf8").toString();
    app.debug("size: " + self.src.length);

    // Unify Linebreaks
    self.src = self.src.replace(/\r\n/g, "\n");

    self.blocks = [];
    self.indexApiBlocks = [];

    // Determine Blocks
    self.blocks = self._findBlocks();
    if(self.blocks.length === 0) return;
    app.debug("count blocks: " + self.blocks.length);

    // Determine Elements of Blocks
    self.elements = self.blocks.map(function(block, i){
        app.debug("count elements in block " + i + ": " + block.length);
        return self._findElements(block);
    });
    if(self.elements.length === 0) return;

    // Determine List of Blocks with API-Elements
    self.indexApiBlocks = self._findBlockWithApiGetIndex(self.elements);
    if(self.indexApiBlocks.length === 0) return;

    return self._parseBlockElements(self.indexApiBlocks, self.elements);
}; // parseFile

/**
 * Parse API Elements with Plugins
 *
 * @param indexApiBlocks
 * @param detectedElements
 * @returns {___anonymous2249_2250}
 */
Parser.prototype._parseBlockElements = function(indexApiBlocks, detectedElements)
{
	var self = this;
	var parsedBlocks = [];
	for(var i = 0; i < indexApiBlocks.length; i += 1)
	{
		var blockIndex = indexApiBlocks[i];
		var elements = detectedElements[blockIndex];
		var blockData = {
			global: {},
			local: {}
		};
		var preventGlobal = false;
		var countAllowedMultiple = 0;
		for(var j = 0; j < elements.length; j += 1)
		{
			var element = elements[j];
			if(self.parsers[element.name])
			{
				// Determine Fieldvalues
				var result = null;
				try {
					result = self.parsers[element.name].parse(element.content, element.source);

					// Markdown
					// TODO: Evaluate if better add a function in specific worker_{name}.js
					if(app.options.marked.gfm &&
						 self.parsers[element.name].markdownFields &&
						 self.parsers[element.name].markdownFields.length > 0
					)
					{
						for(var markdownIndex = 0; markdownIndex < self.parsers[element.name].markdownFields.length; markdownIndex += 1)
						{
							var markdownField = self.parsers[element.name].markdownFields[markdownIndex];
							if(result[markdownField])
							{
								result[markdownField] = markdown(result[markdownField]);
								// remove line breaks.
								result[markdownField] = result[markdownField].replace(/(\r\n|\n|\r)/g, "");
							}
						} // for
					}
				}
				catch(e)
				{
					throw new Error("\"@" + element.sourceName + "\" in file \"" + self.filename +
						"\" block number " + (blockIndex + 1) + " " + e
					);
				}

				if( ! result)
				{
					throw new Error("Empty result for \"@" + element.sourceName + "\" in file \"" + self.filename +
						"\" block number " + (blockIndex + 1) + "."
					);
				}

				// Check if it is allowed to add to global namespace.
				if(self.parsers[element.name].preventGlobal && self.parsers[element.name].preventGlobal === true)
				{
					preventGlobal = true;
					// Check if count global namespace entries > count allowed
					// (e.g. @successTitle is global, but should co-exist with @apiErrorStructure)
					if(Object.keys(blockData.global).length > countAllowedMultiple)
					{
						throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
							"\" block number " + (blockIndex + 1) + ", only one definition or use is allowed in the same block."
						);
					}
				}
				if(self.parsers[element.name].pushTo)
				{
					// Fieldvalues will be inserted into subpath
					var pushTo = self.parsers[element.name].pushTo();

					// Only one global allowed per block
					if(pushTo === "global" || pushTo.substr(0, 7) === "global.")
					{
						var allowMultiple = self.parsers[element.name].allowMultiple || false;

						if(allowMultiple)
						{
							countAllowedMultiple += 1;
						}
						else
						{
							if(Object.keys(blockData.global).length > 0)
							{
								throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
									"\" block number " + (blockIndex + 1) + ", only one definition per block allowed."
								);
							}

							if(preventGlobal === true)
							{
								throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
									"\" block number " + (blockIndex + 1) + ", only one definition or use is allowed in the same block."
								);
							}
						}
					}

					if( ! blockData[pushTo])
					{
						// Create path
						self._createObjectPath(blockData, pushTo);
					}

					var blockDataPath = _pathToObject(pushTo, blockData);

					// Insert Fieldvalues in Path-Array
					if(typeof(blockData[pushTo]) === "object")
					{
						_.extend(blockData[pushTo], result);
					}
					else
					{
						blockDataPath.push(result);
					}

					if(self.parsers[element.name].extendRoot === true)
					{
						// Insert Fieldvalues in Mainpath
						_.extend(blockData, result);
					}
				}
				else
				{
					// Insert Fieldvalues in Mainpath
					_.extend(blockData, result);
				}
				blockData.index = blockIndex + 1;
			}
			else
			{
				app.logWarn("parser plugin \"" + element.name + "\" not found.");
			}
		} // for
		parsedBlocks.push(blockData);
	} // for
	return parsedBlocks;
};

/**
 * Return Path to Object.
 */
function _pathToObject(path, src) {
	if( ! path) return src;
	var pathParts = path.split(".");
	var current = src;
	for(var i = 0; i < pathParts.length; i += 1)
	{
		var part = pathParts[i];
		current = current[part];
	} // for
  return current;
} // _pathToObject

/**
 * Create a not existing Path in an Object.
 *
 * @param src
 * @param path
 * @returns {Object}
 */
Parser.prototype._createObjectPath = function(src, path)
{
	if( ! path) return src;
	var pathParts = path.split(".");
	var current = src;
	for(var i = 0; i < pathParts.length; i += 1)
	{
		var part = pathParts[i];
		if( ! current[part])
		{
			if(i === (pathParts.length - 1) ) current[part] = [];
			else current[part] = {};
		}
		current = current[part];
	} // for
	return current;
}; // _createObjectPath

/**
 * Determine Blocks
 */
Parser.prototype._findBlocks = function()
{
    var self = this;
    var blocks = [];
    var src = self.src;

    // Replace Linebreak with Unicode
    src = src.replace(/\n/g, "\uffff");

    var regexs = {
        ".coffee": {
            // Find document blocks between "###" and "###"
            docBlocksRegExp: /###\uffff?(.+?)###/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".erl": {
            // Find document blocks between "%{" and "%}"
            docBlocksRegExp: /\%\{\uffff?(.+?)\%\}/g,
            // Remove not needed " % " and "	" (tabs) at the beginning
            // HINT: Not sure if erlang developer use the %, but i think it should be no problem
            inlineRegExp: /^(\t+)?(\%)[ ]?/gm
        },
        ".py": {
            // Find document blocks between """ and """
            docBlocksRegExp: /\"\"\"\uffff?(.+?)\"\"\"/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".rb": {
            // Find document blocks between "=begin" and "=end"
            docBlocksRegExp: /\=begin\uffff?(.+?)\=end/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".pm": {
            // Find document blocks between "#**" and "#*"
            // or between "=pod" and "=cut"
            docBlocksRegExp: /#\*\*\uffff?(.+?)#\*|=pod\uffff?(.+?)=cut/g,
            // Remove not needed " # " and "    " (tabs) at the beginning
            inlineRegExp: /^(\s+)?(#)[ ]?/gm
        },
        "default": {
            // Find document blocks between "#**" and "#*"
            docBlocksRegExp: /\/\*\*\uffff?(.+?)\*\//g,
            // Remove not needed " # " and "	" (tabs) at the beginning
            inlineRegExp: /^(\s+)?(\*)[ ]?/gm
        }
    };
    var regexForFile = regexs[self.extension] || regexs["default"];
    var matches = regexForFile.docBlocksRegExp.exec(src);
    while(matches)
    {
        var block = matches[2] || matches[1];

        // Reverse Unicode Linebreaks
        block = block.replace(/\uffff/g, "\n");

        block = block.replace(regexForFile.inlineRegExp, "");

        blocks.push(block);

        // Find next
        matches = regexForFile.docBlocksRegExp.exec(src);
    } // while
    return blocks;

}; // _findBlocks

/**
 * Check if block has API elements and return block indexes.
 */
Parser.prototype._findBlockWithApiGetIndex = function(blocks)
{
    var foundIndexes = [];
    blocks.forEach(function(block, i){
        var add;
        block.forEach(function(element)
        {
            if(add !== false){
                add = element.name.substr(0, 3) === "api" && element.name.substr(0, 9) !== "apiignore";
            }
        });
        if(add)
        {
            foundIndexes.push(i);
            app.debug("api found in block: " + i);
        }
    });
    return foundIndexes;
}; // _findBlockWithApiGetIndex

/**
 * Get Elements of Blocks.
 */
Parser.prototype._findElements = function(block)
{
	var self = this;
	var elements = [];

	// Replace Linebreak with Unicode
	block = block.replace(/\n/g, "\uffff");

	// Elements start with @
	var elementsRegExp = /(@(\w*)\s?(.+?)(?=\uffff[\s\*]*@|$))/gm;
	var matches = elementsRegExp.exec(block);
	while(matches)
	{
		var element = {
			source: matches[1],
			name: matches[2].toLowerCase(),
			sourceName: matches[2],
			content: matches[3]
		};

		// Reverse Unicode Linebreaks
		element.content = element.content.replace(/\uffff/g, "\n");
		element.source = element.source.replace(/\uffff/g, "\n");

		elements.push(element);

		// Next Match
		matches = elementsRegExp.exec(block);
	}
	return elements;
}; // _findElements
<commit_msg>Bugfix: Markdown error on Empty description.<commit_after>var fs = require("fs");
var path = require("path");
var util = require("util");
var _ = require("underscore");
var markdown = require("marked");
var findFiles = require("./utils/find_files");

var app = {};

function Parser(_app)
{
	var self = this;

	// Global Variables
	app = _app;

	// Class Variables
	self.parsers = {};
	self.parsedFileElements = [];
	self.parsedFiles = [];

	// Markdown settings
	markdown.setOptions(app.options.marked);

	// Parser laden
	var parsers = Object.keys(app.parsers);
	parsers.forEach(function(parser) {
		var filename = app.parsers[parser];
		app.debug("load parser: " + parser + ", " + filename);
		self.addParser(parser, require(filename));
	});
} // Parser

/**
 * Inherit
 */
util.inherits(Parser, Object);

/**
 * Exports
 */
module.exports = Parser;

/**
 * Add a Parser
 */
Parser.prototype.addParser = function(name, parser)
{
	this.parsers[name] = parser;
}; // addParser

/**
 * Parse files in specified folder.
 * @param {Object} options The options used to parse and filder the files.
 * @param {Object[]} parsedFiles List of parsed files.
 * @param {String[]} parsedFilenames List of parsed files, with full path.
 */
Parser.prototype.parseFiles = function(options, parsedFiles, parsedFilenames)
{
    var self = this;

    var files = findFiles(options);
    // Parser
    for(var i = 0; i < files.length; i += 1) {
        var filename = options.src + files[i];
        var parsedFile = self.parseFile(filename);
        if(parsedFile) {
            app.log("parse file: " + filename);
            parsedFiles.push(parsedFile);
            parsedFilenames.push(filename);
        }
    } // for
}; // parseFiles

/**
 * Execute Fileparsing
 */
Parser.prototype.parseFile = function(filename)
{
    var self = this;

    app.debug("inspect file: " + filename);

    self.filename = filename;
    self.extension = path.extname(filename).toLowerCase();
    self.src = fs.readFileSync(filename, "utf8").toString();
    app.debug("size: " + self.src.length);

    // Unify Linebreaks
    self.src = self.src.replace(/\r\n/g, "\n");

    self.blocks = [];
    self.indexApiBlocks = [];

    // Determine Blocks
    self.blocks = self._findBlocks();
    if(self.blocks.length === 0) return;
    app.debug("count blocks: " + self.blocks.length);

    // Determine Elements of Blocks
    self.elements = self.blocks.map(function(block, i){
        app.debug("count elements in block " + i + ": " + block.length);
        return self._findElements(block);
    });
    if(self.elements.length === 0) return;

    // Determine List of Blocks with API-Elements
    self.indexApiBlocks = self._findBlockWithApiGetIndex(self.elements);
    if(self.indexApiBlocks.length === 0) return;

    return self._parseBlockElements(self.indexApiBlocks, self.elements);
}; // parseFile

/**
 * Parse API Elements with Plugins
 *
 * @param indexApiBlocks
 * @param detectedElements
 * @returns {___anonymous2249_2250}
 */
Parser.prototype._parseBlockElements = function(indexApiBlocks, detectedElements)
{
	var self = this;
	var parsedBlocks = [];
	for(var i = 0; i < indexApiBlocks.length; i += 1)
	{
		var blockIndex = indexApiBlocks[i];
		var elements = detectedElements[blockIndex];
		var blockData = {
			global: {},
			local: {}
		};
		var preventGlobal = false;
		var countAllowedMultiple = 0;
		for(var j = 0; j < elements.length; j += 1)
		{
			var element = elements[j];
			if(self.parsers[element.name])
			{
				// Determine Fieldvalues
				var result = null;
				try {
					result = self.parsers[element.name].parse(element.content, element.source);

					// Markdown
					// TODO: Evaluate if better add a function in specific worker_{name}.js
					if(  result &&
                         app.options.marked.gfm &&
						 self.parsers[element.name].markdownFields &&
						 self.parsers[element.name].markdownFields.length > 0
					)
					{
						for(var markdownIndex = 0; markdownIndex < self.parsers[element.name].markdownFields.length; markdownIndex += 1)
						{
							var markdownField = self.parsers[element.name].markdownFields[markdownIndex];
							if(result[markdownField])
							{
								result[markdownField] = markdown(result[markdownField]);
								// remove line breaks.
								result[markdownField] = result[markdownField].replace(/(\r\n|\n|\r)/g, "");
							}
						} // for
					}
				}
				catch(e)
				{
					throw new Error("\"@" + element.sourceName + "\" in file \"" + self.filename +
						"\" block number " + (blockIndex + 1) + ", source \"" + element.source + "\" " + e
					);
				}

				if( ! result)
				{
					throw new Error("Empty parser result for \"@" + element.sourceName + "\" in file \"" + self.filename +
						"\" block number " + (blockIndex + 1) + ", source \"" + element.source + "\""
					);
				}

				// Check if it is allowed to add to global namespace.
				if(self.parsers[element.name].preventGlobal && self.parsers[element.name].preventGlobal === true)
				{
					preventGlobal = true;
					// Check if count global namespace entries > count allowed
					// (e.g. @successTitle is global, but should co-exist with @apiErrorStructure)
					if(Object.keys(blockData.global).length > countAllowedMultiple)
					{
						throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
							"\" block number " + (blockIndex + 1) + ", only one definition or use is allowed in the same block."
						);
					}
				}
				if(self.parsers[element.name].pushTo)
				{
					// Fieldvalues will be inserted into subpath
					var pushTo = self.parsers[element.name].pushTo();

					// Only one global allowed per block
					if(pushTo === "global" || pushTo.substr(0, 7) === "global.")
					{
						var allowMultiple = self.parsers[element.name].allowMultiple || false;

						if(allowMultiple)
						{
							countAllowedMultiple += 1;
						}
						else
						{
							if(Object.keys(blockData.global).length > 0)
							{
								throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
									"\" block number " + (blockIndex + 1) + ", only one definition per block allowed."
								);
							}

							if(preventGlobal === true)
							{
								throw new Error("Can't set \"@" + element.sourceName + "\" in file \"" + self.filename +
									"\" block number " + (blockIndex + 1) + ", only one definition or use is allowed in the same block."
								);
							}
						}
					}

					if( ! blockData[pushTo])
					{
						// Create path
						self._createObjectPath(blockData, pushTo);
					}

					var blockDataPath = _pathToObject(pushTo, blockData);

					// Insert Fieldvalues in Path-Array
					if(typeof(blockData[pushTo]) === "object")
					{
						_.extend(blockData[pushTo], result);
					}
					else
					{
						blockDataPath.push(result);
					}

					if(self.parsers[element.name].extendRoot === true)
					{
						// Insert Fieldvalues in Mainpath
						_.extend(blockData, result);
					}
				}
				else
				{
					// Insert Fieldvalues in Mainpath
					_.extend(blockData, result);
				}
				blockData.index = blockIndex + 1;
			}
			else
			{
				app.logWarn("parser plugin \"" + element.name + "\" not found.");
			}
		} // for
		parsedBlocks.push(blockData);
	} // for
	return parsedBlocks;
};

/**
 * Return Path to Object.
 */
function _pathToObject(path, src) {
	if( ! path) return src;
	var pathParts = path.split(".");
	var current = src;
	for(var i = 0; i < pathParts.length; i += 1)
	{
		var part = pathParts[i];
		current = current[part];
	} // for
  return current;
} // _pathToObject

/**
 * Create a not existing Path in an Object.
 *
 * @param src
 * @param path
 * @returns {Object}
 */
Parser.prototype._createObjectPath = function(src, path)
{
	if( ! path) return src;
	var pathParts = path.split(".");
	var current = src;
	for(var i = 0; i < pathParts.length; i += 1)
	{
		var part = pathParts[i];
		if( ! current[part])
		{
			if(i === (pathParts.length - 1) ) current[part] = [];
			else current[part] = {};
		}
		current = current[part];
	} // for
	return current;
}; // _createObjectPath

/**
 * Determine Blocks
 */
Parser.prototype._findBlocks = function()
{
    var self = this;
    var blocks = [];
    var src = self.src;

    // Replace Linebreak with Unicode
    src = src.replace(/\n/g, "\uffff");

    var regexs = {
        ".coffee": {
            // Find document blocks between "###" and "###"
            docBlocksRegExp: /###\uffff?(.+?)###/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".erl": {
            // Find document blocks between "%{" and "%}"
            docBlocksRegExp: /\%\{\uffff?(.+?)\%\}/g,
            // Remove not needed " % " and "	" (tabs) at the beginning
            // HINT: Not sure if erlang developer use the %, but i think it should be no problem
            inlineRegExp: /^(\t+)?(\%)[ ]?/gm
        },
        ".py": {
            // Find document blocks between """ and """
            docBlocksRegExp: /\"\"\"\uffff?(.+?)\"\"\"/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".rb": {
            // Find document blocks between "=begin" and "=end"
            docBlocksRegExp: /\=begin\uffff?(.+?)\=end/g,
            // Remove not needed "	" (tabs) at the beginning
            inlineRegExp: /^(\t+)?[ ]?/gm
        },
        ".pm": {
            // Find document blocks between "#**" and "#*"
            // or between "=pod" and "=cut"
            docBlocksRegExp: /#\*\*\uffff?(.+?)#\*|=pod\uffff?(.+?)=cut/g,
            // Remove not needed " # " and "    " (tabs) at the beginning
            inlineRegExp: /^(\s+)?(#)[ ]?/gm
        },
        "default": {
            // Find document blocks between "#**" and "#*"
            docBlocksRegExp: /\/\*\*\uffff?(.+?)\*\//g,
            // Remove not needed " # " and "	" (tabs) at the beginning
            inlineRegExp: /^(\s+)?(\*)[ ]?/gm
        }
    };
    var regexForFile = regexs[self.extension] || regexs["default"];
    var matches = regexForFile.docBlocksRegExp.exec(src);
    while(matches)
    {
        var block = matches[2] || matches[1];

        // Reverse Unicode Linebreaks
        block = block.replace(/\uffff/g, "\n");

        block = block.replace(regexForFile.inlineRegExp, "");

        blocks.push(block);

        // Find next
        matches = regexForFile.docBlocksRegExp.exec(src);
    } // while
    return blocks;

}; // _findBlocks

/**
 * Check if block has API elements and return block indexes.
 */
Parser.prototype._findBlockWithApiGetIndex = function(blocks)
{
    var foundIndexes = [];
    blocks.forEach(function(block, i){
        var add;
        block.forEach(function(element)
        {
            if(add !== false){
                add = element.name.substr(0, 3) === "api" && element.name.substr(0, 9) !== "apiignore";
            }
        });
        if(add)
        {
            foundIndexes.push(i);
            app.debug("api found in block: " + i);
        }
    });
    return foundIndexes;
}; // _findBlockWithApiGetIndex

/**
 * Get Elements of Blocks.
 */
Parser.prototype._findElements = function(block)
{
	var self = this;
	var elements = [];

	// Replace Linebreak with Unicode
	block = block.replace(/\n/g, "\uffff");

	// Elements start with @
	var elementsRegExp = /(@(\w*)\s?(.+?)(?=\uffff[\s\*]*@|$))/gm;
	var matches = elementsRegExp.exec(block);
	while(matches)
	{
		var element = {
			source: matches[1],
			name: matches[2].toLowerCase(),
			sourceName: matches[2],
			content: matches[3]
		};

		// Reverse Unicode Linebreaks
		element.content = element.content.replace(/\uffff/g, "\n");
		element.source = element.source.replace(/\uffff/g, "\n");

		elements.push(element);

		// Next Match
		matches = elementsRegExp.exec(block);
	}
	return elements;
}; // _findElements
<|endoftext|>


<commit_before>var assert = require("assert"),
    Syntax = require("./types").Syntax,
    Patcher = require("./patcher").Patcher,
    Visitor = require("./visitor").Visitor,
    normalizeOptions = require("./options").normalize;

function Parser(source, options) {
    assert.ok(this instanceof Parser);

    options = normalizeOptions(options);

    var self = this;

    var lines = require("./lines").fromString(
        source, options.tabWidth);

    var pure = options.esprima.parse(lines.toString({
        tabWidth: options.tabWidth,
        reuseWhitespace: false,
        useTabs: false
    }), {
        loc: true,
        comment: true
    });

    new LocationFixer(lines).visit(pure);

    require("./comments").add(pure);

    // In order to ensure we reprint leading and trailing program
    // comments, wrap the original Program node with a File node.
    assert.strictEqual(pure.type, Syntax.Program);
    pure = {
        type: Syntax.File,
        program: pure,
        loc: { start: lines.firstPos(),
               end: lines.lastPos() }
    };

    self.getAst = function() {
        // Return a copy of the original AST so that any changes made may
        // be compared to the original.
        return copyAst(pure);
    };

    function getIndent(node) {
        // TODO Improve this.
        return lines.getIndentAt(node.loc.start.line);
    }

    self.getReprinter = function(node) {
        var orig = node.original,
            reprints = [];

        if (!orig ||
            !findReprints(node, reprints))
            return;

        return function(print) {
            var patcher = new Patcher(lines);

            reprints.forEach(function(reprint) {
                var old = reprint.oldNode;
                patcher.replace(
                    old.loc,
                    print(reprint.newNode).indentTail(
                        getIndent(old)));
            });

            return patcher.get(orig.loc).indentTail(-getIndent(orig));
        };
    };
}
exports.Parser = Parser;

var LocationFixer = Visitor.extend({
    init: function(lines) {
        this.lines = lines;
    },

    genericVisit: function(node) {
        this._super(node);

        var loc = node && node.loc,
            start = loc && loc.start,
            end = loc && loc.end;

        if (start) {
            start.line = Math.max(start.line, 1);
        }

        if (end) {
            end.line = Math.max(end.line, 1);

            var lines = this.lines, pos = {
                line: end.line,
                column: end.column
            };

            // Negative columns might indicate an Esprima bug?
            // For now, treat them as reverse indices, a la Python.
            if (pos.column < 0)
                pos.column += lines.getLineLength(pos.line);

            while (lines.prevPos(pos)) {
                if (/\S/.test(lines.charAt(pos))) {
                    assert.ok(lines.nextPos(pos));

                    end.line = pos.line;
                    end.column = pos.column;

                    break;
                }
            }
        }
    }
});

function copyAst(node) {
    if (typeof node === "object" &&
        node !== null)
    {
        if (node instanceof RegExp)
            return node;

        if (node instanceof Array)
            return node.map(copyAst);

        var copy = {},
            key,
            val;

        for (key in node) {
            if (node.hasOwnProperty(key)) {
                val = copyAst(node[key]);
                if (typeof val !== "function")
                    copy[key] = val;
            }
        }

        // Provide a link from the copy to the original.
        Object.defineProperty(copy, "original", {
            value: node,
            configurable: false,
            enumerable: false,
            writable: true,
        });

        return copy;
    }

    return node;
}

function findReprints(node, reprints) {
    assert.ok(node.original);
    assert.deepEqual(reprints, []);

    var canReprint = findChildReprints(node.original, node, reprints);

    if (!canReprint) {
        // Make absolutely sure the calling code does not attempt to reprint
        // any nodes.
        reprints.length = 0;
    }

    return canReprint;
}

function findAnyReprints(a, b, reprints) {
    if (a === b)
        return true;

    if (a instanceof Array)
        return findArrayReprints(a, b, reprints);

    if (typeof a === "object")
        return findObjectReprints(a, b, reprints);

    return false;
}

function findArrayReprints(a, b, reprints) {
    assert.ok(a instanceof Array);
    var len = a.length;

    if (!(b instanceof Array &&
          b.length === len))
        return false;

    for (var i = 0; i < len; ++i)
        if (!findAnyReprints(a[i], b[i], reprints))
            return false;

    return true;
}

function findObjectReprints(a, b, reprints) {
    assert.strictEqual(typeof a, "object");
    if (!a || !b || typeof b !== "object")
        return false;

    var childReprints = [];
    var canReprintChildren = findChildReprints(a, b, childReprints);

    if (a.type in Syntax) {
        // TODO Decompose this check: if (!printTheSame(a, b))
        if (a.type !== b.type)
            return false;

        if (!canReprintChildren) {
            reprints.push({
                oldNode: a,
                newNode: b
            });

            return true;
        }
    }

    reprints.push.apply(reprints, childReprints);
    return canReprintChildren;
}

function findChildReprints(a, b, reprints) {
    assert.strictEqual(typeof a, "object");
    assert.strictEqual(typeof b, "object");

    for (var k in getUnionOfKeys(a, b)) {
        if (k === "loc")
            continue;

        if (!findAnyReprints(a[k], b[k], reprints))
            return false;
    }

    return true;
}

function getUnionOfKeys(obj) {
    for (var i = 0, key,
             result = {},
             objs = arguments,
             argc = objs.length;
         i < argc;
         i += 1)
    {
        obj = objs[i];
        for (key in obj)
            if (obj.hasOwnProperty(key))
                result[key] = true;
    }
    return result;
}
<commit_msg>Decorate SourceLocation objects with a "lines" property.<commit_after>var assert = require("assert"),
    Syntax = require("./types").Syntax,
    Patcher = require("./patcher").Patcher,
    Visitor = require("./visitor").Visitor,
    normalizeOptions = require("./options").normalize;

function Parser(source, options) {
    assert.ok(this instanceof Parser);

    options = normalizeOptions(options);

    var self = this;

    var lines = require("./lines").fromString(
        source, options.tabWidth);

    var pure = options.esprima.parse(lines.toString({
        tabWidth: options.tabWidth,
        reuseWhitespace: false,
        useTabs: false
    }), {
        loc: true,
        comment: true
    });

    new LocationFixer(lines).visit(pure);

    require("./comments").add(pure);

    // In order to ensure we reprint leading and trailing program
    // comments, wrap the original Program node with a File node.
    assert.strictEqual(pure.type, Syntax.Program);
    pure = {
        type: Syntax.File,
        program: pure,
        loc: { start: lines.firstPos(),
               end: lines.lastPos() }
    };

    self.getAst = function() {
        // Return a copy of the original AST so that any changes made may
        // be compared to the original.
        return copyAst(pure);
    };

    function getIndent(node) {
        // TODO Improve this.
        return lines.getIndentAt(node.loc.start.line);
    }

    self.getReprinter = function(node) {
        var orig = node.original,
            reprints = [];

        if (!orig ||
            !findReprints(node, reprints))
            return;

        return function(print) {
            var patcher = new Patcher(lines);

            reprints.forEach(function(reprint) {
                var old = reprint.oldNode;
                patcher.replace(
                    old.loc,
                    print(reprint.newNode).indentTail(
                        getIndent(old)));
            });

            return patcher.get(orig.loc).indentTail(-getIndent(orig));
        };
    };
}
exports.Parser = Parser;

var LocationFixer = Visitor.extend({
    init: function(lines) {
        this.lines = lines;
    },

    genericVisit: function(node) {
        this._super(node);

        var loc = node && node.loc,
            start = loc && loc.start,
            end = loc && loc.end;

        if (loc) {
            Object.defineProperty(loc, "lines", {
                value: this.lines
            });
        }

        if (start) {
            start.line = Math.max(start.line, 1);
        }

        if (end) {
            end.line = Math.max(end.line, 1);

            var lines = loc.lines, pos = {
                line: end.line,
                column: end.column
            };

            // Negative columns might indicate an Esprima bug?
            // For now, treat them as reverse indices, a la Python.
            if (pos.column < 0)
                pos.column += lines.getLineLength(pos.line);

            while (lines.prevPos(pos)) {
                if (/\S/.test(lines.charAt(pos))) {
                    assert.ok(lines.nextPos(pos));

                    end.line = pos.line;
                    end.column = pos.column;

                    break;
                }
            }
        }
    }
});

function copyAst(node) {
    if (typeof node === "object" &&
        node !== null)
    {
        if (node instanceof RegExp)
            return node;

        if (node instanceof Array)
            return node.map(copyAst);

        var copy = {},
            key,
            val;

        for (key in node) {
            if (node.hasOwnProperty(key)) {
                val = copyAst(node[key]);
                if (typeof val !== "function")
                    copy[key] = val;
            }
        }

        // Provide a link from the copy to the original.
        Object.defineProperty(copy, "original", {
            value: node,
            configurable: false,
            enumerable: false,
            writable: true,
        });

        return copy;
    }

    return node;
}

function findReprints(node, reprints) {
    assert.ok(node.original);
    assert.deepEqual(reprints, []);

    var canReprint = findChildReprints(node.original, node, reprints);

    if (!canReprint) {
        // Make absolutely sure the calling code does not attempt to reprint
        // any nodes.
        reprints.length = 0;
    }

    return canReprint;
}

function findAnyReprints(a, b, reprints) {
    if (a === b)
        return true;

    if (a instanceof Array)
        return findArrayReprints(a, b, reprints);

    if (typeof a === "object")
        return findObjectReprints(a, b, reprints);

    return false;
}

function findArrayReprints(a, b, reprints) {
    assert.ok(a instanceof Array);
    var len = a.length;

    if (!(b instanceof Array &&
          b.length === len))
        return false;

    for (var i = 0; i < len; ++i)
        if (!findAnyReprints(a[i], b[i], reprints))
            return false;

    return true;
}

function findObjectReprints(a, b, reprints) {
    assert.strictEqual(typeof a, "object");
    if (!a || !b || typeof b !== "object")
        return false;

    var childReprints = [];
    var canReprintChildren = findChildReprints(a, b, childReprints);

    if (a.type in Syntax) {
        // TODO Decompose this check: if (!printTheSame(a, b))
        if (a.type !== b.type)
            return false;

        if (!canReprintChildren) {
            reprints.push({
                oldNode: a,
                newNode: b
            });

            return true;
        }
    }

    reprints.push.apply(reprints, childReprints);
    return canReprintChildren;
}

function findChildReprints(a, b, reprints) {
    assert.strictEqual(typeof a, "object");
    assert.strictEqual(typeof b, "object");

    for (var k in getUnionOfKeys(a, b)) {
        if (k === "loc")
            continue;

        if (!findAnyReprints(a[k], b[k], reprints))
            return false;
    }

    return true;
}

function getUnionOfKeys(obj) {
    for (var i = 0, key,
             result = {},
             objs = arguments,
             argc = objs.length;
         i < argc;
         i += 1)
    {
        obj = objs[i];
        for (key in obj)
            if (obj.hasOwnProperty(key))
                result[key] = true;
    }
    return result;
}
<|endoftext|>


<commit_before>const cheerio = require('cheerio')

let parser = {}
parser.parse = function (isChinese, body) {
	const $ = cheerio.load(body, {
		ignoreWhitespace: true,
		xmlMode: true
	})
	let result = ''
	if (isChinese) {
		$('div.trans-container > ul').find('p.wordGroup').each(function (i, elm) {
			result = $(this).text().replace(/\s+/g, ' ')
		})
	} else {
		result = $('div#phrsListTab > div.trans-container > ul').text()
	}
	// phrase
	if (result === '') {
		result = $('div#webPhrase > p.wordGroup').text()
	}
	// sentence
	if (result === '') {
		result = $('div#fanyiToggle > div.trans-container > p:nth-child(2)').text()
	}
	// phonetic
	result = $('div#phrsListTab > h2.wordbook-js > div.baav > span').text().replace(/\s+/g, ' ') + result
	return result
}
module.exports = parser
<commit_msg>修复查询中文只显示最后一条的问题<commit_after>const cheerio = require('cheerio')

let parser = {}
parser.parse = function (isChinese, body) {
	const $ = cheerio.load(body, {
		ignoreWhitespace: true,
		xmlMode: true
	})
	let result = ''
	if (isChinese) {
		$('div.trans-container > ul').find('p.wordGroup').each(function (i, elm) {
			result += $(this).text().replace(/\s+/g, ' ')
		})
	} else {
		result = $('div#phrsListTab > div.trans-container > ul').text()
	}
	// phrase
	if (result === '') {
		result = $('div#webPhrase > p.wordGroup').text()
	}
	// sentence
	if (result === '') {
		result = $('div#fanyiToggle > div.trans-container > p:nth-child(2)').text()
	}
	// phonetic
	result = $('div#phrsListTab > h2.wordbook-js > div.baav > span').text().replace(/\s+/g, ' ') + result
	return result
}
module.exports = parser
<|endoftext|>


<commit_before>var DEBUG = false; // `true` to print debugging info.
var TIMER = false; // `true` to time calls to `parse()` and print the results.

var debug = require('./debug')('parse');
var lex = require('./lexer');

exports = module.exports = parse;

var _comments;   // Whether comments are allowed.
var _depth;      // Current block nesting depth.
var _position;   // Whether to include line/column position.
var _tokens;     // Array of lexical tokens.

/**
 * Convert a CSS string or array of lexical tokens into a `stringify`-able AST.
 *
 * @param {String} css CSS string or array of lexical token
 * @param {Object} [options]
 * @param {Boolean} [options.comments=false] allow comment nodes in the AST
 * @returns {Object} `stringify`-able AST
 */
function parse(css, options) {
  var start; // Debug timer start.

  options || (options = {});
  _comments = !!options.comments;
  _position = !!options.position;

  _depth = 0;

  // Operate on a copy of the given tokens, or the lex()'d CSS string.
  _tokens = Array.isArray(css) ? css.slice() : lex(css);

  var rule;
  var rules = [];
  var token;

  TIMER && (start = Date.now());

  while ((token = next())) {
    rule = parseToken(token);
    rule && rules.push(rule);
  }

  TIMER && debug('ran in', (Date.now() - start) + 'ms');

  return {
    type: "stylesheet",
    stylesheet: {
      rules: rules
    }
  };
}

// -- Functions --------------------------------------------------------------

/**
 * Build an AST node from a lexical token.
 *
 * @param {Object} token lexical token
 * @param {Object} [override] object hash of properties that override those
 *   already in the token, or that will be added to the token.
 * @returns {Object} AST node
 */
function astNode(token, override) {
  override || (override = {});

  var key;
  var keys = ['type', 'name', 'value'];
  var node = {};

  // Avoiding [].forEach for performance reasons.
  for (var i = 0; i < keys.length; ++i) {
    key = keys[i];

    if (token[key]) {
      node[key] = override[key] || token[key];
    }
  }

  keys = Object.keys(override);

  for (i = 0; i < keys.length; ++i) {
    key = keys[i];

    if (!node[key]) {
      node[key] = override[key];
    }
  }

  if (_position) {
    node.position = {
      start: token.start,
      end: token.end
    };
  }

  DEBUG && debug('astNode:', JSON.stringify(node, null, 2));

  return node;
}

/**
 * Remove a lexical token from the stack and return the removed token.
 *
 * @returns {Object} lexical token
 */
function next() {
  var token = _tokens.shift();
  DEBUG && debug('next:', JSON.stringify(token, null, 2));
  return token;
}

// -- Parse* Functions ---------------------------------------------------------

/**
 * Convert an @-group lexical token to an AST node.
 *
 * @param {Object} token @-group lexical token
 * @returns {Object} @-group AST node
 */
function parseAtGroup(token) {
  _depth = _depth + 1;

  // As the @-group token is assembled, relevant token values are captured here
  // temporarily. They will later be used as `tokenize()` overrides.
  var overrides = {};

  if (token.type === 'font-face' || token.type === 'viewport') {
    overrides.declarations = parseDeclarations();
  } else {
    overrides.prefix = token.prefix;
    overrides.rules = parseRules();
  }

  return astNode(token, overrides);
}

/**
 * Convert an @import lexical token to an AST node.
 *
 * @param {Object} token @import lexical token
 * @returns {Object} @import AST node
 */
function parseAtImport(token) {
  return astNode(token);
}

/**
 * Convert an @charset token to an AST node.
 *
 * @param {Object} token @charset lexical token
 * @returns {Object} @charset node
 */
function parseCharset(token) {
  return astNode(token);
}

/**
 * Convert a comment token to an AST Node.
 *
 * @param {Object} token comment lexical token
 * @returns {Object} comment node
 */
function parseComment(token) {
  return astNode(token, {text: token.text});
}

/**
 * Convert a property lexical token to a property AST node.
 *
 * @returns {Object} property node
 */
function parseProperty(token) {
  return astNode(token);
}

/**
 * Convert a selector lexical token to a selector AST node.
 *
 * @param {Object} token selector lexical token
 * @returns {Object} selector node
 */
function parseSelector(token) {
  return astNode(token, {
    type: 'rule',
    selectors: token.text.split(','),
    declarations: parseDeclarations(token)
  });
}

/**
 * Convert a lexical token to an AST node.
 *
 * @returns {Object|undefined} AST node
 */
function parseToken(token) {
  switch (token.type) {
  // Cases are listed in roughly descending order of probability.
  case 'property': return parseProperty(token);

  case 'selector': return parseSelector(token);

  case 'at-group-end': _depth = _depth - 1; return;

  case 'media'     :
  case 'keyframes' :return parseAtGroup(token);

  case 'comment': if (_comments) { return parseComment(token); } break;

  case 'charset': return parseCharset(token);
  case 'import': return parseAtImport(token);

  case 'font-face':
  case 'supports' :
  case 'viewport' : return parseAtGroup(token);
  }

  DEBUG && debug('parseToken: unexpected token:', JSON.stringify(token));
}

// -- Parse Helper Functions ---------------------------------------------------

/**
 * Iteratively parses lexical tokens from the stack into AST nodes until a
 * conditional function returns `false`, at which point iteration terminates
 * and any AST nodes collected are returned.
 *
 * @param {Function} conditionFn
 *   @param {Object} token the lexical token being parsed
 *   @returns {Boolean} `true` if the token should be parsed, `false` otherwise
 * @return {Array} AST nodes
 */
function parseTokensWhile(conditionFn) {
  var node;
  var nodes = [];
  var token;

  while ((token = next()) && (conditionFn && conditionFn(token))) {
    node = parseToken(token);
    node && nodes.push(node);
  }

  // Place an unused non-`end` lexical token back onto the stack.
  if (token && token.type !== 'end') {
    _tokens.unshift(token);
  }

  return nodes;
}

/**
 * Convert a series of tokens into a sequence of declaration AST nodes.
 *
 * @returns {Array} declaration nodes
 */
function parseDeclarations() {
  return parseTokensWhile(function (token) {
    return (token.type === 'property' || token.type === 'comment');
  });
}

/**
 * Convert a series of tokens into a sequence of rule nodes.
 *
 * @returns {Array} rule nodes
 */
function parseRules() {
  return parseTokensWhile(function () { return _depth; });
}
<commit_msg>Trim whitespace around selectors<commit_after>var DEBUG = false; // `true` to print debugging info.
var TIMER = false; // `true` to time calls to `parse()` and print the results.

var debug = require('./debug')('parse');
var lex = require('./lexer');

exports = module.exports = parse;

var _comments;   // Whether comments are allowed.
var _depth;      // Current block nesting depth.
var _position;   // Whether to include line/column position.
var _tokens;     // Array of lexical tokens.

/**
 * Convert a CSS string or array of lexical tokens into a `stringify`-able AST.
 *
 * @param {String} css CSS string or array of lexical token
 * @param {Object} [options]
 * @param {Boolean} [options.comments=false] allow comment nodes in the AST
 * @returns {Object} `stringify`-able AST
 */
function parse(css, options) {
  var start; // Debug timer start.

  options || (options = {});
  _comments = !!options.comments;
  _position = !!options.position;

  _depth = 0;

  // Operate on a copy of the given tokens, or the lex()'d CSS string.
  _tokens = Array.isArray(css) ? css.slice() : lex(css);

  var rule;
  var rules = [];
  var token;

  TIMER && (start = Date.now());

  while ((token = next())) {
    rule = parseToken(token);
    rule && rules.push(rule);
  }

  TIMER && debug('ran in', (Date.now() - start) + 'ms');

  return {
    type: "stylesheet",
    stylesheet: {
      rules: rules
    }
  };
}

// -- Functions --------------------------------------------------------------

/**
 * Build an AST node from a lexical token.
 *
 * @param {Object} token lexical token
 * @param {Object} [override] object hash of properties that override those
 *   already in the token, or that will be added to the token.
 * @returns {Object} AST node
 */
function astNode(token, override) {
  override || (override = {});

  var key;
  var keys = ['type', 'name', 'value'];
  var node = {};

  // Avoiding [].forEach for performance reasons.
  for (var i = 0; i < keys.length; ++i) {
    key = keys[i];

    if (token[key]) {
      node[key] = override[key] || token[key];
    }
  }

  keys = Object.keys(override);

  for (i = 0; i < keys.length; ++i) {
    key = keys[i];

    if (!node[key]) {
      node[key] = override[key];
    }
  }

  if (_position) {
    node.position = {
      start: token.start,
      end: token.end
    };
  }

  DEBUG && debug('astNode:', JSON.stringify(node, null, 2));

  return node;
}

/**
 * Remove a lexical token from the stack and return the removed token.
 *
 * @returns {Object} lexical token
 */
function next() {
  var token = _tokens.shift();
  DEBUG && debug('next:', JSON.stringify(token, null, 2));
  return token;
}

// -- Parse* Functions ---------------------------------------------------------

/**
 * Convert an @-group lexical token to an AST node.
 *
 * @param {Object} token @-group lexical token
 * @returns {Object} @-group AST node
 */
function parseAtGroup(token) {
  _depth = _depth + 1;

  // As the @-group token is assembled, relevant token values are captured here
  // temporarily. They will later be used as `tokenize()` overrides.
  var overrides = {};

  if (token.type === 'font-face' || token.type === 'viewport') {
    overrides.declarations = parseDeclarations();
  } else {
    overrides.prefix = token.prefix;
    overrides.rules = parseRules();
  }

  return astNode(token, overrides);
}

/**
 * Convert an @import lexical token to an AST node.
 *
 * @param {Object} token @import lexical token
 * @returns {Object} @import AST node
 */
function parseAtImport(token) {
  return astNode(token);
}

/**
 * Convert an @charset token to an AST node.
 *
 * @param {Object} token @charset lexical token
 * @returns {Object} @charset node
 */
function parseCharset(token) {
  return astNode(token);
}

/**
 * Convert a comment token to an AST Node.
 *
 * @param {Object} token comment lexical token
 * @returns {Object} comment node
 */
function parseComment(token) {
  return astNode(token, {text: token.text});
}

/**
 * Convert a property lexical token to a property AST node.
 *
 * @returns {Object} property node
 */
function parseProperty(token) {
  return astNode(token);
}

/**
 * Convert a selector lexical token to a selector AST node.
 *
 * @param {Object} token selector lexical token
 * @returns {Object} selector node
 */
function parseSelector(token) {
  function trim(str) {
    return str.trim();
  }

  return astNode(token, {
    type: 'rule',
    selectors: token.text.split(',').map(trim),
    declarations: parseDeclarations(token)
  });
}

/**
 * Convert a lexical token to an AST node.
 *
 * @returns {Object|undefined} AST node
 */
function parseToken(token) {
  switch (token.type) {
  // Cases are listed in roughly descending order of probability.
  case 'property': return parseProperty(token);

  case 'selector': return parseSelector(token);

  case 'at-group-end': _depth = _depth - 1; return;

  case 'media'     :
  case 'keyframes' :return parseAtGroup(token);

  case 'comment': if (_comments) { return parseComment(token); } break;

  case 'charset': return parseCharset(token);
  case 'import': return parseAtImport(token);

  case 'font-face':
  case 'supports' :
  case 'viewport' : return parseAtGroup(token);
  }

  DEBUG && debug('parseToken: unexpected token:', JSON.stringify(token));
}

// -- Parse Helper Functions ---------------------------------------------------

/**
 * Iteratively parses lexical tokens from the stack into AST nodes until a
 * conditional function returns `false`, at which point iteration terminates
 * and any AST nodes collected are returned.
 *
 * @param {Function} conditionFn
 *   @param {Object} token the lexical token being parsed
 *   @returns {Boolean} `true` if the token should be parsed, `false` otherwise
 * @return {Array} AST nodes
 */
function parseTokensWhile(conditionFn) {
  var node;
  var nodes = [];
  var token;

  while ((token = next()) && (conditionFn && conditionFn(token))) {
    node = parseToken(token);
    node && nodes.push(node);
  }

  // Place an unused non-`end` lexical token back onto the stack.
  if (token && token.type !== 'end') {
    _tokens.unshift(token);
  }

  return nodes;
}

/**
 * Convert a series of tokens into a sequence of declaration AST nodes.
 *
 * @returns {Array} declaration nodes
 */
function parseDeclarations() {
  return parseTokensWhile(function (token) {
    return (token.type === 'property' || token.type === 'comment');
  });
}

/**
 * Convert a series of tokens into a sequence of rule nodes.
 *
 * @returns {Array} rule nodes
 */
function parseRules() {
  return parseTokensWhile(function () { return _depth; });
}
<|endoftext|>