<commit_before>'use strict';

var byline        = require('byline'),
    chalk         = require('chalk'),
    eventStream   = require('event-stream'),
    fs            = require('fs'),
    isBinaryFile  = require('isbinaryfile'),
    minimatch     = require('minimatch'),
    readdirp      = require('readdirp');

var ignoredDirectories  = ['node_modules/**', '.git/**', '.hg/**'],
    filesToScan         = ['**/*.js', 'Makefile', '**/*.sh'],
    scanPath            = process.cwd(),
    fileEncoding        = 'utf8',
    lineLengthLimit     = 1000,
    skipChecks          = [],
    messageChecks       = {
      note: {
        regex:    /[\/\/][\/\*]\s*NOTE\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✐ NOTE',
        colorer:  chalk.green
      },
      optimize: {
        regex:    /[\/\/][\/\*]\s*OPTIMIZE\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ↻ OPTIMIZE',
        colorer:  chalk.blue
      },
      todo: {
        regex:    /[\/\/][\/\*]\s*TODO\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✓ TODO',
        colorer:  chalk.magenta
      },
      hack: {
        regex:    /[\/\/][\/\*]\s*HACK\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✄ HACK',
        colorer:  chalk.yellow
      },
      xxx: {
        regex:    /[\/\/][\/\*]\s*XXX\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✗ XXX',
        colorer:  chalk.black.bgYellow
      },
      fixme: {
        regex:    /[\/\/][\/\*]\s*FIXME\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ☠ FIXME',
        colorer:  chalk.red
      },
      bug: {
        regex:    /[\/\/][\/\*]\s*BUG\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ☢ BUG',
        colorer:  chalk.white.bgRed
      }
    };

/**
 * Determines whether or not to let the file through. by ensuring that the
 * file name does not match one of the excluded directories, and ensuring it
 * matches one of the file filters.
 *
 * It will also ensure that even if a binary file matches the filter patterns,
 * it will not let it through as searching binary file contents for string
 * matches will never make sense.
 *
 * @param   {String} fileInformation
 *
 * @return  {Boolean}
 */
// TODO: This could be simpler using minimatch negation patterns in one set, instead disparate ones for files and directories.
function fileFilterer (fileInformation) {
  var shouldIgnoreDirectory = false,
      shouldIgnoreFile      = true,
      letTheFileThrough;

  ignoredDirectories.forEach(function (directoryPattern) {
    if (shouldIgnoreDirectory) return;
    shouldIgnoreDirectory = minimatch(fileInformation.path, directoryPattern, { dot: true });
  });

  if (!shouldIgnoreDirectory) {
    filesToScan.forEach(function (filePattern) {
      if (!shouldIgnoreFile) return;

      shouldIgnoreFile = !(minimatch(fileInformation.name, filePattern));
    });
  }

  letTheFileThrough = !(shouldIgnoreDirectory || (!shouldIgnoreDirectory && shouldIgnoreFile));

  // Never let binary files through, searching them for comments will make no sense...
  if (letTheFileThrough && isBinaryFile.sync(fileInformation.fullPath)) {
    letTheFileThrough = false;
  }

  return letTheFileThrough;
}

/**
 * Takes a line of a file and the line number, and returns an array of all of
 * the messages found in that line. Can return multiple messages per line, for
 * example, if a message was annotated with more than one type. EG: FIXME TODO
 *
 * Each message in the array will have a label, a line_number, a colorer, and a
 * message. Will also include an author property if one is found on the
 * message.
 *
 * @param   {String} lineString The
 * @param   {Number} lineNumber
 *
 * @return  {Array}
 */
function retrieveMessagesFromLine (lineString, lineNumber) {
  var messageFormat = {
    author:       null,
    message:      null,
    label:        null,
    colorer:      null,
    line_number:  lineNumber
  },
  messages = [];

  Object.keys(messageChecks).forEach(function (checkName) {
    var matchResults  = lineString.match(messageChecks[checkName].regex),
        checker       = messageChecks[checkName],
        thisMessage;

    if (matchResults && matchResults.length) {
      thisMessage = JSON.parse(JSON.stringify(messageFormat)); // Clone the above structure.

      thisMessage.label   = checker.label;
      thisMessage.colorer = checker.colorer;

      if (matchResults[1] && matchResults[1].length) {
        thisMessage.author = matchResults[1].trim();
      }

      if (matchResults[2] && matchResults[2].length) {
        thisMessage.message = matchResults[2].trim();
      }
    }

    if (thisMessage) messages.push(thisMessage);
  });

  return messages;
}

/**
 * Takes a line number and returns a padded string matching the total number of
 * characters in totalLinesNumber. EG: A lineNumber of 12 and a
 * totalLinesNumber of 1323 will return the string '  12'.
 *
 * @param   {Number} lineNumber
 * @param   {Number} totalLinesNumber
 *
 * @return  {String}
 */
function getPaddedLineNumber (lineNumber, totalLinesNumber) {
  var paddedLineNumberString = '' + lineNumber;

  while (paddedLineNumberString.length < ('' + totalLinesNumber).length) {
    paddedLineNumberString = ' ' + paddedLineNumberString;
  }

  return paddedLineNumberString;
}

/**
 * Takes an individual message object, as output from retrieveMessagesFromLine
 * and formats it for output.
 *
 * @param     {Object}    individualMessage
 * @property  {String}    individualMessage.author
 * @property  {String}    individualMessage.message
 * @property  {String}    individualMessage.label
 * @property  {Function}  individualMessage.colorer
 * @property  {Number}    individualMessage.line_number
 * @param     {Number}    totalNumberOfLines
 *
 * @return    {String}    The formatted message string.
 */
function formatMessageOutput (individualMessage, totalNumberOfLines) {
  var paddedLineNumber = getPaddedLineNumber(individualMessage.line_number, totalNumberOfLines),
      finalLabelString,
      finalNoteString;

  finalNoteString = chalk.gray('  [Line ' + paddedLineNumber + '] ');

  finalLabelString = individualMessage.label;

  if (individualMessage.author) {
    finalLabelString += (' from ' + individualMessage.author + ': ');
  } else {
    finalLabelString += ': ';
  }

  finalLabelString = chalk.bold(individualMessage.colorer(finalLabelString));

  finalNoteString += finalLabelString;

  if (individualMessage.message && individualMessage.message.length) {
    finalNoteString += individualMessage.colorer(individualMessage.message);
  } else {
    finalNoteString += chalk.grey('[[no message to display]]');
  }

  return finalNoteString;
}

/**
 * Formatter function for the file name. Takes a file path, and the total
 * number of messages in the file, and formats this information for display as
 * the heading for the file messages.
 *
 * @param   {String} filePath
 * @param   {Number} numberOfMessages
 *
 * @return  {String}
 */
function formatFilePathOutput (filePath, numberOfMessages) {
  var filePathOutput = chalk.bold.white('\n* ' + filePath + ' '),
      messagesString = 'messages';

  if (numberOfMessages === 1) {
    messagesString = 'message';
  }

  filePathOutput += chalk.grey('[' + numberOfMessages + ' ' + messagesString + ']:');

  return filePathOutput;
}

/**
 * Takes an object representing the messages and other meta-info for the file
 * and calls off to the formatters for the messages, as well as logs the
 * formatted result.
 *
 * @param     {Object}  messagesInfo
 * @property  {String}  messagesInfo.path The file path
 * @property  {Array}   messagesInfo.messages All of the message objects for the file.
 * @property  {String}  messagesInfo.total_lines Total number of lines in the file.
 */
function logMessages (messagesInfo) {
  if (messagesInfo.messages.length) {
    console.log(formatFilePathOutput(messagesInfo.path, messagesInfo.messages.length));

    messagesInfo.messages.forEach(function (message) {
      var formattedMessage = formatMessageOutput(message, messagesInfo.total_lines);

      console.log(formattedMessage);
    });
  }
}

/**
 * Reads through the configured path scans the matching files for messages.
 */
function scanAndProcessMessages () {
  var stream = readdirp({
    root:       scanPath,
    fileFilter: fileFilterer
  });

  // Remove skipped checks for our mapping
  skipChecks.forEach(function (checkName) {
    delete messageChecks[checkName];
  });

  // TODO: Actually do something meaningful/useful with these handlers.
  stream
    .on('warn', console.warn)
    .on('error', console.error);

  stream
    .pipe(eventStream.map(function (fileInformation, callback) {
      var input                 = fs.createReadStream(fileInformation.fullPath, { encoding: fileEncoding }),
          // lineStream            = byline.createStream(input, { encoding: fileEncoding }),
          fileMessages          = { path: null, total_lines: 0, messages: [] },
          currentFileLineNumber = 1;

      fileMessages.path = fileInformation.path;

      input.pipe( eventStream.split() )
        .pipe( eventStream.map( function( fileLineString, cb ){
          var messages,
              lengthError;

          if (fileLineString.length < lineLengthLimit) {
            messages = retrieveMessagesFromLine(fileLineString, currentFileLineNumber);

            messages.forEach(function (message) {
              fileMessages.messages.push(message);
            });
          } else if (skipChecks.indexOf('line') === -1){
            lengthError = 'Fixme is skipping this line because its length is ' +
                          'greater than the maximum line-length of ' +
                          lineLengthLimit + '.';

            fileMessages.messages.push({
              message:      lengthError,
              line_number:  currentFileLineNumber,
              label:        ' ⚠ SKIPPING CHECK',
              colorer:      chalk.underline.red
            });
          }

          currentFileLineNumber += 1;
        })
      );

      input.on('end', function () {
        fileMessages.total_lines = currentFileLineNumber;

        logMessages(fileMessages);
      });

      callback();
    }));
}

/**
 * Takes an options object and over-writes the defaults, then calls off to the
 * scanner to scan the files for messages.
 *
 * @param     {Object}  options
 * @property  {String}  options.path                The base directory to recursively scan for messages. Defaults to process.cwd()
 * @property  {Array}   options.ignored_directories An array of minimatch glob patterns for directories to ignore scanning entirely.
 * @property  {Array}   options.file_patterns       An array of minimatch glob patterns for files to scan for messages.
 * @property  {String}  options.file_encoding       The encoding the files scanned will be opened with, defaults to 'utf8'.
 * @property  {Number}  options.line_length_limit   The number of characters a line can be before it is ignored. Defaults to 1000.
 * @property  {Array}   options.skip                An array of names of checks to skip.
 */
 // TODO(johnp): Allow custom messageChecks to be added via options.
function parseUserOptionsAndScan (options) {
  if (options) {
    if (options.path) {
      scanPath = options.path;
    }

    if (options.ignored_directories &&
        Array.isArray(options.ignored_directories) &&
        options.ignored_directories.length) {
      ignoredDirectories = options.ignored_directories;
    }

    if (options.file_patterns &&
        Array.isArray(options.file_patterns) &&
        options.file_patterns.length) {
      filesToScan = options.file_patterns;
    }

    if (options.file_encoding) {
      fileEncoding = options.file_encoding;
    }

    if (options.line_length_limit) {
      lineLengthLimit = options.line_length_limit;
    }

    if (options.skip &&
        Array.isArray(options.skip) &&
        options.skip.length) {
      skipChecks = options.skip;
    }
  }

  scanAndProcessMessages();
}

// TODO: Create a github issue
// TODO: Check if issue has been already created
// TODO: add option: Labels => github lables
// TODO: how to deal with assigning issues? TODO @me => issue assigned to me;
// TODO @aymen => get list of contributors and match aymen with one of the contributors
// TODO: add option: if issue is closed => remove annotated line

module.exports = parseUserOptionsAndScan;
<commit_msg>Update list of options<commit_after>'use strict';

var byline        = require('byline'),
    chalk         = require('chalk'),
    eventStream   = require('event-stream'),
    fs            = require('fs'),
    isBinaryFile  = require('isbinaryfile'),
    minimatch     = require('minimatch'),
    readdirp      = require('readdirp');

var ignoredDirectories  = ['node_modules/**', '.git/**', '.hg/**'],
    filesToScan         = ['**/*.js', 'Makefile', '**/*.sh'],
    scanPath            = process.cwd(),
    fileEncoding        = 'utf8',
    lineLengthLimit     = 1000,
    skipChecks          = [],
    messageChecks       = {
      note: {
        regex:    /[\/\/][\/\*]\s*NOTE\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✐ NOTE',
        colorer:  chalk.green
      },
      optimize: {
        regex:    /[\/\/][\/\*]\s*OPTIMIZE\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ↻ OPTIMIZE',
        colorer:  chalk.blue
      },
      todo: {
        regex:    /[\/\/][\/\*]\s*TODO\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✓ TODO',
        colorer:  chalk.magenta
      },
      hack: {
        regex:    /[\/\/][\/\*]\s*HACK\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✄ HACK',
        colorer:  chalk.yellow
      },
      xxx: {
        regex:    /[\/\/][\/\*]\s*XXX\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ✗ XXX',
        colorer:  chalk.black.bgYellow
      },
      fixme: {
        regex:    /[\/\/][\/\*]\s*FIXME\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ☠ FIXME',
        colorer:  chalk.red
      },
      bug: {
        regex:    /[\/\/][\/\*]\s*BUG\s*(?:\(([^:]*)\))*\s*:?\s*(.*)/i,
        label:    ' ☢ BUG',
        colorer:  chalk.white.bgRed
      }
    };

/**
 * Determines whether or not to let the file through. by ensuring that the
 * file name does not match one of the excluded directories, and ensuring it
 * matches one of the file filters.
 *
 * It will also ensure that even if a binary file matches the filter patterns,
 * it will not let it through as searching binary file contents for string
 * matches will never make sense.
 *
 * @param   {String} fileInformation
 *
 * @return  {Boolean}
 */
// TODO: This could be simpler using minimatch negation patterns in one set, instead disparate ones for files and directories.
function fileFilterer (fileInformation) {
  var shouldIgnoreDirectory = false,
      shouldIgnoreFile      = true,
      letTheFileThrough;

  ignoredDirectories.forEach(function (directoryPattern) {
    if (shouldIgnoreDirectory) return;
    shouldIgnoreDirectory = minimatch(fileInformation.path, directoryPattern, { dot: true });
  });

  if (!shouldIgnoreDirectory) {
    filesToScan.forEach(function (filePattern) {
      if (!shouldIgnoreFile) return;

      shouldIgnoreFile = !(minimatch(fileInformation.name, filePattern));
    });
  }

  letTheFileThrough = !(shouldIgnoreDirectory || (!shouldIgnoreDirectory && shouldIgnoreFile));

  // Never let binary files through, searching them for comments will make no sense...
  if (letTheFileThrough && isBinaryFile.sync(fileInformation.fullPath)) {
    letTheFileThrough = false;
  }

  return letTheFileThrough;
}

/**
 * Takes a line of a file and the line number, and returns an array of all of
 * the messages found in that line. Can return multiple messages per line, for
 * example, if a message was annotated with more than one type. EG: FIXME TODO
 *
 * Each message in the array will have a label, a line_number, a colorer, and a
 * message. Will also include an author property if one is found on the
 * message.
 *
 * @param   {String} lineString The
 * @param   {Number} lineNumber
 *
 * @return  {Array}
 */
function retrieveMessagesFromLine (lineString, lineNumber) {
  var messageFormat = {
    author:       null,
    message:      null,
    label:        null,
    colorer:      null,
    line_number:  lineNumber
  },
  messages = [];

  Object.keys(messageChecks).forEach(function (checkName) {
    var matchResults  = lineString.match(messageChecks[checkName].regex),
        checker       = messageChecks[checkName],
        thisMessage;

    if (matchResults && matchResults.length) {
      thisMessage = JSON.parse(JSON.stringify(messageFormat)); // Clone the above structure.

      thisMessage.label   = checker.label;
      thisMessage.colorer = checker.colorer;

      if (matchResults[1] && matchResults[1].length) {
        thisMessage.author = matchResults[1].trim();
      }

      if (matchResults[2] && matchResults[2].length) {
        thisMessage.message = matchResults[2].trim();
      }
    }

    if (thisMessage) messages.push(thisMessage);
  });

  return messages;
}

/**
 * Takes a line number and returns a padded string matching the total number of
 * characters in totalLinesNumber. EG: A lineNumber of 12 and a
 * totalLinesNumber of 1323 will return the string '  12'.
 *
 * @param   {Number} lineNumber
 * @param   {Number} totalLinesNumber
 *
 * @return  {String}
 */
function getPaddedLineNumber (lineNumber, totalLinesNumber) {
  var paddedLineNumberString = '' + lineNumber;

  while (paddedLineNumberString.length < ('' + totalLinesNumber).length) {
    paddedLineNumberString = ' ' + paddedLineNumberString;
  }

  return paddedLineNumberString;
}

/**
 * Takes an individual message object, as output from retrieveMessagesFromLine
 * and formats it for output.
 *
 * @param     {Object}    individualMessage
 * @property  {String}    individualMessage.author
 * @property  {String}    individualMessage.message
 * @property  {String}    individualMessage.label
 * @property  {Function}  individualMessage.colorer
 * @property  {Number}    individualMessage.line_number
 * @param     {Number}    totalNumberOfLines
 *
 * @return    {String}    The formatted message string.
 */
function formatMessageOutput (individualMessage, totalNumberOfLines) {
  var paddedLineNumber = getPaddedLineNumber(individualMessage.line_number, totalNumberOfLines),
      finalLabelString,
      finalNoteString;

  finalNoteString = chalk.gray('  [Line ' + paddedLineNumber + '] ');

  finalLabelString = individualMessage.label;

  if (individualMessage.author) {
    finalLabelString += (' from ' + individualMessage.author + ': ');
  } else {
    finalLabelString += ': ';
  }

  finalLabelString = chalk.bold(individualMessage.colorer(finalLabelString));

  finalNoteString += finalLabelString;

  if (individualMessage.message && individualMessage.message.length) {
    finalNoteString += individualMessage.colorer(individualMessage.message);
  } else {
    finalNoteString += chalk.grey('[[no message to display]]');
  }

  return finalNoteString;
}

/**
 * Formatter function for the file name. Takes a file path, and the total
 * number of messages in the file, and formats this information for display as
 * the heading for the file messages.
 *
 * @param   {String} filePath
 * @param   {Number} numberOfMessages
 *
 * @return  {String}
 */
function formatFilePathOutput (filePath, numberOfMessages) {
  var filePathOutput = chalk.bold.white('\n* ' + filePath + ' '),
      messagesString = 'messages';

  if (numberOfMessages === 1) {
    messagesString = 'message';
  }

  filePathOutput += chalk.grey('[' + numberOfMessages + ' ' + messagesString + ']:');

  return filePathOutput;
}

/**
 * Takes an object representing the messages and other meta-info for the file
 * and calls off to the formatters for the messages, as well as logs the
 * formatted result.
 *
 * @param     {Object}  messagesInfo
 * @property  {String}  messagesInfo.path The file path
 * @property  {Array}   messagesInfo.messages All of the message objects for the file.
 * @property  {String}  messagesInfo.total_lines Total number of lines in the file.
 */
function logMessages (messagesInfo) {
  if (messagesInfo.messages.length) {
    console.log(formatFilePathOutput(messagesInfo.path, messagesInfo.messages.length));

    messagesInfo.messages.forEach(function (message) {
      var formattedMessage = formatMessageOutput(message, messagesInfo.total_lines);

      console.log(formattedMessage);
    });
  }
}

/**
 * Reads through the configured path scans the matching files for messages.
 */
function scanAndProcessMessages () {
  var stream = readdirp({
    root:       scanPath,
    fileFilter: fileFilterer
  });

  // Remove skipped checks for our mapping
  skipChecks.forEach(function (checkName) {
    delete messageChecks[checkName];
  });

  // TODO: Actually do something meaningful/useful with these handlers.
  stream
    .on('warn', console.warn)
    .on('error', console.error);

  stream
    .pipe(eventStream.map(function (fileInformation, callback) {
      var input                 = fs.createReadStream(fileInformation.fullPath, { encoding: fileEncoding }),
          // lineStream            = byline.createStream(input, { encoding: fileEncoding }),
          fileMessages          = { path: null, total_lines: 0, messages: [] },
          currentFileLineNumber = 1;

      fileMessages.path = fileInformation.path;

      input.pipe( eventStream.split() )
        .pipe( eventStream.map( function( fileLineString, cb ){
          var messages,
              lengthError;

          if (fileLineString.length < lineLengthLimit) {
            messages = retrieveMessagesFromLine(fileLineString, currentFileLineNumber);

            messages.forEach(function (message) {
              fileMessages.messages.push(message);
            });
          } else if (skipChecks.indexOf('line') === -1){
            lengthError = 'Fixme is skipping this line because its length is ' +
                          'greater than the maximum line-length of ' +
                          lineLengthLimit + '.';

            fileMessages.messages.push({
              message:      lengthError,
              line_number:  currentFileLineNumber,
              label:        ' ⚠ SKIPPING CHECK',
              colorer:      chalk.underline.red
            });
          }

          currentFileLineNumber += 1;
        })
      );

      input.on('end', function () {
        fileMessages.total_lines = currentFileLineNumber;

        logMessages(fileMessages);
      });

      callback();
    }));
}

/**
 * Takes an options object and over-writes the defaults, then calls off to the
 * scanner to scan the files for messages.
 *
 * @param     {Object}  options
 * @property  {String}  options.path                The base directory to recursively scan for messages. Defaults to process.cwd()
 * @property  {Array}   options.ignored_directories An array of minimatch glob patterns for directories to ignore scanning entirely.
 * @property  {Array}   options.file_patterns       An array of minimatch glob patterns for files to scan for messages.
 * @property  {String}  options.file_encoding       The encoding the files scanned will be opened with, defaults to 'utf8'.
 * @property  {Number}  options.line_length_limit   The number of characters a line can be before it is ignored. Defaults to 1000.
 * @property  {Array}   options.skip                An array of names of checks to skip.
 */
 // TODO(johnp): Allow custom messageChecks to be added via options.
function parseUserOptionsAndScan (options) {
  if (options) {
    if (options.path) {
      scanPath = options.path;
    }

    if (options.ignored_directories &&
        Array.isArray(options.ignored_directories) &&
        options.ignored_directories.length) {
      ignoredDirectories = options.ignored_directories;
    }

    if (options.file_patterns &&
        Array.isArray(options.file_patterns) &&
        options.file_patterns.length) {
      filesToScan = options.file_patterns;
    }

    if (options.file_encoding) {
      fileEncoding = options.file_encoding;
    }

    if (options.line_length_limit) {
      lineLengthLimit = options.line_length_limit;
    }

    if (options.skip &&
        Array.isArray(options.skip) &&
        options.skip.length) {
      skipChecks = options.skip;
    }
  }

  scanAndProcessMessages();
}

// TODO: options: github.username, github.password => Get from Git Config
// TODO: Create a github issue
// TODO: Check if issue has been already created
// TODO: add option: Labels => github lables
// TODO: how to deal with assigning issues? TODO @me => issue assigned to me;
// TODO @aymen => get list of contributors and match aymen with one of the contributors
// TODO: add option: if issue is closed => remove annotated line

module.exports = parseUserOptionsAndScan;
<|endoftext|>


<commit_before>'use strict';

class TeamcityReporter {
    constructor(emitter, reporterOptions, options) {
        this.reporterOptions = reporterOptions;
        this.options = options;
        const events = 'start beforeIteration iteration beforeItem item beforePrerequest prerequest beforeScript script beforeRequest request beforeTest test beforeAssertion assertion console exception beforeDone done'.split(' ');
        events.forEach((e) => { if (typeof this[e] == 'function') emitter.on(e, (err, args) => this[e](err, args)) });
    }

    start(err, args) {
        console.log(`##teamcity[testSuiteStarted name='${this.escape(this.options.collection.name)}']`);
    }

    beforeItem(err, args) {
        this.currItem = {name: args.item.name, passed: true, failedAssertions: []};
        console.log(`##teamcity[testStarted name='${this.escape(this.currItem.name)}' captureStandardOutput='true']`);
    }

    request(err, args) {
        if (!err) {
            this.currItem.response = args.response;
        }
    }

    assertion(err, args) {
        if (err) {
            this.currItem.passed = false;
            this.currItem.failedAssertions.push(args.assertion);
        }
    }

    item(err, args) {
        if (!this.currItem.passed) {
            const msg = this.escape(this.currItem.failedAssertions.join(", "));
            const responseCode = (this.currItem.response && this.currItem.response.responseTime) || "-";
            const reason = (this.currItem.response && this.currItem.response.reason()) || "-";
            const details = this.escape(`Response code: ${responseCode}, reason: ${reason}`);
            console.log(`##teamcity[testFailed name='${this.escape(args.item.name)}' message='${msg}' details='${msg} - ${details}']`);
        }
        const duration = (this.currItem.response && this.currItem.response.responseTime) || 0;
        console.log(`##teamcity[testFinished name='${this.escape(args.item.name)}' duration='${duration}']`);
    }

    done(err, args) {
        console.log(`##teamcity[testSuiteFinished name='${this.options.collection.name}']`);
    }

    /**
     * Escape special patterns as defined at:
     * https://confluence.jetbrains.com/display/TCD9/Build+Script+Interaction+with+TeamCity
     */
    escape(string) {
        return string.replace(/['\n\r|\[\]]|0x(\d|[A-Fa-f]){4}/g, '|$&');
    }
}

module.exports = TeamcityReporter;
<commit_msg>[#4] Fixed TeamcityReporter#escape()<commit_after>'use strict';

class TeamcityReporter {
    constructor(emitter, reporterOptions, options) {
        this.reporterOptions = reporterOptions;
        this.options = options;
        const events = 'start beforeIteration iteration beforeItem item beforePrerequest prerequest beforeScript script beforeRequest request beforeTest test beforeAssertion assertion console exception beforeDone done'.split(' ');
        events.forEach((e) => { if (typeof this[e] == 'function') emitter.on(e, (err, args) => this[e](err, args)) });
    }

    start(err, args) {
        console.log(`##teamcity[testSuiteStarted name='${this.escape(this.options.collection.name)}']`);
    }

    beforeItem(err, args) {
        this.currItem = {name: args.item.name, passed: true, failedAssertions: []};
        console.log(`##teamcity[testStarted name='${this.escape(this.currItem.name)}' captureStandardOutput='true']`);
    }

    request(err, args) {
        if (!err) {
            this.currItem.response = args.response;
        }
    }

    assertion(err, args) {
        if (err) {
            this.currItem.passed = false;
            this.currItem.failedAssertions.push(args.assertion);
        }
    }

    item(err, args) {
        if (!this.currItem.passed) {
            const msg = this.escape(this.currItem.failedAssertions.join(", "));
            const responseCode = (this.currItem.response && this.currItem.response.responseTime) || "-";
            const reason = (this.currItem.response && this.currItem.response.reason()) || "-";
            const details = this.escape(`Response code: ${responseCode}, reason: ${reason}`);
            console.log(`##teamcity[testFailed name='${this.escape(args.item.name)}' message='${msg}' details='${msg} - ${details}']`);
        }
        const duration = (this.currItem.response && this.currItem.response.responseTime) || 0;
        console.log(`##teamcity[testFinished name='${this.escape(args.item.name)}' duration='${duration}']`);
    }

    done(err, args) {
        console.log(`##teamcity[testSuiteFinished name='${this.options.collection.name}']`);
    }

    /**
     * Escape special patterns as defined at:
     * https://confluence.jetbrains.com/display/TCD9/Build+Script+Interaction+with+TeamCity
     */
    escape(string) {
        return string
            .replace(/['|\[\]]/g, '|$&')
            .replace('\n', '|n')
            .replace('\r', '|r')
            .replace(/[\u0100-\uffff]/g, (c) => `|0x${c.charCodeAt(0).toString(16)}`);
    }
}

module.exports = TeamcityReporter;
<|endoftext|>


<commit_before>var extend        = require('node.extend');

var PIDController = function(options)
{
    // Options configuration
    var defaults     = {
        // Function (Reading from sensor)
        input      : function()
        {
            return 0;
        },
        // Function (Writing to device)
        output     : function()
        {
            return 0;
        },
        target     : 0,
        sampleTime : 100,
        Kp         : 1.0,
        Ki         : 0.0,
        Kd         : 0.0,
        min        : 0,
        max        : 255,
        round      : true,
        direction  : 0    // 0 = forward, 1 = reverse
    };

    options         = options || {};
    this.opts       = extend({}, defaults, options);
    this.iVal       = 0;
    this.iValLast   = 0;
    this.oVal       = 0;
    this.oValLast   = 0;
    this.lTime      = 0;
    this.kp         = 0.0;
    this.ki         = 0.0;
    this.kd         = 0.0;
    this.running    = false;
    this.tReached   = false;
    this.iTerm      = 0;
    this.interval   = 0;

    this.setDirection(this.opts.direction);
    this.setTunings(this.opts.Kp, this.opts.Ki, this.opts.Kd);
};

PIDController.prototype = {
    compute         : function ()
    {
        if(!this.running)
        {
            return false;
        }

        var d                = new Date();
        var now              = d.getTime();
        var tChanged         = (now - this.lTime);

        this.opts.sampleTime = (this.opts.sampleTime > 0) ? this.opts.sampleTime : 100;

        if(tChanged >= this.opts.sampleTime)
        {
            /*Compute all the working error variables*/
            this.opts.input();

            var input      = this.iVal;
            var error      = parseFloat(this.opts.target) - input;
            this.iTerm    += (this.ki * error);

            if(this.iTerm > this.opts.max)
            {
                this.iTerm = this.opts.max;
            }
            else if(this.iTerm < this.opts.min)
            {
                this.iTerm= this.opts.min;
            }

            var dInput     = parseFloat(input - this.iValLast);

            /*Compute PID Output*/
            var output = (this.kp * error + this.iTerm - this.kd * dInput);

            if(output > this.opts.max)
            {
                output = this.opts.max;
            }
            else if(output < this.opts.min)
            {
                output = this.opts.min;
            }

            output         = (this.opts.round) ? Math.round(output) : output;

            if(output != this.oValLast)
            {
                this.oValLast  = output;
                this.opts.output(output);
            }

            /*Remember some variables for next time*/
            this.iValLast  = input;
            this.lTime     = now;

            /** Did we hit the target yet? **/
            //console.log('TARGET: ', this.opts.target);
            //console.log('INPUT: ', input);

            if(this.tReached == false && input >= this.opts.target)
            {
                this.tReached = true;
                //console.log('TARGET REACHED!');
            }

            return true;
        }

        // Not time yet.
        return false;
    },
    run            : function()
    {
        if (!this.running)
        {
            return false;
        }

        this.compute();

        var self      = this;
        this.interval = setTimeout(function(){ self.run(); }, this.opts.sampleTime);

        return true;
    },
    setDirection    : function (direction)
    {
        if(direction != this.opts.direction)
        {
            // Switch to and from a negative number
            this.kp = (0 - this.kp);
            this.ki = (0 - this.ki);
            this.kd = (0 - this.kd);
        }

        this.opts.direction = direction;
        return true;
    },
    setOutputLimits : function (min, max)
    {
        if(min >= max)
        {
            return false; // Should throw error.
        }

        // Probably shouldn't be doing this with the OPTS.
        this.opts.min = min;
        this.opts.max = max;

        if(this.running)
        {
            if(this.oVal > this.opts.max)
            {
                this.oVal = this.opts.max;
            }
            else if(this.oVal < this.opts.min)
            {

                this.oVal = this.opts.min;
            }

            if(this.iTerm > this.opts.max)
            {
                this.iTerm = this.opts.max;
            }
            else if(iTerm < this.opts.min)
            {
                this.iTerm = this.opts.min;
            }
        }

        return true;
    },
    setSampleTime   : function (newSampleTime)
    {
        if(newSampleTime > 0)
        {
            var ratio             = parseFloat(newSampleTime / this.opts.sampleTime);
            this.ki              *= ratio;
            this.kd              /= ratio;
            this.opts.sampleTime  = NewSampleTime;

            return true;
        }

        return false;
    },
    setInput        : function (input)
    {
        this.opts.input = input;
        return true;
    },
    setOutput        : function (output)
    {
        this.opts.output = output;
        return true;
    },
    setTarget       : function (target)
    {
        this.tReached    = false;
        this.opts.target = parseFloat(target);
        return true;
    },
    setTunings      : function (Kp, Ki, Kd)
    {
        if(Kp < 0 || Ki < 0 || Kd < 0)
        {
            // Should throw err here.
            return false;
        }

        var sampleTimeInSec = parseFloat(this.opts.sampleTime/1000);
        this.kp             = Kp;
        this.ki             = Ki * sampleTimeInSec;
        this.kd             = Kd / sampleTimeInSec;

        if(this.opts.direction == 1)
        {
            this.kp = (0 - this.kp);
            this.ki = (0 - this.ki);
            this.kd = (0 - this.kd);
        }

        return true;
    },
    start           : function()
    {
        console.log('starting');
        this.iTerm    = this.oVal;
        this.iValLast = this.iVal;
        this.running  = true;

        // Run-a-way intervals (timeouts)?
        clearTimeout(this.interval);

        if(this.iTerm > this.opts.max)
        {
            this.iTerm = this.opts.max;
        }
        else if(this.iTerm < this.opts.min)
        {
            this.iTerm = this.opts.min;
        }

        this.run();
        return true;
    },
    setIVal        : function(val)
    {
        this.iVal = val;
        return true;
    },
    status         : function()
    {
        return {'running' : this.running, 'target' : this.opts.target, 'tReached': this.tReached, 'direction' : this.opts.direction};
    },
    stop            : function()
    {
        this.running  = false;
        clearTimeout(this.interval);
        return true;
    }
}

module.exports    = PIDController;<commit_msg>COMMITTING FOR COMMITTING PURPOSES - CODE NOT TESTED.<commit_after>var util          = require('util');
var events        = require('events');
var extend        = require('node.extend');

var PIDController = function(options)
{
    // Options configuration
    this.opts     = {
        // Function (Reading from sensor)
        input      : function()
        {
            return 0;
        },
        // Function (Writing to device)
        output     : function(val)
        {
            return val;
        },
        target     : 0,
        sampleTime : 1000,  // In MS.
        Kp         : 1.0,
        Ki         : 0.0,
        Kd         : 0.0,
        outMin     : 0,
        outMax     : 255,
        round      : true,
        duration   : 1,    // In seconds (How long to run - 0 = infinite)
        direction  : 0     // 0 = forward, 1 = reverse
    };

    options         = options || {};
    this.opts       = extend({}, this.opts, options);
    this.iVal       = 0;
    this.iValLast   = 0;
    this.oVal       = 0;
    this.oValLast   = 0;
    this.lTime      = 0;
    this.kp         = 0.0;
    this.ki         = 0.0;
    this.kd         = 0.0;
    this.running    = false;
    this.tReached   = false;
    this.iTerm      = 0;
    this.interval   = 0;
    this.startTime  = 0;

    this.setDirection(this.opts.direction);
    this.setTunings(this.opts.Kp, this.opts.Ki, this.opts.Kd);

    // Setup the Events
    events.EventEmitter.call(this);
};

PIDController.prototype = {
    compute         : function ()
    {
        if(!this.running)
        {
            return false;
        }

        var d                = new Date();
        var now              = d.getTime();
        var tChanged         = (now - this.lTime);

        // Make sure we don't get any run away timeouts.
        this.opts.sampleTime = (this.opts.sampleTime > 0) ? this.opts.sampleTime : 1000;

        if(tChanged >= this.opts.sampleTime)
        {
            /*Compute all the working error variables*/
            this.opts.input();

            var input      = this.iVal;
            var error      = parseFloat(this.opts.target) - input;
            this.iTerm    += (this.ki * error);

            if(this.iTerm > this.opts.outMax)
            {
                this.iTerm = this.opts.outMax;
            }
            else if(this.iTerm < this.opts.outMin)
            {
                this.iTerm= this.opts.outMin;
            }

            var dInput     = parseFloat(input - this.iValLast);

            /*Compute PID Output*/
            var output = (this.kp * error + this.iTerm - this.kd * dInput);

            if(output > this.opts.outMax)
            {
                output = this.opts.outMax;
            }
            else if(output < this.opts.outMin)
            {
                output = this.opts.outMin;
            }

            output         = (this.opts.round) ? Math.round(output) : output;

            if(output != this.oValLast)
            {
                this.oValLast  = output;
                this.opts.output(output);
            }

            // Remember some values for next time
            this.iValLast  = input;
            this.lTime     = now;

            // Did we hit the target yet?
            if(this.opts.direction == 0 && this.tReached == false && input >= this.opts.target)
            {
                this.tReached = true;
                this.emit('targetReached');
            }
            else if(this.opts.direction == 1 && this.tReached == false && input <= this.opts.target)
            {
                this.tReached = true;
                this.emit('targetReached');
            }

            return this;
        }

        // Not time yet.
        return false;
    },
    run            : function()
    {
        var self      = this;
        var d         = new Date();
        var now       = d.getTime() / 1000;

        if(this.startTime == 0)
        {
            this.startTime = d.getTime() / 1000;
        }

        if (!this.running)
        {
            return false;
        }

        this.compute();

        this.timeLeft = now - this.startTime;

        if(this.timeLeft > this.opts.duration)
        {
            this.interval = setTimeout(function ()
            {
                self.run();
            }, this.opts.sampleTime);
        }
        else
        {
            this.stop();
        }

        return this;
    },
    setDirection    : function (direction)
    {
        if(direction == 0)
        {
            // Positive (upward) gain
            if(this.kp < 0)
            {
                // Make sure it's a negative number before changing to positive.
                this.kp = (0 - this.kp);
                this.ki = (0 - this.ki);
                this.kd = (0 - this.kd);
            }
        }
        else
        {
            // Negative (downward direction)
            if(this.kp > 0)
            {
                // Make sure it's a positive number before changing to negative.
                this.kp = (0 - this.kp);
                this.ki = (0 - this.ki);
                this.kd = (0 - this.kd);
            }
        }

        this.opts.direction = direction;
        return this;
    },
    setDuration    : function (duration)
    {
        this.opts.duration = duration;
        return this;
    },
    setInput        : function (input)
    {
        this.opts.input = input;
        return this;
    },
    setIVal        : function(val)
    {
        this.iVal = val;
        return this;
    },
    setOutputLimits : function (min, max)
    {
        if(min >= max)
        {
            return false; // Should throw error.
        }

        // Probably shouldn't be doing this with the OPTS.
        this.opts.outMin = min;
        this.opts.outMax = max;

        if(this.running)
        {
            if(this.oVal > this.opts.outMax)
            {
                this.oVal = this.opts.outMax;
            }
            else if(this.oVal < this.opts.outMin)
            {

                this.oVal = this.opts.outMin;
            }

            if(this.iTerm > this.opts.outMax)
            {
                this.iTerm = this.opts.outMax;
            }
            else if(this.iTerm < this.opts.outMin)
            {
                this.iTerm = this.opts.outMin;
            }
        }

        return this;
    },
    setSampleTime   : function (newSampleTime)
    {
        if(newSampleTime > 0)
        {
            var ratio             = parseFloat(newSampleTime / this.opts.sampleTime);
            this.ki              *= ratio;
            this.kd              /= ratio;
            this.opts.sampleTime  = NewSampleTime;

            return this;
        }

        // Need to throw error
        return false;
    },
    setOutput        : function (output)
    {
        this.opts.output = output;
        return this;
    },
    setTarget       : function (target)
    {
        this.tReached    = false;
        this.opts.target = parseFloat(target);

        return this;
    },
    setTunings      : function (Kp, Ki, Kd)
    {
        if(Kp < 0 || Ki < 0 || Kd < 0)
        {
            // Should throw err here.
            return false;
        }

        var sampleTimeInSec = parseFloat(this.opts.sampleTime/1000);
        this.kp             = Kp;
        this.ki             = Ki * sampleTimeInSec;
        this.kd             = Kd / sampleTimeInSec;

        /**
         * We have a setDirection method.
         if(this.opts.direction == 1)
         {
             this.kp = (0 - this.kp);
             this.ki = (0 - this.ki);
             this.kd = (0 - this.kd);
         }
         **/

        return this;
    },
    start           : function()
    {
        // Make sure we can run run()
        this.running  = true;

        // Run-a-way intervals (timeouts)?
        clearTimeout(this.interval);

        // Safeties I guess.
        this.iTerm    = this.oVal;
        this.iValLast = this.iVal;

        if(this.iTerm > this.opts.outMax)
        {
            this.iTerm = this.opts.outMax;
        }
        else if(this.iTerm < this.opts.outMin)
        {
            this.iTerm = this.opts.outMin;
        }

        this.emit('started');
        this.run();
        return this;
    },
    status         : function()
    {
        return {'running' : this.running, 'target' : this.opts.target, 'tReached': this.tReached, 'direction' : this.opts.direction, 'duration' : this.opts.duration, 'timeLeft' : this.timeLeft};
    },
    stop            : function()
    {
        // Make sure run() can't be triggered.
        this.running  = false;

        // Stop the interval (timeout)
        clearTimeout(this.interval);

        // Set the output to 0
        this.opts.output(0);

        this.emit('stopped');
        return this;
    }
};

util.inherits(PIDController, events.EventEmitter);
module.exports    = PIDController;<|endoftext|>


<commit_before>var _ = require("lodash");

var tokenUtils = require('./tokens');

var WORD_BOUNDARY_CHARS = '\t\r\n\u00A0 !\"#$%&()*+,\-.\\/:;<=>?@\[\\\]^_`{|}~';
var WORD_BOUNDARY_REGEX = new RegExp('[' + WORD_BOUNDARY_CHARS + ']');
var SPLIT_REGEX = new RegExp(
    '([^' + WORD_BOUNDARY_CHARS + ']+)');

function Tokenizer(opts) {
    if (!(this instanceof Tokenizer)) return (new Tokenizer(opts));

    this.opts = _.defaults(opts || {}, {
        cacheGet: function(key) { return null; },
        cacheSet: function(key, value) { }
    });

    _.bindAll(this);
}

// Tokenize a text using a transformative function
Tokenizer.prototype.split = function tokenizeSplit(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        cache: _.constant(null)
    });

    return function(text) {
        var prev = undefined;
        var cacheId, cacheValue;

        // if string, convert to one large token
        if (_.isString(text)) {
            text = [
                {
                    value: text,
                    index: 0,
                    offset: text.length
                }
            ];
        }

        cacheId = tokenUtils.tokensId(text, opts.cache());
        if (cacheId) {
            cacheValue = that.opts.cacheGet(cacheId);
            if (cacheValue) {
                return cacheValue;
            }
        }

        return _.chain(text)
            .map(function(token, i) {
                var next = text[i + 1];
                var tokens = fn(
                    // Current text value
                    token.value,

                    // Current complete token
                    _.clone(token),

                    // Previous token
                    prev? _.clone(prev) : null,

                    // Next token
                    next? _.clone(next) : null
                ) || [];

                // Normalize tokens and return
                tokens = tokenUtils.normalize(token, tokens);

                // Update reference to prev
                prev = token;

                return tokens;
            })
            .compact()
            .flatten()
            .tap(function(_tokens) {
                if (!cacheId) return;

                that.opts.cacheSet(cacheId, _tokens);
            })
            .value();
    };
};

// Debug a tokenizing flow
Tokenizer.prototype.debug = function tokenizeDebug() {
    return this.split(function(tok) {
        console.log('token', tok);
        return tok;
    });
};

// Tokenize a text using a RegExp
Tokenizer.prototype.re = function tokenizeRe(re, opts) {
    opts = _.defaults(opts || {}, {
        split: false
    });

    return this.split(function(text) {
        var tokens = [];
        var match;
        var start = 0;

        while (match = re.exec(text)) {
            var index = match.index;
            var value = match[0] || "";
            var offset = value.length;

            tokens.push({
                value: value,
                index: index + start,
                offset: offset,
                match: match
            });

            text = text.slice(index + offset);
            start = start + index + offset;
        }

        if (opts.split && text) {
            tokens.push({
                value: text,
                index: start,
                offset: text.length
            });
        }

        return tokens;
    }, {
        cache: function() {
            return re.toString();
        }
    });
};

// Split and merge tokens
// Used to split as sentences even if sentences is separated in multiple tokens (ex: markup)
// if fn results contain a 'null', it will split in two tokens
Tokenizer.prototype.splitAndMerge = function tokenizeSplitAndMerge(fn) {
    var that = this;

    return function(tokens) {
        var result = [];
        var accu = [];

        function pushAccu() {
            if (accu.length == 0) return;

            result.push(_.pluck(accu, 'value').join(''));
            accu = [];
        }

        that.split(function(token) {
            var toks = fn.apply(null, arguments);

            // Normalize tokens
            toks = tokenUtils.normalize(token, toks);

            // Accumulate tokens and push to final results
            _.each(toks, function(tok) {
                if (!tok) {
                    pushAccu();
                } else {
                    accu.push(tok);
                }
            });
        })(tokens);

        // Push tokens left in accumulator
        pushAccu();

        return result;
    };
};

// Filter when tokenising
Tokenizer.prototype.filter = function tokenizeFilter(fn) {
    return this.split(function(text, tok) {
        if (fn.apply(null, arguments)) {
            return tok.value;
        }
        return undefined;
    });
};

// Filter by testing a regex
Tokenizer.prototype.test = function tokenizeTest(re) {
    return this.filter(function(text, tok) {
        return re.test(text);
    }, {
        cache: re.toString()
    });
};

// Group and process a token as a group
Tokenizer.prototype.flow = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return this.split(fn);
};

// Merge all tokens into one
Tokenizer.prototype.merge = _.partial(Tokenizer.prototype.splitAndMerge, _.identity);

Tokenizer.prototype.sections = _.partial(Tokenizer.prototype.re, /([^\n\.,;!?]+)/i, { split: true });
Tokenizer.prototype.words = _.partial(Tokenizer.prototype.re, SPLIT_REGEX);
Tokenizer.prototype.characters = _.partial(Tokenizer.prototype.re, /[^\s]/);

module.exports = Tokenizer;
<commit_msg>Improve tokenize.debug<commit_after>var _ = require("lodash");

var tokenUtils = require('./tokens');

var WORD_BOUNDARY_CHARS = '\t\r\n\u00A0 !\"#$%&()*+,\-.\\/:;<=>?@\[\\\]^_`{|}~';
var WORD_BOUNDARY_REGEX = new RegExp('[' + WORD_BOUNDARY_CHARS + ']');
var SPLIT_REGEX = new RegExp(
    '([^' + WORD_BOUNDARY_CHARS + ']+)');

function Tokenizer(opts) {
    if (!(this instanceof Tokenizer)) return (new Tokenizer(opts));

    this.opts = _.defaults(opts || {}, {
        cacheGet: function(key) { return null; },
        cacheSet: function(key, value) { }
    });

    _.bindAll(this);
}

// Tokenize a text using a transformative function
Tokenizer.prototype.split = function tokenizeSplit(fn, opts) {
    var that = this;

    opts = _.defaults(opts || {}, {
        cache: _.constant(null)
    });

    return function(text) {
        var prev = undefined;
        var cacheId, cacheValue;

        // if string, convert to one large token
        if (_.isString(text)) {
            text = [
                {
                    value: text,
                    index: 0,
                    offset: text.length
                }
            ];
        }

        cacheId = tokenUtils.tokensId(text, opts.cache());
        if (cacheId) {
            cacheValue = that.opts.cacheGet(cacheId);
            if (cacheValue) {
                return cacheValue;
            }
        }

        return _.chain(text)
            .map(function(token, i) {
                var next = text[i + 1];
                var tokens = fn(
                    // Current text value
                    token.value,

                    // Current complete token
                    _.clone(token),

                    // Previous token
                    prev? _.clone(prev) : null,

                    // Next token
                    next? _.clone(next) : null
                ) || [];

                // Normalize tokens and return
                tokens = tokenUtils.normalize(token, tokens);

                // Update reference to prev
                prev = token;

                return tokens;
            })
            .compact()
            .flatten()
            .tap(function(_tokens) {
                if (!cacheId) return;

                that.opts.cacheSet(cacheId, _tokens);
            })
            .value();
    };
};

// Debug a tokenizing flow
Tokenizer.prototype.debug = function tokenizeDebug() {
    return this.split(function(text, tok) {
        console.log('[' + tok.index + '-' + (tok.index + tok.offset) + ']', tok.value);
        return tok;
    });
};

// Tokenize a text using a RegExp
Tokenizer.prototype.re = function tokenizeRe(re, opts) {
    opts = _.defaults(opts || {}, {
        split: false
    });

    return this.split(function(text) {
        var tokens = [];
        var match;
        var start = 0;

        while (match = re.exec(text)) {
            var index = match.index;
            var value = match[0] || "";
            var offset = value.length;

            tokens.push({
                value: value,
                index: index + start,
                offset: offset,
                match: match
            });

            text = text.slice(index + offset);
            start = start + index + offset;
        }

        if (opts.split && text) {
            tokens.push({
                value: text,
                index: start,
                offset: text.length
            });
        }

        return tokens;
    }, {
        cache: function() {
            return re.toString();
        }
    });
};

// Split and merge tokens
// Used to split as sentences even if sentences is separated in multiple tokens (ex: markup)
// if fn results contain a 'null', it will split in two tokens
Tokenizer.prototype.splitAndMerge = function tokenizeSplitAndMerge(fn) {
    var that = this;

    return function(tokens) {
        var result = [];
        var accu = [];

        function pushAccu() {
            if (accu.length == 0) return;

            result.push(_.pluck(accu, 'value').join(''));
            accu = [];
        }

        that.split(function(token) {
            var toks = fn.apply(null, arguments);

            // Normalize tokens
            toks = tokenUtils.normalize(token, toks);

            // Accumulate tokens and push to final results
            _.each(toks, function(tok) {
                if (!tok) {
                    pushAccu();
                } else {
                    accu.push(tok);
                }
            });
        })(tokens);

        // Push tokens left in accumulator
        pushAccu();

        return result;
    };
};

// Filter when tokenising
Tokenizer.prototype.filter = function tokenizeFilter(fn) {
    return this.split(function(text, tok) {
        if (fn.apply(null, arguments)) {
            return tok.value;
        }
        return undefined;
    });
};

// Filter by testing a regex
Tokenizer.prototype.test = function tokenizeTest(re) {
    return this.filter(function(text, tok) {
        return re.test(text);
    }, {
        cache: re.toString()
    });
};

// Group and process a token as a group
Tokenizer.prototype.flow = function tokenizeFlow() {
    var fn = _.flow.apply(_, arguments);
    return this.split(fn);
};

// Merge all tokens into one
Tokenizer.prototype.merge = _.partial(Tokenizer.prototype.splitAndMerge, _.identity);

Tokenizer.prototype.sections = _.partial(Tokenizer.prototype.re, /([^\n\.,;!?]+)/i, { split: true });
Tokenizer.prototype.words = _.partial(Tokenizer.prototype.re, SPLIT_REGEX);
Tokenizer.prototype.characters = _.partial(Tokenizer.prototype.re, /[^\s]/);

module.exports = Tokenizer;
<|endoftext|>


<commit_before>'use babel';
'use strict';

const {EventEmitter}     = require ('events');
const {Client}           = require ('_debugger');
const {spawn}            = require ('child_process');
const watt               = require ('watt');
const escapeStringRegexp = require ('escape-string-regexp');
const path               = require ('path');
const fs                 = require ('fs');
const tmp                = require ('tmp');

const Extractor = require ('./extractor.js');

class Wannabe extends EventEmitter {
  /**
   * Construct the Wannabe object.
   *
   * @param {string} script - Test script.
   */
  constructor (script) {
    super ();

    this._frames  = {};
    this._script  = script;
    this._client  = new Client ();
    this._mocha   = {
      kill: () => {}
    };

    watt.wrapAll (this);
  }

  /**
   * Spawn mocha with the node debugger.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  _spawn (pattern, next) {
    const regex = pattern instanceof RegExp ?
                  pattern.toString ().replace (/^\/(.*)\/.?$/, '$1') :
                  escapeStringRegexp (pattern);

    this._mocha = spawn ('node', [
      '--debug',
      '--debug-brk',
      path.join (__dirname, './mocha.js'),
      this._script,
      regex
    ], {
      detached: false,
      stdio: ['ignore', 'ignore', 'pipe']
    });

    this._mocha.unref ();

    this._mocha.stderr.once ('data', () => next ());

    this._mocha.on ('close', () => {});
    this._mocha.on ('error', (err) => {
      console.error (err);
    });
  }

  /**
   * Inspect the frame on the current breakpoint.
   *
   * It populates this._frames with the local variables.
   * The key of this._frames is the line number.
   *
   * NOTE: setBreakpoint is slow!
   */
  * _inspect (next) {
    const {frames} = yield this._client.reqBacktrace (next);
    const line = `${frames[0].line}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    const frame = {
      locals: [],
      arguments: [],
      returnValue: null
    };

    frames[0].locals.forEach ((local) => {
      /* Local variables */
      frame.locals.push ({
        name:  local.name,
        type:  local.value.type,
        value: local.value.value
      });
    });

    frames[0].arguments.forEach ((argument) => {
      /* Arguments */
      frame.arguments.push ({
        name:  argument.name,
        type:  argument.value.type,
        value: argument.value.value
      });
    });

    /* Return value */
    if (frames[0].returnValue && frames[0].returnValue.type !== 'undefined') {
      frame.returnValue = {
        type: frames[0].returnValue.type,
        value: frames[0].returnValue.value
      };
    }

    this._frames[line].push (frame);
  }

  _exception (ex, next) {
    const line = `${ex.body.sourceLine}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    this._frames[line].push ({
      exception: {
        type: ex.body.exception.type,
        text: ex.body.exception.text
      }
    });

    next ();
  }

  /**
   * Initialize the client connection.
   *
   * It waits on the first breakpoint because the child is executed with
   * the --debug-brk argument.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  * init (pattern, next) {
    yield this._spawn (pattern, next);

    let first = true;
    this._client.on ('break', () => {
      if (first) {
        this.emit ('ready');
        first = false;
        return;
      }

      this._inspect (() => this._client.reqContinue (() => {}));
    });

    this._client.on ('exception', (ex) => {
      this._exception (ex, () => this._client.reqContinue (() => {}));
    });

    this._client.once ('connect', next.parallel ());
    this._client.connect (5858);
    yield next.sync ();

    yield this.once ('ready', next); // wait for the first breakpoint (--debug-brk)
    yield this._client.reqSetExceptionBreak ('all', next);
  }

  /**
   * Set one or more breakpoints according to the current script.
   *
   * @param {number[]} lines - Lines where put the breakpoints.
   */
  * setBreakpoints (lines, next) {
    for (const line of lines) {
      this._client.setBreakpoint ({
        type:   'script',
        target: this._script,
        line:   line
      }, next.parallel ());
    }

    yield next.sync ();
  }

  /**
   * Run Wannabe.
   *
   * When a new breakpoint is reached, the continue request is automatically
   * sent until that the child is terminated.
   *
   * @returns all frames.
   */
  * run (next) {
    const promise = new Promise ((resolve) => {
      this._client.once ('error', (err) => {
        resolve (this._frames);
      });

      this._client.once ('close', () => {
        resolve (this._frames);
      });
    });

    yield this._client.reqContinue (next);
    return yield promise;
  }

  dispose () {
    this._mocha.kill ('SIGINT');
  }
}

const extract = watt (function * (script, data, extractor, next) {
  let file = null;
  if (data) {
    /* We must create a new script in the same location that {script}.
     * The reason is that we can not pass a buffer to mocha; only files
     * can be passed to the runner.
     */
    file = yield tmp.tmpName ({
      template: path.join (script, '../.wannabe-XXXXXX')
    }, next);
    console.log (file);
    yield fs.writeFile (file, data, next);
  } else {
    file = script;
  }

  let _exThrown = false;
  let frames = null;
  const wannabe = new Wannabe (file);

  try {
    const {lines, pattern} = extractor (file);
    if (!lines.length) {
      return {};
    }

    yield wannabe.init (pattern);

    yield wannabe.setBreakpoints (lines);
    frames = yield wannabe.run ();
  } catch (ex) {
    _exThrown = true;
    throw ex;
  } finally {
    wannabe.dispose ();
    if (data) {
      yield fs.unlink (file, next);
    }
    // FIXME: see watt bug: https://github.com/mappum/watt/issues/16
    if (!_exThrown) {
      return frames;
    }
  }
});

exports.byPattern = watt (function * (script, data, funcName, pattern, next) {
  return yield extract (script, data, (file) => {
    const lines = data ?
      Extractor.byFuncInData (data, funcName, pattern) :
      Extractor.byFuncInFile (file, funcName, pattern);
    return {
      lines,
      pattern
    }
  }, next);
});

exports.byLine = watt (function * (script, data, funcName, line, next) {
  return yield extract (script, data, (file) => {
    return data ?
      Extractor.byLineInData (data, funcName, line) :
      Extractor.byLineInFile (file, funcName, line);
  }, next);
});
<commit_msg>Use the class name if the value is not available<commit_after>'use babel';
'use strict';

const {EventEmitter}     = require ('events');
const {Client}           = require ('_debugger');
const {spawn}            = require ('child_process');
const watt               = require ('watt');
const escapeStringRegexp = require ('escape-string-regexp');
const path               = require ('path');
const fs                 = require ('fs');
const tmp                = require ('tmp');

const Extractor = require ('./extractor.js');

class Wannabe extends EventEmitter {
  /**
   * Construct the Wannabe object.
   *
   * @param {string} script - Test script.
   */
  constructor (script) {
    super ();

    this._frames  = {};
    this._script  = script;
    this._client  = new Client ();
    this._mocha   = {
      kill: () => {}
    };

    watt.wrapAll (this);
  }

  /**
   * Spawn mocha with the node debugger.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  _spawn (pattern, next) {
    const regex = pattern instanceof RegExp ?
                  pattern.toString ().replace (/^\/(.*)\/.?$/, '$1') :
                  escapeStringRegexp (pattern);

    this._mocha = spawn ('node', [
      '--debug',
      '--debug-brk',
      path.join (__dirname, './mocha.js'),
      this._script,
      regex
    ], {
      detached: false,
      stdio: ['ignore', 'ignore', 'pipe']
    });

    this._mocha.unref ();

    this._mocha.stderr.once ('data', () => next ());

    this._mocha.on ('close', () => {});
    this._mocha.on ('error', (err) => {
      console.error (err);
    });
  }

  /**
   * Inspect the frame on the current breakpoint.
   *
   * It populates this._frames with the local variables.
   * The key of this._frames is the line number.
   *
   * NOTE: setBreakpoint is slow!
   */
  * _inspect (next) {
    const {frames} = yield this._client.reqBacktrace (next);
    const line = `${frames[0].line}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    const frame = {
      locals: [],
      arguments: [],
      returnValue: null
    };

    frames[0].locals.forEach ((local) => {
      /* Local variables */
      frame.locals.push ({
        name:  local.name,
        type:  local.value.type,
        value: local.value.value || local.value.className
      });
    });

    frames[0].arguments.forEach ((argument) => {
      /* Arguments */
      frame.arguments.push ({
        name:  argument.name,
        type:  argument.value.type,
        value: argument.value.value
      });
    });

    /* Return value */
    if (frames[0].returnValue && frames[0].returnValue.type !== 'undefined') {
      frame.returnValue = {
        type: frames[0].returnValue.type,
        value: frames[0].returnValue.value
      };
    }

    this._frames[line].push (frame);
  }

  _exception (ex, next) {
    const line = `${ex.body.sourceLine}`;

    if (!this._frames[line]) {
      this._frames[line] = [];
    }

    this._frames[line].push ({
      exception: {
        type: ex.body.exception.type,
        text: ex.body.exception.text
      }
    });

    next ();
  }

  /**
   * Initialize the client connection.
   *
   * It waits on the first breakpoint because the child is executed with
   * the --debug-brk argument.
   *
   * @param {string|RegExp} regtest - Tests cases to grep.
   */
  * init (pattern, next) {
    yield this._spawn (pattern, next);

    let first = true;
    this._client.on ('break', () => {
      if (first) {
        this.emit ('ready');
        first = false;
        return;
      }

      this._inspect (() => this._client.reqContinue (() => {}));
    });

    this._client.on ('exception', (ex) => {
      this._exception (ex, () => this._client.reqContinue (() => {}));
    });

    this._client.once ('connect', next.parallel ());
    this._client.connect (5858);
    yield next.sync ();

    yield this.once ('ready', next); // wait for the first breakpoint (--debug-brk)
    yield this._client.reqSetExceptionBreak ('all', next);
  }

  /**
   * Set one or more breakpoints according to the current script.
   *
   * @param {number[]} lines - Lines where put the breakpoints.
   */
  * setBreakpoints (lines, next) {
    for (const line of lines) {
      this._client.setBreakpoint ({
        type:   'script',
        target: this._script,
        line:   line
      }, next.parallel ());
    }

    yield next.sync ();
  }

  /**
   * Run Wannabe.
   *
   * When a new breakpoint is reached, the continue request is automatically
   * sent until that the child is terminated.
   *
   * @returns all frames.
   */
  * run (next) {
    const promise = new Promise ((resolve) => {
      this._client.once ('error', (err) => {
        resolve (this._frames);
      });

      this._client.once ('close', () => {
        resolve (this._frames);
      });
    });

    yield this._client.reqContinue (next);
    return yield promise;
  }

  dispose () {
    this._mocha.kill ('SIGINT');
  }
}

const extract = watt (function * (script, data, extractor, next) {
  let file = null;
  if (data) {
    /* We must create a new script in the same location that {script}.
     * The reason is that we can not pass a buffer to mocha; only files
     * can be passed to the runner.
     */
    file = yield tmp.tmpName ({
      template: path.join (script, '../.wannabe-XXXXXX')
    }, next);
    console.log (file);
    yield fs.writeFile (file, data, next);
  } else {
    file = script;
  }

  let _exThrown = false;
  let frames = null;
  const wannabe = new Wannabe (file);

  try {
    const {lines, pattern} = extractor (file);
    if (!lines.length) {
      return {};
    }

    yield wannabe.init (pattern);

    yield wannabe.setBreakpoints (lines);
    frames = yield wannabe.run ();
  } catch (ex) {
    _exThrown = true;
    throw ex;
  } finally {
    wannabe.dispose ();
    if (data) {
      yield fs.unlink (file, next);
    }
    // FIXME: see watt bug: https://github.com/mappum/watt/issues/16
    if (!_exThrown) {
      return frames;
    }
  }
});

exports.byPattern = watt (function * (script, data, funcName, pattern, next) {
  return yield extract (script, data, (file) => {
    const lines = data ?
      Extractor.byFuncInData (data, funcName, pattern) :
      Extractor.byFuncInFile (file, funcName, pattern);
    return {
      lines,
      pattern
    }
  }, next);
});

exports.byLine = watt (function * (script, data, funcName, line, next) {
  return yield extract (script, data, (file) => {
    return data ?
      Extractor.byLineInData (data, funcName, line) :
      Extractor.byLineInFile (file, funcName, line);
  }, next);
});
<|endoftext|>


<commit_before>"use strict";

var conversions = {};
module.exports = conversions;

function sign(x) {
    return x < 0 ? -1 : 1;
}

function evenRound(x) {
    // Round x to the nearest integer, choosing the even integer if it lies halfway between two.
    if ((x % 1) === 0.5 && (x & 1) === 0) { // [even number].5; round down (i.e. floor)
        return Math.floor(x);
    } else {
        return Math.round(x);
    }
}

function createNumberConversion(bitLength, opts) {
    if (!opts.isUnsigned) {
        --bitLength;
    }
    const lowerBound = opts.isUnsigned ? 0 : -Math.pow(2, bitLength);
    const upperBound = Math.pow(2, bitLength) - 1;

    const moduloVal = opts.moduloBitLength ? Math.pow(2, opts.moduloBitLength) : Math.pow(2, bitLength);
    const moduloBound = opts.moduloBitLength ? Math.pow(2, opts.moduloBitLength - 1) : Math.pow(2, bitLength - 1);

    return function(V, opts) {
        let x = +V;

        if (opts.enforceRange) {
            if (!Number.isFinite(x)) {
                throw new TypeError("Argument is not a finite number");
            }

            x = sign(x) * Math.floor(Math.abs(x));
            if (x < lowerBound || x > upperBound) {
                throw new TypeError("Argument is not in byte range");
            }

            return x;
        }

        if (!isNaN(x) && opts.clamp) {
            x = evenRound(x);

            if (x < lowerBound) x = lowerBound;
            if (x > upperBound) x = upperBound;
            return x;
        }

        if (!Number.isFinite(x) || x === 0) {
            return 0;
        }

        x = sign(x) * Math.floor(Math.abs(x));
        x = x % moduloVal;

        if (!opts.isUnsigned && x >= moduloBound) {
            return x - moduloVal;
        }

        return x;
    }
}

conversions["void"] = function () {
    return undefined;
};

conversions["boolean"] = function (val) {
    return !!val;
};

conversions["byte"] = createNumberConversion(8, { unsigned: false });
conversions["octet"] = createNumberConversion(8, { unsigned: true });

conversions["short"] = createNumberConversion(16, { unsigned: false });
conversions["unsigned short"] = createNumberConversion(16, { unsigned: true });

conversions["long"] = createNumberConversion(32, { unsigned: false });
conversions["unsigned long"] = createNumberConversion(32, { unsigned: true });

conversions["long long"] = createNumberConversion(32, { unsigned: false, moduloBitLength: 64 });
conversions["unsigned long long"] = createNumberConversion(32, { unsigned: true, moduloBitLength: 64 });

conversions["double"] = function (V) {
    const x = +V;

    if (!Number.isFinite(x)) {
        throw new TypeError("Argument is not a finite floating-point value");
    }

    return x;
};

conversions["unrestricted double"] = function (V) {
    const x = +V;

    if (isNaN(x)) {
        throw new TypeError("Argument is NaN");
    }

    return x;
};

// not quite valid, but good enough for JS
conversions["float"] = conversions["double"];
conversions["unrestricted float"] = conversions["unrestricted double"];

conversions["DOMString"] = function (V, opts) {
    if (opts.treatNullAsEmptyString && V === null) {
        return "";
    }

    return String(V);
};

conversions["ByteString"] = function (V, opts) {
    const x = String(V);
    let c = undefined;
    for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) {
        if (c > 255) {
            throw new TypeError("Argument is not a valid bytestring");
        }
    }

    return x;
};

conversions["USVString"] = function (V) {
    const S = String(V);
    const n = S.length;
    const U = [];
    for (let i = 0; i < n; ++i) {
        const c = S.charCodeAt(i);
        if (c < 0xD800 || c > 0xDFFF) {
            U.push(String.fromCodePoint(c));
        } else if (0xDC00 <= c && c <= 0xDFFF) {
            U.push(String.fromCodePoint(0xFFFD));
        } else {
            if (i === n - 1) {
                U.push(String.fromCodePoint(0xFFFD));
            } else {
                const d = S.charCodeAt(i + 1);
                if (0xDC00 <= d && d <= 0xDFFF) {
                    const a = c & 0x3FF;
                    const b = d & 0x3FF;
                    U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b));
                    ++i;
                } else {
                    U.push(String.fromCodePoint(0xFFFD));
                }
            }
        }
    }

    return U;
};

conversions["Date"] = function (V, opts) {
    if (!(V instanceof Date)) {
        throw new TypeError("Argument is not a Date object");
    }
    if (isNaN(V)) {
        return undefined;
    }

    return V;
};

conversions["RegExp"] = function (V, opts) {
    if (!(V instanceof RegExp)) {
        V = new RegExp(V);
    }

    return V;
};
<commit_msg>Don't require opts argument everywhere<commit_after>"use strict";

var conversions = {};
module.exports = conversions;

function sign(x) {
    return x < 0 ? -1 : 1;
}

function evenRound(x) {
    // Round x to the nearest integer, choosing the even integer if it lies halfway between two.
    if ((x % 1) === 0.5 && (x & 1) === 0) { // [even number].5; round down (i.e. floor)
        return Math.floor(x);
    } else {
        return Math.round(x);
    }
}

function createNumberConversion(bitLength, opts) {
    if (!opts.isUnsigned) {
        --bitLength;
    }
    const lowerBound = opts.isUnsigned ? 0 : -Math.pow(2, bitLength);
    const upperBound = Math.pow(2, bitLength) - 1;

    const moduloVal = opts.moduloBitLength ? Math.pow(2, opts.moduloBitLength) : Math.pow(2, bitLength);
    const moduloBound = opts.moduloBitLength ? Math.pow(2, opts.moduloBitLength - 1) : Math.pow(2, bitLength - 1);

    return function(V, opts) {
        if (!opts) opts = {};

        let x = +V;

        if (opts.enforceRange) {
            if (!Number.isFinite(x)) {
                throw new TypeError("Argument is not a finite number");
            }

            x = sign(x) * Math.floor(Math.abs(x));
            if (x < lowerBound || x > upperBound) {
                throw new TypeError("Argument is not in byte range");
            }

            return x;
        }

        if (!isNaN(x) && opts.clamp) {
            x = evenRound(x);

            if (x < lowerBound) x = lowerBound;
            if (x > upperBound) x = upperBound;
            return x;
        }

        if (!Number.isFinite(x) || x === 0) {
            return 0;
        }

        x = sign(x) * Math.floor(Math.abs(x));
        x = x % moduloVal;

        if (!opts.isUnsigned && x >= moduloBound) {
            return x - moduloVal;
        }

        return x;
    }
}

conversions["void"] = function () {
    return undefined;
};

conversions["boolean"] = function (val) {
    return !!val;
};

conversions["byte"] = createNumberConversion(8, { unsigned: false });
conversions["octet"] = createNumberConversion(8, { unsigned: true });

conversions["short"] = createNumberConversion(16, { unsigned: false });
conversions["unsigned short"] = createNumberConversion(16, { unsigned: true });

conversions["long"] = createNumberConversion(32, { unsigned: false });
conversions["unsigned long"] = createNumberConversion(32, { unsigned: true });

conversions["long long"] = createNumberConversion(32, { unsigned: false, moduloBitLength: 64 });
conversions["unsigned long long"] = createNumberConversion(32, { unsigned: true, moduloBitLength: 64 });

conversions["double"] = function (V) {
    const x = +V;

    if (!Number.isFinite(x)) {
        throw new TypeError("Argument is not a finite floating-point value");
    }

    return x;
};

conversions["unrestricted double"] = function (V) {
    const x = +V;

    if (isNaN(x)) {
        throw new TypeError("Argument is NaN");
    }

    return x;
};

// not quite valid, but good enough for JS
conversions["float"] = conversions["double"];
conversions["unrestricted float"] = conversions["unrestricted double"];

conversions["DOMString"] = function (V, opts) {
    if (!opts) opts = {};

    if (opts.treatNullAsEmptyString && V === null) {
        return "";
    }

    return String(V);
};

conversions["ByteString"] = function (V, opts) {
    const x = String(V);
    let c = undefined;
    for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) {
        if (c > 255) {
            throw new TypeError("Argument is not a valid bytestring");
        }
    }

    return x;
};

conversions["USVString"] = function (V) {
    const S = String(V);
    const n = S.length;
    const U = [];
    for (let i = 0; i < n; ++i) {
        const c = S.charCodeAt(i);
        if (c < 0xD800 || c > 0xDFFF) {
            U.push(String.fromCodePoint(c));
        } else if (0xDC00 <= c && c <= 0xDFFF) {
            U.push(String.fromCodePoint(0xFFFD));
        } else {
            if (i === n - 1) {
                U.push(String.fromCodePoint(0xFFFD));
            } else {
                const d = S.charCodeAt(i + 1);
                if (0xDC00 <= d && d <= 0xDFFF) {
                    const a = c & 0x3FF;
                    const b = d & 0x3FF;
                    U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b));
                    ++i;
                } else {
                    U.push(String.fromCodePoint(0xFFFD));
                }
            }
        }
    }

    return U;
};

conversions["Date"] = function (V, opts) {
    if (!(V instanceof Date)) {
        throw new TypeError("Argument is not a Date object");
    }
    if (isNaN(V)) {
        return undefined;
    }

    return V;
};

conversions["RegExp"] = function (V, opts) {
    if (!(V instanceof RegExp)) {
        V = new RegExp(V);
    }

    return V;
};
<|endoftext|>


<commit_before>var browserFactory = require('./browserFactory.js')
  , listenerFactory = require('./listenerFactory.js')
  , defaultConfig = require('./defaultConfig.js')

function Plugin(cucumber, config) {
    var initPromise = browserFactory(config || defaultConfig)
    initPromise.then(onBrowserInit)
    return initPromise

    function onBrowserInit(browser) {
        var listener = listenerFactory(browser)
        cucumber.attachListener(listener)
    }
}

module.exports = Plugin<commit_msg>updating to use attachListeners instead of passed runtime<commit_after>var browserFactory = require('./browserFactory.js')
  , listenerFactory = require('./listenerFactory.js')
  , defaultConfig = require('./defaultConfig.js')

function Plugin(supportCodeHelper, config) {
    var initPromise = browserFactory(config || defaultConfig)
    initPromise.then(onBrowserInit)
    return initPromise

    function onBrowserInit(browser) {
        var listener = listenerFactory(browser)
        supportCodeHelper.registerListener(listener)
    }
}

module.exports = Plugin<|endoftext|>


<commit_before>var request = require('request');
var cheerio = require('cheerio');
var oftype  = require('oftype');
var color   = require('colors');
var URL		= require('url');
var log4js  = require('log4js');

var config = {};
var logger = {};
var taskQueues = [];
var urlDB = [];

function insertNoRepeat(container, item) {
	if (container.indexOf(item) == -1)
		container.push(item);
}

function fetch(url) {
	insertNoRepeat(urlDB, url);
	var encodedURL = '';
	if (url.indexOf('%') == -1) {
		encodedURL = encodeURI(url);
	}
	else {
		encodedURL = url;
		url = decodeURI(url);
	}
	request(encodedURL, function(err, res, body) {
		if (err || res.statusCode != 200) {
			if (err)
				logger.error("" + err + url);
			else
				logger.warn("" + res.statusCode + "\t" + url);
		} else {
			logger.info("" + res.statusCode + "\t" + url);
			var $ = cheerio.load(body);
			var nextTasks = config.filter(err, res, $);
			var urlObj = URL.parse(url);
			var prefix = urlObj.protocol + '//' + (urlObj.host || urlObj.hostname);
			if (nextTasks != null ) {
				for (var i = 0; i < nextTasks.length; i++) {
					var innerLink = nextTasks[i].split('/').some(function(item) { return item.indexOf('#') != -1} );
					if (!innerLink) {
						var task = URL.resolve(prefix, nextTasks[i]);
						var host = URL.parse(task).host;
						var taskQueue = taskQueues.filter(function(queue) {
							return queue.name === host;
						})[0];
						if (taskQueue)
							insertNoRepeat(taskQueue.tasks, task);
					}
				}
			}
		}
	});
}

function start(end) {
	var intervalID = setInterval(function() {
		// drop empty queue
		taskQueues = taskQueues.filter(function(queue) {
			return queue.tasks.length != 0;
		});
		if (taskQueues.length != 0) {
			var tasks = taskQueues.map(function(queue) {
				return queue.tasks.splice(0, 1)[0];
			});
			tasks.forEach(function(url) {
				var host = URL.parse(url).host;
				if (config.domains.indexOf(host) != -1 && urlDB.indexOf(url) == -1)
					fetch(url);
			});
		} else {
			clearInterval(intervalID);
			logger.info('All tasks has been finished.');
			if (end)
				end();
		}
	}, config.interval);
}

function initialize(options) {
	if (!options.startURLs) {
		logger.error('At least one URL must be specified!');
		return -1;
	}
	if (!options.filter) {
		logger.error('Filter must be specified!');
		return -1;
	}
	config.filter = options.filter;
	config.startURLs = options.startURLs;
	config.domains = options.startURLs.map(function(url) {
		return URL.parse(url).host;
	})
	config.interval = options.interval || 2 * 1000;
	config.silent = options.silent || false;
	config.log = options.log || true;
	config.domains.forEach(function(host) {
		taskQueues[host] = [];
	});
	// push all URLs into taskQueues
	config.startURLs.forEach(function(url) {
		var urlObj = URL.parse(url);
		var queue = new TaskQueue(urlObj.host);
		queue.tasks.push(url);
		taskQueues.push(queue);
	});
	// log or not
	log4js.configure({
		appenders: [
			{ type: 'console', category: 'console'},
			{ type: 'dateFile',
				filename: 'logs/spider.log',
				pattern: 'yyyy-MM-dd',
				category: 'default'
			}
		],
		replaceConsole: true
	});
	if (config.log)  {
		logger = log4js.getLogger('default');
	}
	else
		logger = log4js.getLogger('console');
}

function TaskQueue(host) {
	this.name = host;
	this.tasks = [];
}

module.exports = function(options) {
	this.start = start;
	this.config = config;
	initialize(options);
}
<commit_msg>add timeout to avoid long waiting<commit_after>var request = require('request');
var cheerio = require('cheerio');
var oftype  = require('oftype');
var color   = require('colors');
var URL		= require('url');
var log4js  = require('log4js');

var config = {};
var logger = {};
var taskQueues = [];
var urlDB = [];

function insertNoRepeat(container, item) {
	if (container.indexOf(item) == -1)
		container.push(item);
}

function fetch(url) {
	insertNoRepeat(urlDB, url);
	var encodedURL = '';
	if (url.indexOf('%') == -1) {
		encodedURL = encodeURI(url);
	}
	else {
		encodedURL = url;
		url = decodeURI(url);
	}
	var req = {
		url: url,
		timeout: 10 * 1000
	};
	request(req, function(err, res, body) {
		if (err || res.statusCode != 200) {
			if (err)
				logger.error("" + err + url);
			else
				logger.warn("" + res.statusCode + "\t" + url);
		} else {
			logger.info("" + res.statusCode + "\t" + url);
			var $ = cheerio.load(body);
			var nextTasks = config.filter(err, res, $);
			var urlObj = URL.parse(url);
			var prefix = urlObj.protocol + '//' + (urlObj.host || urlObj.hostname);
			if (nextTasks != null ) {
				for (var i = 0; i < nextTasks.length; i++) {
					var innerLink = nextTasks[i].split('/').some(function(item) { return item.indexOf('#') != -1} );
					if (!innerLink) {
						var task = URL.resolve(prefix, nextTasks[i]);
						var host = URL.parse(task).host;
						var taskQueue = taskQueues.filter(function(queue) {
							return queue.name === host;
						})[0];
						if (taskQueue)
							insertNoRepeat(taskQueue.tasks, task);
					}
				}
			}
		}
	});
}

function start(end) {
	var intervalID = setInterval(function() {
		// drop empty queue
		taskQueues = taskQueues.filter(function(queue) {
			return queue.tasks.length != 0;
		});
		if (taskQueues.length != 0) {
			var tasks = taskQueues.map(function(queue) {
				return queue.tasks.splice(0, 1)[0];
			});
			tasks.forEach(function(url) {
				var host = URL.parse(url).host;
				if (config.domains.indexOf(host) != -1 && urlDB.indexOf(url) == -1)
					fetch(url);
			});
		} else {
			clearInterval(intervalID);
			logger.info('All tasks has been finished.');
			if (end)
				end();
		}
	}, config.interval);
}

function initialize(options) {
	if (!options.startURLs) {
		logger.error('At least one URL must be specified!');
		return -1;
	}
	if (!options.filter) {
		logger.error('Filter must be specified!');
		return -1;
	}
	config.filter = options.filter;
	config.startURLs = options.startURLs;
	config.domains = options.startURLs.map(function(url) {
		return URL.parse(url).host;
	})
	config.interval = options.interval || 2 * 1000;
	config.silent = options.silent || false;
	config.log = options.log || true;
	config.domains.forEach(function(host) {
		taskQueues[host] = [];
	});
	// push all URLs into taskQueues
	config.startURLs.forEach(function(url) {
		var urlObj = URL.parse(url);
		var queue = new TaskQueue(urlObj.host);
		queue.tasks.push(url);
		taskQueues.push(queue);
	});
	// log or not
	log4js.configure({
		appenders: [
			{ type: 'console', category: 'console'},
			{ type: 'dateFile',
				filename: 'logs/spider.log',
				pattern: 'yyyy-MM-dd',
				category: 'default'
			}
		],
		replaceConsole: true
	});
	if (config.log)  {
		logger = log4js.getLogger('default');
	}
	else
		logger = log4js.getLogger('console');
}

function TaskQueue(host) {
	this.name = host;
	this.tasks = [];
}

module.exports = function(options) {
	this.start = start;
	this.config = config;
	initialize(options);
}
<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.7.1
(function() {
  var Request, Response, XHR, cleanURL, createError, delay, extend, factory, once,
    __hasProp = {}.hasOwnProperty,
    __slice = [].slice;

  cleanURL = require('./plugins/cleanurl');

  XHR = require('./xhr');

  delay = require('./delay');

  createError = require('./error').create;

  Response = require('./response');

  Request = require('./request');

  extend = require('xtend');

  once = require('once');

  factory = function(defaults, plugins) {
    var http, method, _fn, _i, _len, _ref;
    if (defaults == null) {
      defaults = {};
    }
    if (plugins == null) {
      plugins = [];
    }
    http = function(req, cb) {
      var done, k, plugin, v, xhr, _i, _j, _len, _len1, _ref;
      req = new Request(extend(defaults, req));
      for (_i = 0, _len = plugins.length; _i < _len; _i++) {
        plugin = plugins[_i];
        if (xhr = typeof plugin.createXHR === "function" ? plugin.createXHR(req) : void 0) {
          break;
        }
      }
      if (xhr == null) {
        xhr = new XHR;
      }
      done = once(delay(function(err) {
        var res, _j, _len1;
        xhr.onload = xhr.onerror = xhr.onreadystatechange = xhr.ontimeout = xhr.onprogress = null;
        res = (err != null ? err.isHttpError : void 0) ? err : new Response(req);
        for (_j = 0, _len1 = plugins.length; _j < _len1; _j++) {
          plugin = plugins[_j];
          if (typeof plugin.processResponse === "function") {
            plugin.processResponse(res);
          }
        }
        return cb(err, res);
      }));
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          switch (Math.floor(xhr.status / 100)) {
            case 2:
              return done();
            case 4:
              if (xhr.status === 404 && !req.errorOn404) {
                return done();
              } else {
                return done(createError('Client Error', req));
              }
              break;
            case 5:
              return done(createError('Server Error', req));
            default:
              return done(createError('HTTP Error', req));
          }
        }
      };
      xhr.onload = function() {
        return done();
      };
      xhr.onerror = function() {
        return done(createError('Internal XHR Error', req));
      };
      xhr.ontimeout = function() {};
      xhr.onprogress = function() {};
      req.xhr = xhr;
      for (_j = 0, _len1 = plugins.length; _j < _len1; _j++) {
        plugin = plugins[_j];
        if (typeof plugin.processRequest === "function") {
          plugin.processRequest(req);
        }
      }
      xhr.open(req.method, req.url);
      _ref = req.headers;
      for (k in _ref) {
        if (!__hasProp.call(_ref, k)) continue;
        v = _ref[k];
        xhr.setRequestHeader(k, v);
      }
      xhr.send(req.body);
      return req;
    };
    _ref = ['get', 'post', 'put', 'head', 'patch', 'delete'];
    _fn = function(http, method) {
      return http[method] = function(req, cb) {
        req = new Request(req);
        req.method = method;
        return http(req, cb);
      };
    };
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      method = _ref[_i];
      _fn(http, method);
    }
    http.plugins = function() {
      return plugins;
    };
    http.defaults = function(newValues) {
      if (newValues) {
        return factory(extend(defaults, newValues), plugins);
      } else {
        return defaults;
      }
    };
    http.use = function() {
      var newPlugins;
      newPlugins = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
      return factory(defaults, plugins.concat(newPlugins));
    };
    http.bare = function() {
      return factory();
    };
    http.Request = Request;
    http.Response = Response;
    return http;
  };

  module.exports = factory({}, [cleanURL]);

}).call(this);
<commit_msg>Rewrite index<commit_after>(function() {

var cleanURL = require('./plugins/cleanurl');
var XHR = require('./xhr');
var delay = require('./delay');
var createError = require('./error').create;
var Response = require('./response');
var Request = require('./request');
var extend = require('xtend');
var once = require('once');

function factory(defaults, plugins) {
  defaults = defaults || {};
  plugins = plugins || [];

  function http(req, cb) {
    var xhr, plugin, i, done, k, v;

    req = new Request(extend(defaults, req));

    for (i = 0; i < plugins.length; i++) {
      plugin = plugins[i];
      if (plugin.createXHR) {
        xhr = plugin.createXHR(req);
        break;
      }
    }
    xhr = xhr || new XHR;

    done = once(delay(function(err) {
      xhr.onload = xhr.onerror = xhr.onreadystatechange = xhr.ontimeout = xhr.onprogress = null;
      var res = err && err.isHttpError ? err : new Response(req);
      for (i = 0; i < plugins.length; i++) {
        plugin = plugins[i];
        if (plugin.processResponse) {
          plugin.processResponse(res);
        }
      }
      cb(err, res);
    }));

    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        var type = Math.floor(xhr.status / 100);
        if (type === 2) {
          done();
        } else if (xhr.status === 404 && !req.errorOn404) {
          done();
        } else if (type === 4) {
          done(createError('Client Error', req));
        } else if (type === 5) {
          done(createError('Server Error', req));
        } else {
          done(createError('HTTP Error', req));
        }
      }
    };

    xhr.onload = function() { done(); };

    xhr.onerror = function() {
      done(createError('Internal XHR Error', req));
    };

    xhr.ontimeout = function() {};

    xhr.onprogress = function() {};

    req.xhr = xhr;

    for (i = 0; i < plugins.length; i++) {
      plugin = plugins[i];
      if (plugin.processRequest) {
        plugin.processRequest(req);
      }
    }

    xhr.open(req.method, req.url);

    for (k in req.headers) {
      if (!req.headers.hasOwnProperty(k)) { continue; }
      xhr.setRequestHeader(k, req.headers[k]);
    }

    xhr.send(req.body);

    return req;
  };

  var methods = ['get', 'post', 'put', 'head', 'patch', 'delete'];
  for (i = 0; i < methods.length; i++) {
    var method = methods[i];
    http[method] = (function(method) {
      return function(req, cb) {
        req = new Request(req);
        req.method = method;
        return http(req, cb);
      };
    }(method));
  }

  http.plugins = function() {
    return plugins;
  };

  http.defaults = function(newValues) {
    if (newValues) {
      return factory(extend(defaults, newValues), plugins);
    } else {
      return defaults;
    }
  };

  http.use = function() {
    var newPlugins = Array.prototype.slice.call(arguments, 0);
    return factory(defaults, plugins.concat(newPlugins));
  };

  http.bare = function() {
    return factory();
  };

  http.Request = Request;
  http.Response = Response;

  return http;
};

module.exports = factory({}, [cleanURL]);

}());
<|endoftext|>