<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var util = require('util');
var fs = require('fs');
var path = require('path');
var net = require('net');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;

var sprintf = require('sprintf').sprintf;
var async = require('async');
var logmagic = require('logmagic');

var constants = require('./constants');
var parser = require('./parser');
var common = require('./common');
var testUtil = require('./util');
var coverage = require('./coverage');
var getReporter = require('./reporters/index').getReporter;
var generateMakefile = require('./gen_makefile').generateMakefile;
var _debugger = require('./debugger');
var DebuggerInterface = _debugger.Interface;
var ProcessRunner = require('./process_runner/runner').ProcessRunner;

exports.exitCode = 0;
exports.processRunner = null;

function TestRunner(options) {
  var testReporter, coverageReporter, scopeLeaksReporter,
      testReporterOptions, coverageReporterOptions, scopeLeaksReporterOptions,
      defaultSocketPath;

  defaultSocketPath = sprintf('%s-%s', constants.DEFAULT_SOCKET_PATH,
                              Math.random() * 10000);
  this._tests = options['tests'];
  this._dependencies = options['dependencies'] || null;
  this._onlyEssential = options['only-essential'] || false;
  this._socketPath = options['socket-path'] || defaultSocketPath;
  this._coverage = options['coverage'];
  this._scopeLeaks = options['scope-leaks'];
  this._verbosity = options['verbosity'] || constants.DEFAULT_VERBOSITY;
  this._failfast = options['failfast'] || false;
  this._realTime = options['real-time'] || false;
  this._debug = options['debug'];

  testReporter = options['test-reporter'] || constants.DEFAULT_TEST_REPORTER;
  testReporterOptions = {
    'print_stdout': options['print-stdout'],
    'print_stderr': options['print-stderr'],
    'styles': (options['no-styles'] ? false : true),
    'report_timing': options['report-timing']
  };

  this._testReporter = getReporter('test', testReporter, this._tests,
                                   testReporterOptions);

  if (this._coverage) {
      coverageReporter = options['coverage-reporter'] || constants.DEFAULT_COVERAGE_REPORTER;
      coverageReporterOptions = {
        'directory': options['coverage-dir'],
        'file': options['coverage-file']
      };

      this._coverageReporter = getReporter('coverage',
                                            coverageReporter,
                                            this._tests,
                                            coverageReporterOptions);
  }
  else {
      this._coverageReporter = null;
  }

  if (this._scopeLeaks) {
    scopeLeaksReporter = options['scope-leaks-reporter'] || constants.DEFAULT_SCOPE_LEAKS_REPORTER;
    scopeLeaksReporterOptions = {
      'sequential': (options['concurrency'] === 1 || options['sequential'])
    };
    this._scopeLeaksReporter = getReporter('scope-leaks', scopeLeaksReporter,
                                           this._tests,
                                           scopeLeaksReporterOptions);
  }
  else {
    this._scopeLeaksReporter = null;
  }

  this._server = null;
  this._processRunner = null;
  this._testFilesData = {};

  this._completed = false;
  this._forceStopped = false;
  this._completedTests = [];
}

TestRunner.prototype.runTests = function(testInitFile, chdir,
                                         customAssertModule,
                                         timeout,
                                         concurrency, failFast) {
  var self = this, ops = [];
  timeout = timeout || constants.DEFAULT_TEST_TIMEOUT;

  function handleChildTimeout(child, filePath) {
    var resultObj, testFile;

    if (!child.killed) {
      testFile = self._testFilesData[filePath];

      resultObj = {
        'tests': [],
        'timeout': true
      };

      child.kill('SIGTERM');
      self._handleTestResult(filePath, resultObj);
    }

    if (self._failfast) {
      self.forceStop();
    }
  }

  function onBound() {
    self._testReporter.handleTestsStart();

    async.forEachSeries(self._tests, function(filePath, callback) {
      var result, pattern, cwd, child, timeoutId, testFileData;

      if (self._completed || self._forceStopped) {
        callback(new Error('Runner has been stopped'));
        return;
      }

      cwd = process.cwd();
      result = common.getTestFilePathAndPattern(filePath);
      filePath = result[0];
      pattern = result[1];
      filePath = (filePath.charAt(0) !== '/') ? path.join(cwd, filePath) : filePath;
      child = self._spawnTestProcess(filePath, testInitFile, chdir,
                                     customAssertModule, timeout,
                                     concurrency, pattern);

      if (!self._debug) {
        timeoutId = setTimeout(function() {
          handleChildTimeout(child, filePath, callback);
        }, timeout);
      }

      self._testFilesData[filePath] = {
        'child': child,
        'callback': callback,
        'timeout_id': timeoutId,
        'stdout': '',
        'stderr': ''
      };

      testFileData = self._testFilesData[filePath];

      child.stdout.on('data', function(chunk) {
        if (self._realTime) {
          process.stdout.write(chunk.toString());
        }
        else {
          testFileData['stdout'] += chunk;
        }
      });

      child.stderr.on('data', function(chunk) {
        if (self._realTime) {
          process.stderr.write(chunk.toString());
        }
        else {
          testFileData['stderr'] += chunk;
        }
      });

      child.on('exit', function() {
        clearTimeout(timeoutId);
        self._handleTestFileEnd(filePath);
        delete self._testFilesData[filePath];
      });
    },

    function(err) {
      self._handleTestsCompleted();
    });
  }

  function startServer(callback) {
    self._startServer(self._handleConnection.bind(self),
                      onBound);
  }

  function createRunner(callback) {
    self._processRunner = new ProcessRunner(self._dependencies);
    exports.processRunner = self._processRunner;
    callback(null, null);
  }

  function findDependenciesToRun(_, callback) {
    var cwd = process.cwd();
    var testPaths = self._tests.map(function(testPath) {
      testPath = common.getTestFilePathAndPattern(testPath)[0];
      testPath = (testPath.charAt(0) !== '/') ? path.join(cwd, testPath) : testPath;
      return testPath;
    });

    self._processRunner.findDependencies(testPaths, callback);
  }

  function startDependencies(dependencies, callback) {
    self._processRunner.start(dependencies, callback);
  }

  if (self._dependencies) {
    ops.push(createRunner);

    if (self._onlyEssential) {
      ops.push(findDependenciesToRun);
    }

    ops.push(startDependencies);
  }

  ops.push(startServer);

  async.waterfall(ops, function(err) {
    if (err) {
      throw err;
    }
 });
};

TestRunner.prototype._spawnTestProcess = function(filePath,
                                                  testInitFile,
                                                  chdir,
                                                  customAssertModule,
                                                  timeout,
                                                  concurrency,
                                                  pattern) {
  var cwd = process.cwd();
  var libCovDir = (this._coverage) ? path.join(cwd, 'lib-cov') : null;
  var runFilePath = path.join(__dirname, 'run_test_file.js');
  var port = parseInt((Math.random() * (65000 - 2000) + 2000), 10);
  var args = [];

  if (this._debug) {
    args.push('--debug-brk=' + port);
  }

  args = args.concat([runFilePath, filePath, this._socketPath, cwd, libCovDir,
                      this._scopeLeaks, chdir, customAssertModule, testInitFile,
                      timeout, concurrency, pattern]);
  this._testReporter.handleTestFileStart(filePath);
  var child = spawn(process.execPath, args);

  if (this._debug) {
    var debuggerInterface = new DebuggerInterface();
    debuggerInterface.connect(port, 400, function(err, client) {
      // Skip the breakpoint set by debug-brk
      client.reqContinue();
    });
  }

  return child;
};

TestRunner.prototype._addCompletedTest = function(filePath) {
  this._completedTests.push(filePath);
};

TestRunner.prototype._handleTestsCompleted = function() {
  var statusCode;

  if (!this._completed || this._forceStopped) {
    this._completed = true;
    this._stopServer();

    statusCode = this._testReporter.handleTestsComplete();

    if (this._scopeLeaks) {
      this._scopeLeaksReporter.handleTestsComplete();
    }

    if (this._coverage) {
      this._coverageReporter.handleTestsComplete();
    }

    exports.exitCode = statusCode;

    if (this._processRunner) {
      this._processRunner.stop();
    }

    if (this._debug) {
      process.exit();
    }
  }
};

TestRunner.prototype._handleTestResult = function(filePath, resultObj) {
  this._testReporter.handleTestEnd(filePath, resultObj);

  if (this._scopeLeaks) {
    this._scopeLeaksReporter.handleTestEnd(filePath, resultObj);
  }

  if (this._failfast && resultObj['status'] === 'failure') {
    this.forceStop();
  }
};

TestRunner.prototype._handleTestCoverageResult = function(filePath, coverageData) {
  var coverageObj = JSON.parse(coverageData);
  this._coverageReporter.handleTestFileComplete(filePath, coverageObj);
};

TestRunner.prototype._handleTestFileEnd = function(filePath) {
  var testData, coverageData, resultObj, coverageObj, split, testFile;
  var stdout = '';
  var stderr = '';

  testFile = this._testFilesData[filePath];
  if (testFile) {
    stdout = testFile['stdout'];
    stderr = testFile['stderr'];
  }

  this._testReporter.handleTestFileComplete(filePath, stdout, stderr);
  this._addCompletedTest(filePath);

  if (testFile) {
    testFile['callback']();
  }
};

TestRunner.prototype._handleConnection = function(connection) {
  var self = this;
  var data = '';
  var lineProcessor = new testUtil.LineProcessor();
  var dataString, endMarkIndex, testFile, testFileCallback, testFileTimeoutId;

  function onLine(line) {
    var result, filePath, end, resultObj;
    result = testUtil.parseResultLine(line);
    end = result[0];
    filePath = result[1];
    resultObj = result[2];

    if (end) {
      return;
    }

    if (resultObj.hasOwnProperty('coverage')) {
      self._handleTestCoverageResult(filePath, resultObj['coverage']);
    }
    else {
      self._handleTestResult(filePath, resultObj);
    }
  }

  lineProcessor.on('line', onLine);

  function onData(chunk) {
    lineProcessor.appendData(chunk);
    data += chunk;
  }

  connection.on('data', onData);
};

TestRunner.prototype._startServer = function(connectionHandler,
                                             onBound) {
  this._server = net.createServer(connectionHandler);
  this._server.listen(this._socketPath, onBound);
};

TestRunner.prototype.forceStop = function() {
  var testFile, testFileData, child, timeoutId;

  this._forceStopped = true;
  for (testFile in this._testFilesData) {
    if (this._testFilesData.hasOwnProperty(testFile)) {
      testFileData = this._testFilesData[testFile];
      child = testFileData['child'];
      timeoutId = testFileData['timeout_id'];
      clearTimeout(timeoutId);
      child.kill('SIGTERM');
    }
  }
};

TestRunner.prototype._stopServer = function() {
  if (this._server) {
    this._server.close();
    this._server = null;
  }
};

function run(cwd, argv) {
  var customAssertModule, exportedFunctions;
  var runner, testReporter, coverageArgs;
  var socketPath, concurrency, scopeLeaks;

  if ((argv === undefined) && (cwd instanceof Array)) {
    argv = cwd;
  }

  var p = parser.getParser(constants.WHISKEY_OPTIONS);
  p.banner = 'Usage: whiskey [options] --tests "files"';
  var options = parser.parseArgv(p, argv);

  if (options['coverage-files']) {
      var coverageReporter = options['coverage-reporter'] || constants.DEFAULT_COVERAGE_REPORTER;
      var coverageReporterOptions = {
        'directory': options['coverage-dir'],
        'file': options['coverage-file']
      };

      this._coverageReporter = getReporter('coverage',
                                            coverageReporter,
                                            [],
                                          coverageReporterOptions);
    var coverageObj = coverage.aggregateCoverage(options['coverage-files'].split(','));
    this._coverageReporter.handleTestsComplete(coverageObj);
  }
  else if (options.tests && options.tests.length > 0) {
    options.tests = options.tests.split(' ');

    if (options['debug'] && options.tests.length > 1) {
      throw new Error('--debug option can currently only be used with a single test file');
    }
    else if (options['gen-makefile'] && options['makefile-path']) {
      generateMakefile(options.tests, options['makefile-path'], function(err) {
        if (err) {
          throw err;
        }

        util.puts(sprintf('Makefile has been saved in %s.', options['makefile-path']));
      });

      return;
    }

    customAssertModule = options['custom-assert-module'];
    if (customAssertModule) {
      customAssertModule = (customAssertModule.charAt(0) !== '/') ? path.join(cwd, customAssertModule) : customAssertModule;

      if (path.existsSync(customAssertModule)) {
        customAssertModule = customAssertModule.replace(/$\.js/, '');
      }
      else {
        customAssertModule = null;
      }
    }

    if (options['coverage']) {
      var nodePath = process.env['NODE_PATH'];

      if (!nodePath || nodePath.indexOf('lib-cov') === -1) {
        throw new Error('lib-cov is not in NODE_PATH. NODE_PATH environment variable' +
                         ' must contain lib-cov path for the coverage to work.');
      }

      coverageArgs = [ 'jscoverage' ];

      if (options['coverage-encoding']) {
        coverageArgs.push(sprintf('--encoding=%s', options['coverage-encoding']));
      }

      if (options['coverage-exclude']) {
        coverageArgs.push(sprintf('--exclude=%s', options['coverage-exclude']));
      }

      coverageArgs.push(sprintf('lib %s', constants.COVERAGE_PATH));
      coverageArgs = coverageArgs.join(' ');
      exec(sprintf('rm -fr %s ; %s', constants.COVERAGE_PATH, coverageArgs),
           function(err) {
        if (err) {
          if (err.message.match(/jscoverage: not found/i)) {
            err = new Error('jscoverage binary not found. To use test coverage ' +
                            ' you need to install node-jscoverag binary - ' +
                            'https://github.com/visionmedia/node-jscoverage');
          }

          throw err;
        }
      });
    }

    concurrency = options['sequential'] ? 1 : options['concurrency'];
    concurrency = concurrency || constants.DEFAULT_CONCURRENCY;
    scopeLeaks = options['scope-leaks'];

    options['print-stdout'] = (options['quiet']) ? false : true;
    options['print-stderr'] = (options['quiet']) ? false : true;

    if (options['quiet']) {
      logmagic.registerSink('null', function nullLogger() {});
      logmagic.route('__root__', logmagic.INFO, 'null');
    }

    runner = new TestRunner(options);
    runner.runTests(options['test-init-file'], options['chdir'],
                    customAssertModule,
                    options['timeout'],
                    concurrency,
                    options['fail-fast']);
  }
  else if (!p._halted) {
    console.log(p.banner);
  }

  process.on('SIGINT', function onSigint() {
    runner.forceStop();
  });

  process.on('exit', function() {
    process.reallyExit(exports.exitCode);
  });
}

exports.run = run;
<commit_msg>Fix a race condition when using code coverage and instrumenting the code.<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var util = require('util');
var fs = require('fs');
var path = require('path');
var net = require('net');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;

var sprintf = require('sprintf').sprintf;
var async = require('async');
var logmagic = require('logmagic');

var constants = require('./constants');
var parser = require('./parser');
var common = require('./common');
var testUtil = require('./util');
var coverage = require('./coverage');
var getReporter = require('./reporters/index').getReporter;
var generateMakefile = require('./gen_makefile').generateMakefile;
var _debugger = require('./debugger');
var DebuggerInterface = _debugger.Interface;
var ProcessRunner = require('./process_runner/runner').ProcessRunner;

exports.exitCode = 0;
exports.processRunner = null;

function TestRunner(options) {
  var testReporter, coverageReporter, scopeLeaksReporter,
      testReporterOptions, coverageReporterOptions, scopeLeaksReporterOptions,
      defaultSocketPath;

  defaultSocketPath = sprintf('%s-%s', constants.DEFAULT_SOCKET_PATH,
                              Math.random() * 10000);
  this._tests = options['tests'];
  this._dependencies = options['dependencies'] || null;
  this._onlyEssential = options['only-essential'] || false;
  this._socketPath = options['socket-path'] || defaultSocketPath;
  this._coverage = options['coverage'];
  this._scopeLeaks = options['scope-leaks'];
  this._verbosity = options['verbosity'] || constants.DEFAULT_VERBOSITY;
  this._failfast = options['failfast'] || false;
  this._realTime = options['real-time'] || false;
  this._debug = options['debug'];

  testReporter = options['test-reporter'] || constants.DEFAULT_TEST_REPORTER;
  testReporterOptions = {
    'print_stdout': options['print-stdout'],
    'print_stderr': options['print-stderr'],
    'styles': (options['no-styles'] ? false : true),
    'report_timing': options['report-timing']
  };

  this._testReporter = getReporter('test', testReporter, this._tests,
                                   testReporterOptions);

  if (this._coverage) {
      coverageReporter = options['coverage-reporter'] || constants.DEFAULT_COVERAGE_REPORTER;
      coverageReporterOptions = {
        'directory': options['coverage-dir'],
        'file': options['coverage-file']
      };

      this._coverageReporter = getReporter('coverage',
                                            coverageReporter,
                                            this._tests,
                                            coverageReporterOptions);
  }
  else {
      this._coverageReporter = null;
  }

  if (this._scopeLeaks) {
    scopeLeaksReporter = options['scope-leaks-reporter'] || constants.DEFAULT_SCOPE_LEAKS_REPORTER;
    scopeLeaksReporterOptions = {
      'sequential': (options['concurrency'] === 1 || options['sequential'])
    };
    this._scopeLeaksReporter = getReporter('scope-leaks', scopeLeaksReporter,
                                           this._tests,
                                           scopeLeaksReporterOptions);
  }
  else {
    this._scopeLeaksReporter = null;
  }

  this._server = null;
  this._processRunner = null;
  this._testFilesData = {};

  this._completed = false;
  this._forceStopped = false;
  this._completedTests = [];
}

TestRunner.prototype.runTests = function(testInitFile, chdir,
                                         customAssertModule,
                                         timeout,
                                         concurrency, failFast) {
  var self = this, ops = [];
  timeout = timeout || constants.DEFAULT_TEST_TIMEOUT;

  function handleChildTimeout(child, filePath) {
    var resultObj, testFile;

    if (!child.killed) {
      testFile = self._testFilesData[filePath];

      resultObj = {
        'tests': [],
        'timeout': true
      };

      child.kill('SIGTERM');
      self._handleTestResult(filePath, resultObj);
    }

    if (self._failfast) {
      self.forceStop();
    }
  }

  function onBound() {
    self._testReporter.handleTestsStart();

    async.forEachSeries(self._tests, function(filePath, callback) {
      var result, pattern, cwd, child, timeoutId, testFileData;

      if (self._completed || self._forceStopped) {
        callback(new Error('Runner has been stopped'));
        return;
      }

      cwd = process.cwd();
      result = common.getTestFilePathAndPattern(filePath);
      filePath = result[0];
      pattern = result[1];
      filePath = (filePath.charAt(0) !== '/') ? path.join(cwd, filePath) : filePath;
      child = self._spawnTestProcess(filePath, testInitFile, chdir,
                                     customAssertModule, timeout,
                                     concurrency, pattern);

      if (!self._debug) {
        timeoutId = setTimeout(function() {
          handleChildTimeout(child, filePath, callback);
        }, timeout);
      }

      self._testFilesData[filePath] = {
        'child': child,
        'callback': callback,
        'timeout_id': timeoutId,
        'stdout': '',
        'stderr': ''
      };

      testFileData = self._testFilesData[filePath];

      child.stdout.on('data', function(chunk) {
        if (self._realTime) {
          process.stdout.write(chunk.toString());
        }
        else {
          testFileData['stdout'] += chunk;
        }
      });

      child.stderr.on('data', function(chunk) {
        if (self._realTime) {
          process.stderr.write(chunk.toString());
        }
        else {
          testFileData['stderr'] += chunk;
        }
      });

      child.on('exit', function() {
        clearTimeout(timeoutId);
        self._handleTestFileEnd(filePath);
        delete self._testFilesData[filePath];
      });
    },

    function(err) {
      self._handleTestsCompleted();
    });
  }

  function startServer(callback) {
    self._startServer(self._handleConnection.bind(self),
                      onBound);
  }

  function createRunner(callback) {
    self._processRunner = new ProcessRunner(self._dependencies);
    exports.processRunner = self._processRunner;
    callback(null, null);
  }

  function findDependenciesToRun(_, callback) {
    var cwd = process.cwd();
    var testPaths = self._tests.map(function(testPath) {
      testPath = common.getTestFilePathAndPattern(testPath)[0];
      testPath = (testPath.charAt(0) !== '/') ? path.join(cwd, testPath) : testPath;
      return testPath;
    });

    self._processRunner.findDependencies(testPaths, callback);
  }

  function startDependencies(dependencies, callback) {
    self._processRunner.start(dependencies, callback);
  }

  if (self._dependencies) {
    ops.push(createRunner);

    if (self._onlyEssential) {
      ops.push(findDependenciesToRun);
    }

    ops.push(startDependencies);
  }

  ops.push(startServer);

  async.waterfall(ops, function(err) {
    if (err) {
      throw err;
    }
 });
};

TestRunner.prototype._spawnTestProcess = function(filePath,
                                                  testInitFile,
                                                  chdir,
                                                  customAssertModule,
                                                  timeout,
                                                  concurrency,
                                                  pattern) {
  var cwd = process.cwd();
  var libCovDir = (this._coverage) ? path.join(cwd, 'lib-cov') : null;
  var runFilePath = path.join(__dirname, 'run_test_file.js');
  var port = parseInt((Math.random() * (65000 - 2000) + 2000), 10);
  var args = [];

  if (this._debug) {
    args.push('--debug-brk=' + port);
  }

  args = args.concat([runFilePath, filePath, this._socketPath, cwd, libCovDir,
                      this._scopeLeaks, chdir, customAssertModule, testInitFile,
                      timeout, concurrency, pattern]);
  this._testReporter.handleTestFileStart(filePath);
  var child = spawn(process.execPath, args);

  if (this._debug) {
    var debuggerInterface = new DebuggerInterface();
    debuggerInterface.connect(port, 400, function(err, client) {
      // Skip the breakpoint set by debug-brk
      client.reqContinue();
    });
  }

  return child;
};

TestRunner.prototype._addCompletedTest = function(filePath) {
  this._completedTests.push(filePath);
};

TestRunner.prototype._handleTestsCompleted = function() {
  var statusCode;

  if (!this._completed || this._forceStopped) {
    this._completed = true;
    this._stopServer();

    statusCode = this._testReporter.handleTestsComplete();

    if (this._scopeLeaks) {
      this._scopeLeaksReporter.handleTestsComplete();
    }

    if (this._coverage) {
      this._coverageReporter.handleTestsComplete();
    }

    exports.exitCode = statusCode;

    if (this._processRunner) {
      this._processRunner.stop();
    }

    if (this._debug) {
      process.exit();
    }
  }
};

TestRunner.prototype._handleTestResult = function(filePath, resultObj) {
  this._testReporter.handleTestEnd(filePath, resultObj);

  if (this._scopeLeaks) {
    this._scopeLeaksReporter.handleTestEnd(filePath, resultObj);
  }

  if (this._failfast && resultObj['status'] === 'failure') {
    this.forceStop();
  }
};

TestRunner.prototype._handleTestCoverageResult = function(filePath, coverageData) {
  var coverageObj = JSON.parse(coverageData);
  this._coverageReporter.handleTestFileComplete(filePath, coverageObj);
};

TestRunner.prototype._handleTestFileEnd = function(filePath) {
  var testData, coverageData, resultObj, coverageObj, split, testFile;
  var stdout = '';
  var stderr = '';

  testFile = this._testFilesData[filePath];
  if (testFile) {
    stdout = testFile['stdout'];
    stderr = testFile['stderr'];
  }

  this._testReporter.handleTestFileComplete(filePath, stdout, stderr);
  this._addCompletedTest(filePath);

  if (testFile) {
    testFile['callback']();
  }
};

TestRunner.prototype._handleConnection = function(connection) {
  var self = this;
  var data = '';
  var lineProcessor = new testUtil.LineProcessor();
  var dataString, endMarkIndex, testFile, testFileCallback, testFileTimeoutId;

  function onLine(line) {
    var result, filePath, end, resultObj;
    result = testUtil.parseResultLine(line);
    end = result[0];
    filePath = result[1];
    resultObj = result[2];

    if (end) {
      return;
    }

    if (resultObj.hasOwnProperty('coverage')) {
      self._handleTestCoverageResult(filePath, resultObj['coverage']);
    }
    else {
      self._handleTestResult(filePath, resultObj);
    }
  }

  lineProcessor.on('line', onLine);

  function onData(chunk) {
    lineProcessor.appendData(chunk);
    data += chunk;
  }

  connection.on('data', onData);
};

TestRunner.prototype._startServer = function(connectionHandler,
                                             onBound) {
  this._server = net.createServer(connectionHandler);
  this._server.listen(this._socketPath, onBound);
};

TestRunner.prototype.forceStop = function() {
  var testFile, testFileData, child, timeoutId;

  this._forceStopped = true;
  for (testFile in this._testFilesData) {
    if (this._testFilesData.hasOwnProperty(testFile)) {
      testFileData = this._testFilesData[testFile];
      child = testFileData['child'];
      timeoutId = testFileData['timeout_id'];
      clearTimeout(timeoutId);
      child.kill('SIGTERM');
    }
  }
};

TestRunner.prototype._stopServer = function() {
  if (this._server) {
    this._server.close();
    this._server = null;
  }
};

function run(cwd, argv) {
  var customAssertModule, exportedFunctions;
  var runner, testReporter, coverageArgs;
  var socketPath, concurrency, scopeLeaks, runnerArgs;

  if ((argv === undefined) && (cwd instanceof Array)) {
    argv = cwd;
  }

  var p = parser.getParser(constants.WHISKEY_OPTIONS);
  p.banner = 'Usage: whiskey [options] --tests "files"';
  var options = parser.parseArgv(p, argv);

  if (options['coverage-files']) {
      var coverageReporter = options['coverage-reporter'] || constants.DEFAULT_COVERAGE_REPORTER;
      var coverageReporterOptions = {
        'directory': options['coverage-dir'],
        'file': options['coverage-file']
      };

      this._coverageReporter = getReporter('coverage',
                                            coverageReporter,
                                            [],
                                          coverageReporterOptions);
    var coverageObj = coverage.aggregateCoverage(options['coverage-files'].split(','));
    this._coverageReporter.handleTestsComplete(coverageObj);
  }
  else if (options.tests && options.tests.length > 0) {
    options.tests = options.tests.split(' ');

    if (options['debug'] && options.tests.length > 1) {
      throw new Error('--debug option can currently only be used with a single test file');
    }
    else if (options['gen-makefile'] && options['makefile-path']) {
      generateMakefile(options.tests, options['makefile-path'], function(err) {
        if (err) {
          throw err;
        }

        util.puts(sprintf('Makefile has been saved in %s.', options['makefile-path']));
      });

      return;
    }

    customAssertModule = options['custom-assert-module'];
    if (customAssertModule) {
      customAssertModule = (customAssertModule.charAt(0) !== '/') ? path.join(cwd, customAssertModule) : customAssertModule;

      if (path.existsSync(customAssertModule)) {
        customAssertModule = customAssertModule.replace(/$\.js/, '');
      }
      else {
        customAssertModule = null;
      }
    }

    concurrency = options['sequential'] ? 1 : options['concurrency'];
    concurrency = concurrency || constants.DEFAULT_CONCURRENCY;
    scopeLeaks = options['scope-leaks'];

    options['print-stdout'] = (options['quiet']) ? false : true;
    options['print-stderr'] = (options['quiet']) ? false : true;

    if (options['quiet']) {
      logmagic.registerSink('null', function nullLogger() {});
      logmagic.route('__root__', logmagic.INFO, 'null');
    }

    runner = new TestRunner(options);
    runnerArgs = [options['test-init-file'], options['chdir'],
                  customAssertModule, options['timeout'],
                  concurrency, options['fail-fast']];

    if (options['coverage']) {
      var nodePath = process.env['NODE_PATH'];

      if (!nodePath || nodePath.indexOf('lib-cov') === -1) {
        throw new Error('lib-cov is not in NODE_PATH. NODE_PATH environment variable' +
                         ' must contain lib-cov path for the coverage to work.');
      }

      coverageArgs = [ 'jscoverage' ];

      if (options['coverage-encoding']) {
        coverageArgs.push(sprintf('--encoding=%s', options['coverage-encoding']));
      }

      if (options['coverage-exclude']) {
        coverageArgs.push(sprintf('--exclude=%s', options['coverage-exclude']));
      }

      coverageArgs.push(sprintf('lib %s', constants.COVERAGE_PATH));
      coverageArgs = coverageArgs.join(' ');
      exec(sprintf('rm -fr %s ; %s', constants.COVERAGE_PATH, coverageArgs),
           function(err) {
        if (err) {
          if (err.message.match(/jscoverage: not found/i)) {
            err = new Error('jscoverage binary not found. To use test coverage ' +
                            ' you need to install node-jscoverag binary - ' +
                            'https://github.com/visionmedia/node-jscoverage');
          }

          throw err;
        }

        runner.runTests.apply(runner, runnerArgs);
      });
    }
    else {
      runner.runTests.apply(runner, runnerArgs);
    }

  }
  else if (!p._halted) {
    console.log(p.banner);
  }

  process.on('SIGINT', function onSigint() {
    runner.forceStop();
  });

  process.on('exit', function() {
    process.reallyExit(exports.exitCode);
  });
}

exports.run = run;
<|endoftext|>


<commit_before>document.getElementById("id_x").innerHTML = "salut";<commit_msg>am citit senzorii de orientare<commit_after>
window.addEventListener("deviceorientation", on_device_orientation);

function on_device_orientation(e)
{
	document.getElementById("id_z").innerHTML = e.alpha;
	document.getElementById("id_x").innerHTML = e.beta;
	document.getElementById("id_y").innerHTML = e.gamma;
}<|endoftext|>


<commit_before>(function(express, jc, path, http, https, fs){
  'use strict';

  var Run = function(argv, app){
    var configPath = (typeof argv[1] === 'undefined' ?
                      'iora.json' :
                      (path.extname(argv[1]) !== '') ?
                       argv[1] :
                       argv[1] + '.json'
                     );

    var config = jc.read(path.resolve(configPath));

    if (typeof config.http !== 'undefined' && !config.http.disabled) {
      http.Server(app).listen(
        config.http.port || 80,
        config.host || config.http.host || '0.0.0.0',
        (function(listener){
          if (listener) return listener;
        })(config.listener || config.http.listener)
      );
    }

    if (typeof config.https !== 'undefined' && !config.https.disabled) {
      var fetchedData = {};
      for (var key in config.https.options) {
        if (typeof config.https.options[key] === 'string') {
          fs.readFile(config.https.options[key], function(err, data){
            if (!err) {
              fetchedData[key] = data;
            } else {
              fetchedData[key] = config.https.options[key];
            }
          });
        }
      }
      https.Server(fetchedData, app).listen(
        config.https.port || 80,
        config.host || config.https.host || '0.0.0.0',
        (function(listener){
          if (listener) return listener;
        })(config.listener || config.https.listener)
      );
    }
  };

  module.exports = exports = Run;
})(require('express'),
   require('json-config-reader'),
   require('path'),
   require('http'),
   require('https'),
   require('fs'));
<commit_msg>Better server initiation.<commit_after>(function(express, jc, path, protocols){
  'use strict';

  module.exports = exports = function(argv, app){
    var configPath = (function(input){
      if (typeof input === 'undefined') return 'iora.json';
      if (path.extname(input) === '')
        return input + '.json';
      else
        return input;
    })(argv[1]),
    config = jc.read(path.resolve(configPath));

    var initServer = function(protocol){
      var server = config[protocol];
      if (typeof server === 'undefined') return;
      if (typeof server.disabled === 'undefined' || !server.disabled) {
        protocols[protocol].Server(
          (function(isHTTPS){
            if (isHTTPS) {
              return config.options;
            } else {
              return app;
            }
          })(protocol === 'https'),
          protocol === 'https' ? app : undefined
        ).listen(
          server.port || (protocol === 'https' ? 443 : 80),
          config.host || server.host || '0.0.0.0',
          (function(listener){
            if (typeof listener !== 'undefined') return require(path.resolve(listener));
          })(config.listener || server.listener)
        );
      }
    };

    initServer('http');
    initServer('https');

  };
})(require('express'),
   require('json-config-reader'),
   require('path'),
   {
     'http': require('http'),
     'https': require('https'),
   });
<|endoftext|>


<commit_before>SO_DEBUGGING = false;            // for blather(), below.

function translate(text) {
    /* Stub translate function for eventual i8n. */
    return text
};

$(document).ready(function() {
    /* Prevent "event.layerX and event.layerY are broken and deprecated ..."
       WebKit warning message, per:
      http://stackoverflow.com/questions/7825448/webkit-issues-with-event-layerx-and-event-layery
      */
    $.event.props = $.event.props.join('|').replace('layerX|layerY|', '').split('|');
})

/* from http://forthescience.org/blog/2010/11/30/base32-encoding-in-javascript/
   Unrestrained via "WTFPL": http://sam.zoy.org/wtfpl/
*/
function b32encode_trim(s) {
    return b32encode(s).replace(/=+$/, '');
}
function b32encode(s) {
    /* encodes a string s to base32 and returns the encoded string */
    var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    var parts = [];
    var quanta= Math.floor((s.length / 5));
    var leftover = s.length % 5;

    if (leftover != 0) {
        for (var i = 0; i < (5-leftover); i++) { s += '\x00'; }
        quanta += 1;
    }
    for (i = 0; i < quanta; i++) {
        parts.push(alphabet.charAt(s.charCodeAt(i*5) >> 3));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5) & 0x07) << 2)
                                    | (s.charCodeAt(i*5+1) >> 6)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+1) & 0x3F) >> 1) ));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+1) & 0x01) << 4)
                                    | (s.charCodeAt(i*5+2) >> 4)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+2) & 0x0F) << 1)
                                    | (s.charCodeAt(i*5+3) >> 7)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+3) & 0x7F) >> 2)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+3) & 0x03) << 3)
                                    | (s.charCodeAt(i*5+4) >> 5)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+4) & 0x1F) )));
    }
    var replace = 0;
    if (leftover == 1) replace = 6;
    else if (leftover == 2) replace = 4;
    else if (leftover == 3) replace = 3;
    else if (leftover == 4) replace = 1;
    for (i = 0; i < replace; i++) parts.pop();
    for (i = 0; i < replace; i++) parts.push("=");
    return parts.join("");
}

function error_alert(purpose, status_code) {
    /* Post and alert and throw an error for 'purpose' with 'status_code'.
       'purpose' should name the problem, and 'status_code' can be one
       of a few SpiderOak web status codes, or text elaborating the problem. */
    var msg;
    if (typeof status_code === "string") {
        msg = status_code; }
    else if (status_code === 401) {
        msg = 'Unauthorized.'; }
    else if (status_code === 403) {
        msg = 'Incorrect username or password.'; }
    else if (status_code === 404) {
        msg = 'Incorrect ShareID or RoomKey.'; }
    else {
        msg = ('Temporary server failure. Please try again later.'); }
    msg = translate(purpose) + ": " + translate(msg);
    if (typeof status_code !== "string") {
        msg += " (" + status_code + ")"; }
    alert(msg);
    throw new Error(msg);
}

function fragment_quote(id) {
    /* Escape fragment selector chars to avoid jQuery mistaking for CSS
       selector *or other* non-fragment constructs. */
    // The '/' does need to be quoted, surprisingly.
    return id.replace(/(:|\.|\/)/g,'\\$1'); }

function blather(msg, do_alert) {
    if (SO_DEBUGGING) {
        if (do_alert) { alert(msg); }
        else { console.log(msg); }
    }
}
<commit_msg>Add utility functions:<commit_after>SO_DEBUGGING = false;            // for blather(), below.

function translate(text) {
    /* Stub translate function for eventual i8n. */
    return text
};

$(document).ready(function() {
    /* Prevent "event.layerX and event.layerY are broken and deprecated ..."
       WebKit warning message, per:
      http://stackoverflow.com/questions/7825448/webkit-issues-with-event-layerx-and-event-layery
      */
    $.event.props = $.event.props.join('|').replace('layerX|layerY|', '').split('|');
})

/* from http://forthescience.org/blog/2010/11/30/base32-encoding-in-javascript/
   Unrestrained via "WTFPL": http://sam.zoy.org/wtfpl/
*/
function b32encode_trim(s) {
    return b32encode(s).replace(/=+$/, '');
}
function b32encode(s) {
    /* encodes a string s to base32 and returns the encoded string */
    var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    var parts = [];
    var quanta= Math.floor((s.length / 5));
    var leftover = s.length % 5;

    if (leftover != 0) {
        for (var i = 0; i < (5-leftover); i++) { s += '\x00'; }
        quanta += 1;
    }
    for (i = 0; i < quanta; i++) {
        parts.push(alphabet.charAt(s.charCodeAt(i*5) >> 3));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5) & 0x07) << 2)
                                    | (s.charCodeAt(i*5+1) >> 6)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+1) & 0x3F) >> 1) ));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+1) & 0x01) << 4)
                                    | (s.charCodeAt(i*5+2) >> 4)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+2) & 0x0F) << 1)
                                    | (s.charCodeAt(i*5+3) >> 7)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+3) & 0x7F) >> 2)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+3) & 0x03) << 3)
                                    | (s.charCodeAt(i*5+4) >> 5)));
        parts.push(alphabet.charAt( ((s.charCodeAt(i*5+4) & 0x1F) )));
    }
    var replace = 0;
    if (leftover == 1) replace = 6;
    else if (leftover == 2) replace = 4;
    else if (leftover == 3) replace = 3;
    else if (leftover == 4) replace = 1;
    for (i = 0; i < replace; i++) parts.pop();
    for (i = 0; i < replace; i++) parts.push("=");
    return parts.join("");
}

function error_alert(purpose, status_code) {
    /* Post and alert and throw an error for 'purpose' with 'status_code'.
       'purpose' should name the problem, and 'status_code' can be one
       of a few SpiderOak web status codes, or text elaborating the problem. */
    var msg;
    if (typeof status_code === "string") {
        msg = status_code; }
    else if (status_code === 401) {
        msg = 'Unauthorized.'; }
    else if (status_code === 403) {
        msg = 'Incorrect username or password.'; }
    else if (status_code === 404) {
        msg = 'Incorrect ShareID or RoomKey.'; }
    else {
        msg = ('Temporary server failure. Please try again later.'); }
    msg = translate(purpose) + ": " + translate(msg);
    if (typeof status_code !== "string") {
        msg += " (" + status_code + ")"; }
    alert(msg);
    throw new Error(msg);
}

function fragment_quote(id) {
    /* Escape fragment selector chars to avoid jQuery mistaking for CSS
       selector *or other* non-fragment constructs. */
    // The '/' does need to be quoted, surprisingly.
    return id.replace(/(:|\.|\/)/g,'\\$1'); }

function blather(msg, do_alert) {
    if (SO_DEBUGGING) {
        if (do_alert) { alert(msg); }
        else { console.log(msg); }
    }
}

FILE_TYPE_BY_SUFFIX = {txt: "Text",
                      pdf: "Adobe PDF",
                      doc: "MS Word", docx: "MS Word (Open XML)",
                      xls: "MS Excel",
                      ppt: "MS Powerpoint",
                      png: "Image", jpg: "Image", jpeg: "Image", gif: "Image",
                      ico: "MS Icon",
                      svg: "Structured Vector Graphics",
                      ps: "PostScript", eps: "Extended PostScript",
                      avi: "Video", mpg: "Video", mpeg: "Video",
                      mov: "Video",
                      mp3: "MPEG Audio", ogg: "Ogg Vorbis Audio",
                      wav: "Waveform Audio",
                      exe: "Executable",
                      o: "Linkable Object Code",
                      c: "C Source Code",
                      py: "Python Script",
                      pl: "Perl Script",
                      tcl: "TCL Script",
                      js: "Javascript",
                      bat: "MS Batch Script",
                      zip: "Compressed Archive (zip)",
                      gz: "Compressed (gzip)",
                      tgz: "Compressed Archive (gzip)",
                      jar: "Java Archive",
                      htm: "HyperText", html: "HyperText",
                      php: "PHP HyperText",
                      xml: "Extensible Markup Language",
                     }
function classify_file_by_name(name) {
    /* Return a string inferred from a file's name.
       Return an empty string if fail to infer anything.
     */
    var splat = name.split('.');
    var extension = splat[splat.length-1];
    var is_backup = false;
    var classification = FILE_TYPE_BY_SUFFIX[extension.toLowerCase()];
    if (! classification) {
        if (splat.length > 2 && (extension.match(/[0-9]/)
                                 || extension.match("~")
                                 || extension.match("#"))) {
            is_backup = true;
            var extension = splat[splat.length-2];
            classification = FILE_TYPE_BY_SUFFIX[extension];
        }}
    return classification || "";
}
function bytesToSize(bytes) {
    /* Return description of number of 'bytes' */
    /* Adapted from an entry found on:
       http://codeaid.net/javascript/convert-size-in-bytes-to-human-readable-format-(javascript) */
    if (! bytes) { return "empty"; }
    var sizes = [ 'n/a', 'bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
    var i = +Math.floor(Math.log(bytes) / Math.log(1024));
    return  ((bytes / Math.pow(1024, i)).toFixed(i ? 1 : 0)
             + ' ' + sizes[isNaN(bytes) ? 0 : i+1]);
}
<|endoftext|>


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

const isNil = require('lodash.isnil');
const receiver = require('kinvey-code-task-runner');

class Service {
  constructor(callback) {
    this.dataLink = require('./service/dataLink');
    this.businessLogic = require('./service/businessLogic');
    this.moduleGenerator = require('./service/moduleGenerator');

    const taskReceivedCallback = (task, completionCallback) => {
      if (task.taskType === 'dataLink') {
        return this.dataLink.process(task, this.moduleGenerator.generate(task), completionCallback);
      } else if (task.taskType === 'dataLinkDiscovery') {
        return this.dataLink.getServiceObjects(task, completionCallback);
      } else if (task.taskType === 'businessLogic') {
        return this.businessLogic.process(task, this.moduleGenerator.generate(task), completionCallback);
      }
    };

    receiver.start(taskReceivedCallback, (err) => {
      if (!isNil(err)) {
        return callback(new Error(`Could not start task receiver: ${err}`));
      }
      return callback(null, this);
    });
  }
}

function generateService(callback) {
  return new Service((err, service) => {
    callback(err, service);
  });
}

exports.service = generateService;
<commit_msg>BACK-1531 Added options to autodetect sdk type of tcp vs http for running DLCs internally or externally<commit_after>/**
 * Copyright (c) 2016 Kinvey Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

const isNil = require('lodash.isnil');
const receiver = require('kinvey-code-task-runner');

class Service {
  constructor(options = {}, callback) {
    if (!callback && typeof options === 'function') {
      callback = options;
    }

    options.type = process.env.SDK_RECEIVER === 'tcp' ? 'tcp' : 'http';

    this.dataLink = require('./service/dataLink');
    this.businessLogic = require('./service/businessLogic');
    this.moduleGenerator = require('./service/moduleGenerator');

    const taskReceivedCallback = (task, completionCallback) => {
      if (task.taskType === 'dataLink') {
        return this.dataLink.process(task, this.moduleGenerator.generate(task), completionCallback);
      } else if (task.taskType === 'dataLinkDiscovery') {
        return this.dataLink.getServiceObjects(task, completionCallback);
      } else if (task.taskType === 'businessLogic') {
        return this.businessLogic.process(task, this.moduleGenerator.generate(task), completionCallback);
      }
    };

    receiver.start(options, taskReceivedCallback, (err) => {
      if (!isNil(err)) {
        return callback(new Error(`Could not start task receiver: ${err}`));
      }
      return callback(null, this);
    });
  }
}

function generateService(options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = null;
  }

  return new Service(options, (err, service) => {
    callback(err, service);
  });
}

exports.service = generateService;
<|endoftext|>


<commit_before>'use strict';

var Lexer = require('./lexer'),
    Expression = require('./expressions').Expression,
    Equation = require('./equations');

/*
    This parser module uses the shunting yard algorithm to convert input strings
    to algebraic expressions using the algebra.js module.
*/
var Parser = function() {
    this.lexer = new Lexer();
    this.current_token = null;
    this.operator_stack = []; //The operator stack
    this.output = []; //The output stack

    //Operator precendence definitions
    this.prec = {
        'EQUALS' : 1,
        'PLUS' : 2,
        'MINUS' : 2,
        'MULTIPLY': 3,
        'DIVIDE':3,
        'POWER': 4
    };

    //Operator associativity definitions
    this.asso = {
        'PLUS' : 'LEFT',
        'MINUS' : 'LEFT',
        'MULTIPLY': 'LEFT',
        'DIVIDE':'LEFT',
        'POWER': 'RIGHT',
        'EQUALS' : 'RIGHT'
    };
};

/*
    Initializes the parser internals and the lexer.
    The input is then parsed using the shunting yard algorithm
    and the expression tree is constructed and returned as the result
*/
Parser.prototype.parse = function(input) {
    this.operator_stack = []; // empty the operator stack
    this.output = []; //empty the output stack
    //pass the input to the lexer
    this.lexer.input(input);
    //perform shunting yard algorithm
    this.shunting_yard();
    //construct the expression tree
    return this.construct_expression();
};

//Returns the stacks head
Parser.prototype.stack_top = function() {
    return this.operator_stack[this.operator_stack.length - 1];
};

//Moves the stacks head to the output
Parser.prototype.stack_head_to_ouput = function() {
    this.output.push(this.operator_stack.pop());
};

/*
    The shunting yard algorithm according to the description on https://en.wikipedia.org/wiki/Shunting-yard_algorithm. Comments are taken from the description on the site.
    This implementation ignores function and seperator tokens as they are not needed for the
    parser.
*/
Parser.prototype.shunting_yard = function() {
    //Read the first token
    this.current_token = this.lexer.token();
    //While there are tokens to be read:
    while(this.current_token !== null){
        //If the token is a number, then add it to the output queue.
        if(this.current_token.type === 'NUMBER' || this.current_token.type === 'IDENTIFIER'){
            this.output.push(this.current_token);
        //If the token is an operator, o1, then:
        }else if (this.current_token.type ==='OPERATOR'){
            var o1 = this.current_token;
            //while there is an operator token, o2, at the top of the operator stack, and either
            while(this.operator_stack.length > 0){
                var o2 = this.stack_top();
                //o1 is left-associative and its precedence is less than or equal to that of o2, or o1 is right associative, and has precedence less than that of o2,
                if((this.asso[o1.value] === 'LEFT' && this.prec[o1.value] <= this.prec[o2.value])||
                   (this.asso[o1.value] === 'RIGHT' && this.prec[o1.value] < this.prec[o2.value])){
                    //then pop o2 off the operator stack, onto the output queue;
                    this.stack_head_to_ouput();
                }else{
                    break;
                }
            }
            //push o1 onto the operator stack.
            this.operator_stack.push(o1);
        }else {
            //If the token is a left parenthesis (i.e. '('), then push it onto the stack.
            if(this.current_token.value === 'L_PAREN'){
                this.operator_stack.push(this.current_token);
            //If the token is a right parenthesis (i.e. ')'):
            }else{
                //Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
                while(this.stack_top() !== undefined){
                    if(this.stack_top().value === 'L_PAREN'){
                        break;
                    }else{
                        this.stack_head_to_ouput();
                    }
                }
                //Pop the left parenthesis from the stack, but not onto the output queue.
                var head = this.operator_stack.pop();
                //If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
                if(head === undefined){
                    throw new Error('Unbalanced Parenthesis');
                }
            }
        }
        this.current_token = this.lexer.token();
    }
    //When there are no more tokens to read:
    //While there are still operator tokens in the stack:
    while(this.operator_stack.length > 0){
        //If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
        if(this.stack_top().type === 'PAREN'){
            throw new Error('Unbalanced Parenthesis');
        }else{
            //Pop the operator onto the output queue.
            this.stack_head_to_ouput();
        }
    }
    //Exit.
};

//Converts the base types NUMBER and IDENTIFIER to an Expression.
Parser.prototype.convert_for_application = function(operand) {
    var result = operand;
    if(operand.type !== undefined){
        if(parseInt(operand.value) == operand.value){
            result = new Expression(parseInt(operand.value));
        }else{
            result = new Expression(operand.value);
        }
    }
    return result;
};

/*
    Applies the specified operator to the specified operands.
    op is always a token of type OPERATOR,
    operands lhs and rhs can be tokens of type NUMBER or IDENTIFIER
    or Expression objects
*/
Parser.prototype.apply_operator = function(op, lhs, rhs) {
    var result;
    //Perform operand type conversions if needed
    lhs = this.convert_for_application(lhs);
    rhs = this.convert_for_application(rhs);

    //Apply the operator
    switch(op.value){
        case 'PLUS': result = lhs.add(rhs);break;
        case 'MINUS': result = lhs.subtract(rhs);break;
        case 'MULTIPLY': result = lhs.multiply(rhs);break;
        case 'DIVIDE':
            /*
                Division is a bit special as the algebra.js module
                only allows division by integers or Fractions, but not
                Expressions. Therefore the rhs operand is always converted to an
                integer.
            */
            result = lhs.divide(parseInt(rhs.toString()));
            break;
        // Power also doesn't accept expressions as rhs operand
        case 'POWER': result = lhs.pow(parseInt(rhs.toString()));break;
        case 'EQUALS' : result = new Equation(lhs,rhs);break;
    }
    return result;
};

/*
    Recursively build the expression tree.
*/
Parser.prototype.construct_expression = function() {
    //Read the stack head
    var head = this.output.pop();
    //If its an operator, recursively construct the operands and apply the operator to construct the node
    if(head.type === 'OPERATOR'){
        var rhs = this.construct_expression();
        var lhs = this.construct_expression();
        return this.apply_operator(head, lhs,rhs);
    }else{
        //If it is not an operator, it can only be a number or a variable, which are leaves in the tree
        return head;
    }
};

module.exports = Parser;<commit_msg>Moved token conversion from operator application to tree construction<commit_after>'use strict';

var Lexer = require('./lexer'),
    Expression = require('./expressions').Expression,
    Equation = require('./equations');

/*
    This parser module uses the shunting yard algorithm to convert input strings
    to algebraic expressions using the algebra.js module.
*/
var Parser = function() {
    this.lexer = new Lexer();
    this.current_token = null;
    this.operator_stack = []; //The operator stack
    this.output = []; //The output stack

    //Operator precendence definitions
    this.prec = {
        'EQUALS' : 1,
        'PLUS' : 2,
        'MINUS' : 2,
        'MULTIPLY': 3,
        'DIVIDE':3,
        'POWER': 4
    };

    //Operator associativity definitions
    this.asso = {
        'PLUS' : 'LEFT',
        'MINUS' : 'LEFT',
        'MULTIPLY': 'LEFT',
        'DIVIDE':'LEFT',
        'POWER': 'RIGHT',
        'EQUALS' : 'RIGHT'
    };
};

/*
    Initializes the parser internals and the lexer.
    The input is then parsed using the shunting yard algorithm
    and the expression tree is constructed and returned as the result
*/
Parser.prototype.parse = function(input) {
    this.operator_stack = []; // empty the operator stack
    this.output = []; //empty the output stack
    //pass the input to the lexer
    this.lexer.input(input);
    //perform shunting yard algorithm
    this.shunting_yard();
    //construct the expression tree
    return this.construct_expression();
};

//Returns the stacks head
Parser.prototype.stack_top = function() {
    return this.operator_stack[this.operator_stack.length - 1];
};

//Moves the stacks head to the output
Parser.prototype.stack_head_to_ouput = function() {
    this.output.push(this.operator_stack.pop());
};

/*
    The shunting yard algorithm according to the description on https://en.wikipedia.org/wiki/Shunting-yard_algorithm. Comments are taken from the description on the site.
    This implementation ignores function and seperator tokens as they are not needed for the
    parser.
*/
Parser.prototype.shunting_yard = function() {
    //Read the first token
    this.current_token = this.lexer.token();
    //While there are tokens to be read:
    while(this.current_token !== null){
        //If the token is a number, then add it to the output queue.
        if(this.current_token.type === 'NUMBER' || this.current_token.type === 'IDENTIFIER'){
            this.output.push(this.current_token);
        //If the token is an operator, o1, then:
        }else if (this.current_token.type ==='OPERATOR'){
            var o1 = this.current_token;
            //while there is an operator token, o2, at the top of the operator stack, and either
            while(this.operator_stack.length > 0){
                var o2 = this.stack_top();
                //o1 is left-associative and its precedence is less than or equal to that of o2, or o1 is right associative, and has precedence less than that of o2,
                if((this.asso[o1.value] === 'LEFT' && this.prec[o1.value] <= this.prec[o2.value])||
                   (this.asso[o1.value] === 'RIGHT' && this.prec[o1.value] < this.prec[o2.value])){
                    //then pop o2 off the operator stack, onto the output queue;
                    this.stack_head_to_ouput();
                }else{
                    break;
                }
            }
            //push o1 onto the operator stack.
            this.operator_stack.push(o1);
        }else {
            //If the token is a left parenthesis (i.e. '('), then push it onto the stack.
            if(this.current_token.value === 'L_PAREN'){
                this.operator_stack.push(this.current_token);
            //If the token is a right parenthesis (i.e. ')'):
            }else{
                //Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
                while(this.stack_top() !== undefined){
                    if(this.stack_top().value === 'L_PAREN'){
                        break;
                    }else{
                        this.stack_head_to_ouput();
                    }
                }
                //Pop the left parenthesis from the stack, but not onto the output queue.
                var head = this.operator_stack.pop();
                //If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
                if(head === undefined){
                    throw new Error('Unbalanced Parenthesis');
                }
            }
        }
        this.current_token = this.lexer.token();
    }
    //When there are no more tokens to read:
    //While there are still operator tokens in the stack:
    while(this.operator_stack.length > 0){
        //If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
        if(this.stack_top().type === 'PAREN'){
            throw new Error('Unbalanced Parenthesis');
        }else{
            //Pop the operator onto the output queue.
            this.stack_head_to_ouput();
        }
    }
    //Exit.
};

//Converts the base types NUMBER and IDENTIFIER to an Expression.
Parser.prototype.convert_for_application = function(operand) {
    var result = operand;
    if(operand.type !== undefined){
        if(parseInt(operand.value) == operand.value){
            result = new Expression(parseInt(operand.value));
        }else{
            result = new Expression(operand.value);
        }
    }
    return result;
};

/*
    Applies the specified operator to the specified operands.
    op is always a token of type OPERATOR,
    operands lhs and rhs can be tokens of type NUMBER or IDENTIFIER
    or Expression objects
*/
Parser.prototype.apply_operator = function(op, lhs, rhs) {
    var result;

    //Apply the operator
    switch(op.value){
        case 'PLUS': result = lhs.add(rhs);break;
        case 'MINUS': result = lhs.subtract(rhs);break;
        case 'MULTIPLY': result = lhs.multiply(rhs);break;
        case 'DIVIDE':
            /*
                Division is a bit special as the algebra.js module
                only allows division by integers or Fractions, but not
                Expressions. Therefore the rhs operand is always converted to an
                integer.
            */
            result = lhs.divide(parseInt(rhs.toString()));
            break;
        // Power also doesn't accept expressions as rhs operand
        case 'POWER': result = lhs.pow(parseInt(rhs.toString()));break;
        case 'EQUALS' : result = new Equation(lhs,rhs);break;
    }
    return result;
};

/*
    Recursively build the expression tree.
*/
Parser.prototype.construct_expression = function() {
    //Read the stack head
    var head = this.output.pop();
    //If its an operator, recursively construct the operands and apply the operator to construct the node
    if(head.type === 'OPERATOR'){
        var rhs = this.construct_expression();
        var lhs = this.construct_expression();
        return this.apply_operator(head, lhs,rhs);
    }else{
        //If it is not an operator, it can only be a number or a variable, which are leaves in the tree
        return this.convert_for_application(head);
    }
};

module.exports = Parser;<|endoftext|>


<commit_before>const { extend } = require('./utils')

const DOCUMENT_TYPE_NODE = '#documentType'
const TEXT_NODE = '#text'
const DIV_NODE = 'div'
const COMMENT_NODE = '#comment'
const COMMENT_NODE_PUG = '//'

const hasSingleTextNodeChild = node => {
  return (
    node.childNodes &&
    node.childNodes.length === 1 &&
    node.childNodes[0].nodeName === TEXT_NODE
  )
}

const defaultOptions = {
  useTabs: false,
}

class Parser {
  constructor(root, options = {}) {
    // Merge default options with supplied
    const { useTabs } = extend(defaultOptions, options)

    this.root = root
    this.pug = ''

    // Tabs or spaces?
    this.indentType = useTabs ? '\t' : '  '
  }

  getIndent(level = 0) {
    return this.indentType.repeat(level)
  }

  parse() {
    const walk = this.walk(this.root.childNodes, 0)
    let it

    do {
      it = walk.next()
    } while (!it.done)

    return this.pug.substring(1)
  }

  /**
   * DOM tree traversal
   * Depth-first search (pre-order)
   *
   * @param {DOM} tree - DOM tree or Node
   * @param {Number} level - Current tree level
   */
  *walk(tree, level) {
    if (!tree) {
      return
    }

    for (let i = 0; i < tree.length; i++) {
      const node = tree[i]

      const newline = this.parseNode(node, level)
      if (newline) {
        this.pug += `\n${newline}`
      }

      if (
        node.childNodes &&
        node.childNodes.length > 0 &&
        !hasSingleTextNodeChild(node)
      ) {
        yield* this.walk(node.childNodes, level + 1)
      }
    }
  }

  /*
   * Returns a Pug node name with all attributes set in parentheses.
   */
  static getNodeWithAttributes(node) {
    const { tagName, attrs } = node
    const attributes = []
    let pugNode = tagName

    if (!attrs) {
      return pugNode
    }

    // Add CSS selectors to pug node and append any element attributes to it
    for (const attr of attrs) {
      const { name, value } = attr

      // Remove div tag if a selector is present (shorthand)
      // e.g. div#form() -> #form()
      const hasSelector = name === 'id' || name === 'class'
      if (tagName === DIV_NODE && hasSelector) {
        pugNode = pugNode.replace(DIV_NODE, '')
      }

      switch (name) {
        case 'id':
          pugNode += `#${value}`
          break
        case 'class':
          pugNode += `.${value.split(' ').join('.')}`
          break
        default: {
          // Add escaped single quotes (\') to attribute values
          const val = value.replace(/'/g, "\\'")
          if (!val) {
            attributes.push(name)
          } else {
            attributes.push(`${name}='${val}'`)
          }
          break
        }
      }
    }

    if (attributes.length) {
      pugNode += `(${attributes.join(', ')})`
    }

    return pugNode
  }

  /**
   * formatPugNode applies the correct indent for the current line,
   * and formats the value as either as a single or multiline string.
   *
   * @param {String} node - The pug node (e.g. header(class='foo'))
   * @param {String} value - The node's value
   * @param {Number} level - Current tree level to generate indent
   * @param {String} blockChar - The character used to denote a multiline value
   */
  formatPugNode(node, value = '', level, blockChar = '.') {
    const indent = this.getIndent(level)
    const result = `${indent}${node}`

    const lines = value.split('\n')

    if (lines.length > 1) {
      const indentChild = this.getIndent(level + 1)
      const multiline = lines.map(line => `${indentChild}${line}`).join('\n')
      return `${result}${blockChar}\n${multiline}`
    }

    return value.length ? `${result} ${value}` : result
  }

  /**
   * createDoctype formats a #documentType element
   */
  createDoctype(node, level) {
    const indent = this.getIndent(level)
    return `${indent}doctype html`
  }

  /**
   * createComment formats a #comment element.
   *
   * Block comments in Pug don't require the dot '.' character.
   */
  createComment(node, level) {
    return this.formatPugNode(COMMENT_NODE_PUG, node.data, level, '')
  }

  /**
   * createText formats a #text element.
   *
   * A #text element containing only line breaks (\n) indicates
   * unnecessary whitespace between elements that should be removed.
   *
   * Actual text in a single #text element has no significant
   * whitespace and should be treated as inline text.
   */
  createText(node, level) {
    const { value } = node
    const indent = this.getIndent(level)

    // Omit line breaks between HTML elements
    if (/^[\n]+$/.test(value)) {
      return false
    }

    return `${indent}| ${value}`
  }

  /**
   * createElement formats a generic HTML element.
   */
  createElement(node, level) {
    const pugNode = Parser.getNodeWithAttributes(node)

    if (hasSingleTextNodeChild(node)) {
      const value = node.childNodes[0].value
      return this.formatPugNode(pugNode, value, level)
    }

    return this.formatPugNode(pugNode, node.value, level)
  }

  parseNode(node, level) {
    const { nodeName } = node

    switch (nodeName) {
      case DOCUMENT_TYPE_NODE:
        return this.createDoctype(node, level)

      case COMMENT_NODE:
        return this.createComment(node, level)

      case TEXT_NODE:
        return this.createText(node, level)

      default:
        return this.createElement(node, level)
    }
  }
}

module.exports = Parser
<commit_msg>Sometimes a ternary operator isn't so bad<commit_after>const { extend } = require('./utils')

const DOCUMENT_TYPE_NODE = '#documentType'
const TEXT_NODE = '#text'
const DIV_NODE = 'div'
const COMMENT_NODE = '#comment'
const COMMENT_NODE_PUG = '//'

const hasSingleTextNodeChild = node => {
  return (
    node.childNodes &&
    node.childNodes.length === 1 &&
    node.childNodes[0].nodeName === TEXT_NODE
  )
}

const defaultOptions = {
  useTabs: false,
}

class Parser {
  constructor(root, options = {}) {
    // Merge default options with supplied
    const { useTabs } = extend(defaultOptions, options)

    this.root = root
    this.pug = ''

    // Tabs or spaces?
    this.indentType = useTabs ? '\t' : '  '
  }

  getIndent(level = 0) {
    return this.indentType.repeat(level)
  }

  parse() {
    const walk = this.walk(this.root.childNodes, 0)
    let it

    do {
      it = walk.next()
    } while (!it.done)

    return this.pug.substring(1)
  }

  /**
   * DOM tree traversal
   * Depth-first search (pre-order)
   *
   * @param {DOM} tree - DOM tree or Node
   * @param {Number} level - Current tree level
   */
  *walk(tree, level) {
    if (!tree) {
      return
    }

    for (let i = 0; i < tree.length; i++) {
      const node = tree[i]

      const newline = this.parseNode(node, level)
      if (newline) {
        this.pug += `\n${newline}`
      }

      if (
        node.childNodes &&
        node.childNodes.length > 0 &&
        !hasSingleTextNodeChild(node)
      ) {
        yield* this.walk(node.childNodes, level + 1)
      }
    }
  }

  /*
   * Returns a Pug node name with all attributes set in parentheses.
   */
  static getNodeWithAttributes(node) {
    const { tagName, attrs } = node
    const attributes = []
    let pugNode = tagName

    if (!attrs) {
      return pugNode
    }

    // Add CSS selectors to pug node and append any element attributes to it
    for (const attr of attrs) {
      const { name, value } = attr

      // Remove div tag if a selector is present (shorthand)
      // e.g. div#form() -> #form()
      const hasSelector = name === 'id' || name === 'class'
      if (tagName === DIV_NODE && hasSelector) {
        pugNode = pugNode.replace(DIV_NODE, '')
      }

      switch (name) {
        case 'id':
          pugNode += `#${value}`
          break
        case 'class':
          pugNode += `.${value.split(' ').join('.')}`
          break
        default: {
          // Add escaped single quotes (\') to attribute values
          const val = value.replace(/'/g, "\\'")
          attributes.push(val ? `${name}='${val}'` : name)
          break
        }
      }
    }

    if (attributes.length) {
      pugNode += `(${attributes.join(', ')})`
    }

    return pugNode
  }

  /**
   * formatPugNode applies the correct indent for the current line,
   * and formats the value as either as a single or multiline string.
   *
   * @param {String} node - The pug node (e.g. header(class='foo'))
   * @param {String} value - The node's value
   * @param {Number} level - Current tree level to generate indent
   * @param {String} blockChar - The character used to denote a multiline value
   */
  formatPugNode(node, value = '', level, blockChar = '.') {
    const indent = this.getIndent(level)
    const result = `${indent}${node}`

    const lines = value.split('\n')

    if (lines.length > 1) {
      const indentChild = this.getIndent(level + 1)
      const multiline = lines.map(line => `${indentChild}${line}`).join('\n')
      return `${result}${blockChar}\n${multiline}`
    }

    return value.length ? `${result} ${value}` : result
  }

  /**
   * createDoctype formats a #documentType element
   */
  createDoctype(node, level) {
    const indent = this.getIndent(level)
    return `${indent}doctype html`
  }

  /**
   * createComment formats a #comment element.
   *
   * Block comments in Pug don't require the dot '.' character.
   */
  createComment(node, level) {
    return this.formatPugNode(COMMENT_NODE_PUG, node.data, level, '')
  }

  /**
   * createText formats a #text element.
   *
   * A #text element containing only line breaks (\n) indicates
   * unnecessary whitespace between elements that should be removed.
   *
   * Actual text in a single #text element has no significant
   * whitespace and should be treated as inline text.
   */
  createText(node, level) {
    const { value } = node
    const indent = this.getIndent(level)

    // Omit line breaks between HTML elements
    if (/^[\n]+$/.test(value)) {
      return false
    }

    return `${indent}| ${value}`
  }

  /**
   * createElement formats a generic HTML element.
   */
  createElement(node, level) {
    const pugNode = Parser.getNodeWithAttributes(node)

    const value = hasSingleTextNodeChild(node)
      ? node.childNodes[0].value
      : node.value

    return this.formatPugNode(pugNode, value, level)
  }

  parseNode(node, level) {
    const { nodeName } = node

    switch (nodeName) {
      case DOCUMENT_TYPE_NODE:
        return this.createDoctype(node, level)

      case COMMENT_NODE:
        return this.createComment(node, level)

      case TEXT_NODE:
        return this.createText(node, level)

      default:
        return this.createElement(node, level)
    }
  }
}

module.exports = Parser
<|endoftext|>