<commit_before>// NML parser
// Turns NML code into an AST, for the vm to run

var ObjectId = require('mongodb').ObjectId;

var PREPOSITIONS = [
  'with', 'using', 'at', 'to', 'in front of', 'in', 'inside', 'into', 'on top of',
  'on', 'onto', 'upon', 'out of', 'from inside', 'from', 'over', 'through', 'under',
  'underneath', 'beneath', 'behind', 'beside', 'for', 'about', 'is', 'as', 'off',
  'off of'
];
var CONDITIONAL_COMPARATORS = ['>', '==', '<', '!=', 'in'];
var CONDITIONAL_JOINS       = ['and', 'or'];
// in: object is in another
//     object is in list
// eg. 'person in car' is true if person is a child of car
// eg. '1 in [1, 2, 3]' is true
var INDEX_SPECIFIABLE = ['array', 'var', 'prop'];
var ASSIGNABLE_TYPES = ['var', 'prop'];
var ASSIGNMENT_OPERATORS = ['=', '+=', '-=', '/=', '*='];

var Parser = {};

// Breaks a line up into it's simple components
Parser.parseLine = function(line) {
  // First, create the components array
  var components = [];
  var ctxStack = [{type: 'root', ctx: components}];

  // We'll start by creating string literals
  // String literals are stated by single quotes, nothing else
  var state = null;
  var buffer = '';

  // Termination functions
  var term = {
    fluid: function() {
      if(buffer === 'true' || buffer === 'false') { // Parse bools
        ctxStack.slice(-1)[0].ctx.push(buffer === 'true');
      } else if(buffer === 'null') {
        ctxStack.slice(-1)[0].ctx.push({type: 'null', value: null});
      } else if(!isNaN(parseInt(buffer))) {
        ctxStack.slice(-1)[0].ctx.push(parseInt(buffer));
      } else {
        ctxStack.slice(-1)[0].ctx.push(buffer);
      }
      state = null; // Go back to the default state;
    },
    stringlit: function() {
      ctxStack.slice(-1)[0].ctx.push(buffer);
      state = null;
    },
    objectid: function() {
      ctxStack.slice(-1)[0].ctx.push(new ObjectId(buffer));
      state = null;
    },
    objectidalias: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'objectIdAlias', value: buffer});
      state = null;
    },
    var: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'var', name: buffer});
      state = null;
    },
    prop: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'prop', name: buffer});
      state = null;
    },
    comment: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'comment', text: buffer});
      state = null;
    },
    null: function() {}
  }

  // ssss state machine whoop whoop
  for(var i = 0; i < line.length; i++) {
    switch(state) {
      case 'stringlit':
        if(line[i] === '\'' && line[i - 1] !== '\\') {
          term.stringlit();
        } else {
          buffer += line[i];
        }
        break;
      case 'fluid':
        if( line[i] === ' ' || // Spaces will terminate the fluid string
           (line[i] === ']' && line[i - 1] !== '\\') || // If a close-array is encounted and it's not escaped, stop the fluid string
           (line[i] === '(' && line[i - 1] !== '\\') || // If there is an open parens and it wasn't escaped, it's probably method arguments
           (line[i] === ')' && line[i - 1] !== '\\')    // If there is a closeparens and it wasn't escaped, terminate and ignore it.
          ) {
          i--; // Rewind a character so the space can be interpreted below
          term[state]();
        } else {
          buffer += line[i];
        }
        break;
      case 'var':
      case 'prop':
        if( line[i] == ' ' ||
           (line[i] === ']' && line[i - 1] !== '\\')
          ) {
          i--; // Rewind so the specifier can be closed, if it exists (that check is done down below)
          term[state]();
        } else if(line[i] == '[') {
          // Go onto the context stack from here, as we need to wait for the
          // close bracket. This will also allow us to use variables inside the
          // index specifier
          ctxStack.push({type: state, name: buffer, ctx: []});
          state = null; // Set state to null
        } else {
          buffer += line[i];
        }
        break;
      case 'objectid':
      case 'objectidalias':
        if(line[i] == ' ') {
          term[state]();
        } else {
          buffer += line[i];
        }
        break;
      case 'comment':
        // Comments cannot be broken out of
        buffer += line[i];
        break;
      default:
        buffer = '';
        // Find the next state
        switch(line[i]) {
          case '\'': // String literal
            state = 'stringlit';
            break;
          case '$': // Local var
            state = 'var';
            break;
          case '%': // Object property
            state = 'prop';
            break;
          case '#': // ObjectId or Object Id Alias
            if(line[i + 1] == '#') { // ## for ObjectId alises - eg. ##ROOT
              state = 'objectidalias';
              i++; // Skip the extra hash
            } else {
              state = 'objectid';
            }
            break;
          case '[': // Open array/list
            // We don't change state as we are still interpreting items
            // However, we push to a 'context' stack, so new items go in our
            // array and not the general components array.
            ctxStack.push({type: 'array', ctx: []}); // Push a new array onto the context stack
            break;
          case '(': // Parens
          case ')': // Ignore them, they aren't currently being used for grouping expressions
            break;
          case ',': // The same deal with commas, we mainly use spaces for separating parameters and elements
            break;
          case ']': // Close array/list
            if(INDEX_SPECIFIABLE.indexOf(ctxStack.slice(-1)[0].type) < 0) throw 'last opened context in line was not an array or array index specifier';
            // Merge the top context down into the context below it
            ctxStack.slice(-2)[0].ctx.push(ctxStack.slice(-1)[0]);
            // Pop off the array from the context stack
            ctxStack.pop();
            break;
          case ';': // Comment
            state = 'comment';
            break;
          case ' ':
            // Don't really do anything on spaces
            break;
          case '\\':
            state = 'fluid';
            // \ is a prefix for the fluid state
            // so \$var will give a fluidstring of $var
            break;
          default:
            state = 'fluid';
            i--; // Go back a character so the fluid state can interpret it
        }
    }
  }

  term[state](); // Terminate the current state

  return components;
}

// Gets conditions out of parsed line. For if, while, etc.
Parser.conditionsFromParsedLine = function(line) {
  var conditions = [];
  var i = 0;
  while(i < line.length) {
    if(line[i]   === undefined) throw 'conditions usage: <statement> <thing1> [<comparator> <thing2>] [join]; <thing1> missing';
    var condition = {
      left: line[i],
      comparator: null,
      right: null,
      join: null
    };

    if(line[i+1] === undefined || CONDITIONAL_JOINS.indexOf(line[i+1]) >= 0) {
      condition.comparator = '==';
      condition.right = true;
      condition.join = line[i+1] || null;
      i += 2;
    } else {
      if(line[i+2] === undefined) throw 'conditions usage: <statement> <thing1> [<comparator> <thing2>] [join]; <thing2> missing';
      if(CONDITIONAL_COMPARATORS.indexOf(line[i+1]) < 0) throw 'conditions usage: <statement> <thing1> [<comparator> <thing2>] [join]; <comparator> not valid';
      if(line[i+3] !== undefined && CONDITIONAL_JOINS.indexOf(line[i+3]) < 0) throw 'conditions usage: <statement> <thing1> [<comparator> <thing2>] [join]; [join] not valid';

      condition.comparator = line[i+1];
      condition.right = line[i+2];
      condition.join = line[i+3] || null;
      i += 4;
    }

    conditions.push(condition);
  }
  if(conditions.length < 1) throw 'conditions usage: <statement> <thing1> <comparator> <thing2>';

  return conditions;
}

// Finds a prepoistion in a parsed line
Parser.prepositionsFromParsedLine = function(line) {
  var prepositions = [];
  for(var prepos of PREPOSITIONS) {
    prepos = prepos.split(' '); // Split the prepositions up on the spaces
    var found = false;
    for(var iLine = 0; iLine < line.length; iLine++) {
      found = false;
      for(var iPrepos = 0; iPrepos < prepos.length; iPrepos++) {
        if(line[iLine + iPrepos] !== prepos[iPrepos]) { found = false; break; }
        found = true;
      }
      if(found) { found = iLine; break; }
    }
    if(found !== false) {
      prepositions.push({
        prepos: prepos.join(' '),
        size: prepos.length,
        found: found
      });
    }
  }

  // Run through the prepositions array and delete duplicates
  for(var prepos of prepositions) {
    for(var p of prepositions) {
      if(p.found === prepos.found) {
        // Delete the smaller one
        if(p.size > prepos.size) prepositions.splice(prepositions.indexOf(prepos), 1);
        if(p.size < prepos.size) prepositions.splice(prepositions.indexOf(p), 1);
      }
    }
  }

  return prepositions;
}

// Loads a block of written NML code and translates it to AST, returns it
// ready for load into a vm or storage
Parser.codeToAst = function(code) {
  var ast = [];
  var lines = code.split("\n");
  var blockStack = [ast]; // Code is added to the last element on this
                          // array/stack.

  // Iterate over each line of code
  for(var lineNo in lines) {
    var line = Parser.parseLine(lines[lineNo].trim());
    if(line.length < 1) continue; // Skip line, it's empty
    var commentExists = line.slice(-1)[0].type === 'comment';
    var err = function(str) {
      throw 'line ' + parseInt(lineNo + 1) + ': ' + str;
    };
    // Step 1- Is this a function declaration, if statement, or any structure
    //         that begins with a keyword. 'Is it a keyword?'
    //    How- Split it on spaces, look at the first word

    var initialKeyword = line[0];
    switch(initialKeyword) {
      case 'end':
        if(blockStack.length < 2) err('no blocks to end (no blocks to end on stack)');
        blockStack.pop();
        continue;
      case 'while':
      case 'if':
        // Form if/while statement
        // Use same code for both, from ast point of view they're the same
        try {
          var conditions = Parser.conditionsFromParsedLine(line.slice(1, line.length));
        } catch(errstr) {
          // Throw up any error given to us from the conditional parser
          err(errstr);
        }
        // Alright, all verified, push the statement onto the AST and the new block onto the stack
        var newBlock = [];
        var statement = {
          type: initialKeyword,
          conditions: conditions,
          block: newBlock
        };
        blockStack.slice(-1)[0].push(statement);
        blockStack.push(newBlock);
        continue;
    }

    // Step 2- Is this a declaration/assignment? Check for ASSIGNMENT_OPERATORS in line[1];
    if(ASSIGNMENT_OPERATORS.indexOf(line[1]) >= 0) {
      if(line[2] === undefined || line[2].type === 'comment') err('an assignment must have two sides');
      if(ASSIGNABLE_TYPES.indexOf(line[0].type) < 0) err('type on left of assignment cannot be set');
      // All good here, add it to the current block
      var stmt = {
        type: 'assign',
        op: line[1][0],
        src: line.slice(2, line.length - (commentExists ? 1 : 0)),
        dst: line[0]
      };
      // if line[2] is a string, join the rest of the line together for the src
      // TODO This implementation is bad, makes no sense
      //      Comments are included
      //if(typeof(stmt.src) === 'string') stmt.src = line.slice(2, line.length).join(' ');
      // Push it onto the current block
      blockStack.slice(-1)[0].push(stmt);
      continue;
    }

    // Step 3- This is a statement of some kind. Figure out what it is and put it
    //         in the current block.
    //    How- Find a preposition if it exists

    // Searching for the preposition is a fair bit of effort, as they are split up across multiple words
    var preposList = Parser.prepositionsFromParsedLine(line);
    // Delete any prepositions in the line at index 1
    if(preposList.length > 0 && preposList[0].found === 1) {
      var preposRemove = preposList[0];
      line.splice(1, preposRemove.size);
      // Offset all the prepositions in the list as the indexes have changed
      for(var prepos of preposList) {
        prepos.found -= preposRemove.size;
      }
      preposList.splice(0, 1);
    }

    // Find the verb, direct object, prepos and indirect object, if they exist
    var verb = line[0];
    var directObj = null;
    var prepos = null;
    var indirectObj = null;
    if(line.length > 1) { // There is a direct object
      // Is there a preposition
      if(preposList.length > 0) {
        directObj = line.slice(1, preposList[0].found);
        prepos = preposList[0].prepos;
        indirectObj = line.slice(preposList[0].found + preposList[0].size, line.length);
      } else {
        directObj = line.slice().splice(1, line.length);
      }
    }

    // Push the statement onto the block stack
    blockStack.slice(-1)[0].push({
      type: 'verbcall',
      verb: verb,
      directObj: directObj,
      prepos: prepos,
      indirectObj: indirectObj,
      params: line.slice(1, line.length) // Include the original parameter list, as some commands may need it
    });
  }

  if(blockStack.length > 1) throw (blockStack.length - 1) + ' block(s) are still open.';

  return ast;
}

module.exports = Parser;
<commit_msg>cleanup conditional parsing by just including a vague 'expr'<commit_after>// NML parser
// Turns NML code into an AST, for the vm to run

var ObjectId = require('mongodb').ObjectId;

var PREPOSITIONS = [
  'with', 'using', 'at', 'to', 'in front of', 'in', 'inside', 'into', 'on top of',
  'on', 'onto', 'upon', 'out of', 'from inside', 'from', 'over', 'through', 'under',
  'underneath', 'beneath', 'behind', 'beside', 'for', 'about', 'is', 'as', 'off',
  'off of'
];
var CONDITIONAL_COMPARATORS = ['>', '==', '<', '!=', 'in'];
var CONDITIONAL_JOINS       = ['and', 'or'];
// in: object is in another
//     object is in list
// eg. 'person in car' is true if person is a child of car
// eg. '1 in [1, 2, 3]' is true
var INDEX_SPECIFIABLE = ['array', 'var', 'prop'];
var ASSIGNABLE_TYPES = ['var', 'prop'];
var ASSIGNMENT_OPERATORS = ['=', '+=', '-=', '/=', '*='];

var Parser = {};

// Breaks a line up into it's simple components
Parser.parseLine = function(line) {
  // First, create the components array
  var components = [];
  var ctxStack = [{type: 'root', ctx: components}];

  // We'll start by creating string literals
  // String literals are stated by single quotes, nothing else
  var state = null;
  var buffer = '';

  // Termination functions
  var term = {
    fluid: function() {
      if(buffer === 'true' || buffer === 'false') { // Parse bools
        ctxStack.slice(-1)[0].ctx.push(buffer === 'true');
      } else if(buffer === 'null') {
        ctxStack.slice(-1)[0].ctx.push({type: 'null', value: null});
      } else if(!isNaN(parseInt(buffer))) {
        ctxStack.slice(-1)[0].ctx.push(parseInt(buffer));
      } else {
        ctxStack.slice(-1)[0].ctx.push(buffer);
      }
      state = null; // Go back to the default state;
    },
    stringlit: function() {
      ctxStack.slice(-1)[0].ctx.push(buffer);
      state = null;
    },
    objectid: function() {
      ctxStack.slice(-1)[0].ctx.push(new ObjectId(buffer));
      state = null;
    },
    objectidalias: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'objectIdAlias', value: buffer});
      state = null;
    },
    var: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'var', name: buffer});
      state = null;
    },
    prop: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'prop', name: buffer});
      state = null;
    },
    comment: function() {
      ctxStack.slice(-1)[0].ctx.push({type: 'comment', text: buffer});
      state = null;
    },
    null: function() {}
  }

  // ssss state machine whoop whoop
  for(var i = 0; i < line.length; i++) {
    switch(state) {
      case 'stringlit':
        if(line[i] === '\'' && line[i - 1] !== '\\') {
          term.stringlit();
        } else {
          buffer += line[i];
        }
        break;
      case 'fluid':
        if( line[i] === ' ' || // Spaces will terminate the fluid string
           (line[i] === ']' && line[i - 1] !== '\\') || // If a close-array is encounted and it's not escaped, stop the fluid string
           (line[i] === '(' && line[i - 1] !== '\\') || // If there is an open parens and it wasn't escaped, it's probably method arguments
           (line[i] === ')' && line[i - 1] !== '\\')    // If there is a closeparens and it wasn't escaped, terminate and ignore it.
          ) {
          i--; // Rewind a character so the space can be interpreted below
          term[state]();
        } else {
          buffer += line[i];
        }
        break;
      case 'var':
      case 'prop':
        if( line[i] == ' ' ||
           (line[i] === ']' && line[i - 1] !== '\\')
          ) {
          i--; // Rewind so the specifier can be closed, if it exists (that check is done down below)
          term[state]();
        } else if(line[i] == '[') {
          // Go onto the context stack from here, as we need to wait for the
          // close bracket. This will also allow us to use variables inside the
          // index specifier
          ctxStack.push({type: state, name: buffer, ctx: []});
          state = null; // Set state to null
        } else {
          buffer += line[i];
        }
        break;
      case 'objectid':
      case 'objectidalias':
        if(line[i] == ' ') {
          term[state]();
        } else {
          buffer += line[i];
        }
        break;
      case 'comment':
        // Comments cannot be broken out of
        buffer += line[i];
        break;
      default:
        buffer = '';
        // Find the next state
        switch(line[i]) {
          case '\'': // String literal
            state = 'stringlit';
            break;
          case '$': // Local var
            state = 'var';
            break;
          case '%': // Object property
            state = 'prop';
            break;
          case '#': // ObjectId or Object Id Alias
            if(line[i + 1] == '#') { // ## for ObjectId alises - eg. ##ROOT
              state = 'objectidalias';
              i++; // Skip the extra hash
            } else {
              state = 'objectid';
            }
            break;
          case '[': // Open array/list
            // We don't change state as we are still interpreting items
            // However, we push to a 'context' stack, so new items go in our
            // array and not the general components array.
            ctxStack.push({type: 'array', ctx: []}); // Push a new array onto the context stack
            break;
          case '(': // Parens
          case ')': // Ignore them, they aren't currently being used for grouping expressions
            break;
          case ',': // The same deal with commas, we mainly use spaces for separating parameters and elements
            break;
          case ']': // Close array/list
            if(INDEX_SPECIFIABLE.indexOf(ctxStack.slice(-1)[0].type) < 0) throw 'last opened context in line was not an array or array index specifier';
            // Merge the top context down into the context below it
            ctxStack.slice(-2)[0].ctx.push(ctxStack.slice(-1)[0]);
            // Pop off the array from the context stack
            ctxStack.pop();
            break;
          case ';': // Comment
            state = 'comment';
            break;
          case ' ':
            // Don't really do anything on spaces
            break;
          case '\\':
            state = 'fluid';
            // \ is a prefix for the fluid state
            // so \$var will give a fluidstring of $var
            break;
          default:
            state = 'fluid';
            i--; // Go back a character so the fluid state can interpret it
        }
    }
  }

  term[state](); // Terminate the current state

  return components;
}

// Finds a prepoistion in a parsed line
Parser.prepositionsFromParsedLine = function(line) {
  var prepositions = [];
  for(var prepos of PREPOSITIONS) {
    prepos = prepos.split(' '); // Split the prepositions up on the spaces
    var found = false;
    for(var iLine = 0; iLine < line.length; iLine++) {
      found = false;
      for(var iPrepos = 0; iPrepos < prepos.length; iPrepos++) {
        if(line[iLine + iPrepos] !== prepos[iPrepos]) { found = false; break; }
        found = true;
      }
      if(found) { found = iLine; break; }
    }
    if(found !== false) {
      prepositions.push({
        prepos: prepos.join(' '),
        size: prepos.length,
        found: found
      });
    }
  }

  // Run through the prepositions array and delete duplicates
  for(var prepos of prepositions) {
    for(var p of prepositions) {
      if(p.found === prepos.found) {
        // Delete the smaller one
        if(p.size > prepos.size) prepositions.splice(prepositions.indexOf(prepos), 1);
        if(p.size < prepos.size) prepositions.splice(prepositions.indexOf(p), 1);
      }
    }
  }

  return prepositions;
}

// Loads a block of written NML code and translates it to AST, returns it
// ready for load into a vm or storage
Parser.codeToAst = function(code) {
  var ast = [];
  var lines = code.split("\n");
  var blockStack = [ast]; // Code is added to the last element on this
                          // array/stack.

  // Iterate over each line of code
  for(var lineNo in lines) {
    var line = Parser.parseLine(lines[lineNo].trim());
    if(line.length < 1) continue; // Skip line, it's empty
    var commentExists = line.slice(-1)[0].type === 'comment';
    var err = function(str) {
      throw 'line ' + parseInt(lineNo + 1) + ': ' + str;
    };
    // Step 1- Is this a function declaration, if statement, or any structure
    //         that begins with a keyword. 'Is it a keyword?'
    //    How- Split it on spaces, look at the first word

    var initialKeyword = line[0];
    switch(initialKeyword) {
      case 'end':
        if(blockStack.length < 2) err('no blocks to end (no blocks to end on stack)');
        blockStack.pop();
        continue;
      case 'while':
      case 'if':
        // Form if/while statement
        // Use same code for both, from ast point of view they're the same
        var statement = {
          type: initialKeyword,
          expr: line.slice(1, line.length - (commentExists ? 1 : 0)),
          block: []
        };
        if(statement.expr.length < 1) err(initialKeyword + ' usage: ' + initialKeyword + ' <expr>');
        blockStack.slice(-1)[0].push(statement);
        blockStack.push(statement.block);
        continue;
    }

    // Step 2- Is this a declaration/assignment? Check for ASSIGNMENT_OPERATORS in line[1];
    if(ASSIGNMENT_OPERATORS.indexOf(line[1]) >= 0) {
      if(line[2] === undefined || line[2].type === 'comment') err('an assignment must have two sides');
      if(ASSIGNABLE_TYPES.indexOf(line[0].type) < 0) err('type on left of assignment cannot be set');
      // All good here, add it to the current block
      var stmt = {
        type: 'assign',
        op: line[1][0],
        src: line.slice(2, line.length - (commentExists ? 1 : 0)),
        dst: line[0]
      };
      // if line[2] is a string, join the rest of the line together for the src
      // TODO This implementation is bad, makes no sense
      //      Comments are included
      //if(typeof(stmt.src) === 'string') stmt.src = line.slice(2, line.length).join(' ');
      // Push it onto the current block
      blockStack.slice(-1)[0].push(stmt);
      continue;
    }

    // Step 3- This is a statement of some kind. Figure out what it is and put it
    //         in the current block.
    //    How- Find a preposition if it exists

    // Searching for the preposition is a fair bit of effort, as they are split up across multiple words
    var preposList = Parser.prepositionsFromParsedLine(line);
    // Delete any prepositions in the line at index 1
    if(preposList.length > 0 && preposList[0].found === 1) {
      var preposRemove = preposList[0];
      line.splice(1, preposRemove.size);
      // Offset all the prepositions in the list as the indexes have changed
      for(var prepos of preposList) {
        prepos.found -= preposRemove.size;
      }
      preposList.splice(0, 1);
    }

    // Find the verb, direct object, prepos and indirect object, if they exist
    var verb = line[0];
    var directObj = null;
    var prepos = null;
    var indirectObj = null;
    if(line.length > 1) { // There is a direct object
      // Is there a preposition
      if(preposList.length > 0) {
        directObj = line.slice(1, preposList[0].found);
        prepos = preposList[0].prepos;
        indirectObj = line.slice(preposList[0].found + preposList[0].size, line.length);
      } else {
        directObj = line.slice().splice(1, line.length);
      }
    }

    // Push the statement onto the block stack
    blockStack.slice(-1)[0].push({
      type: 'verbcall',
      verb: verb,
      directObj: directObj,
      prepos: prepos,
      indirectObj: indirectObj,
      params: line.slice(1, line.length) // Include the original parameter list, as some commands may need it
    });
  }

  if(blockStack.length > 1) throw (blockStack.length - 1) + ' block(s) are still open.';

  return ast;
}

module.exports = Parser;
<|endoftext|>


<commit_before>require('chromedriver');
var webdriver = require('selenium-webdriver');
var browser = new webdriver.Builder()
  .forBrowser('chrome')
  .build();

// Parse jsunit html report, exit(1) if there are any failures.
var testHtml = function (htmlString) {
  var regex = /[\d]+\spassed,\s([\d]+)\sfailed./i;
  var numOfFailure = regex.exec(htmlString)[1];
  var regex2 = /Unit Tests for .*]/;
  var testStatus = regex2.exec(htmlString)[0];
  console.log("============Unit Test Summary=================");
  console.log(testStatus);
  var regex3 = /\d+ passed,\s\d+ failed/;
  var detail = regex3.exec(htmlString)[0];
  console.log(detail);
  console.log("============Unit Test Summary=================");
  if (parseInt(numOfFailure) !== 0) {
    console.log(htmlString);
    process.exit(1);
  }
};

var path = process.cwd();

var runTests = async function () {
  var element, text;

  await browser.get("file://" + path + "/tests/jsunit/vertical_tests.html");
  await browser.sleep(5000);
  element = await browser.findElement({id: "closureTestRunnerLog"});
  text = await element.getText();
  testHtml(text);

  await browser.get("file://" + path + "/tests/jsunit/horizontal_tests.html");
  await browser.sleep(5000);
  element = await browser.findElement({id: "closureTestRunnerLog"});
  text = await element.getText();
  testHtml(text);
};

runTests().catch(e => {
    console.error(e);
    process.exit(1);
});
<commit_msg>fix(test): quit Selenium session after tests<commit_after>require('chromedriver');
var webdriver = require('selenium-webdriver');
var browser = new webdriver.Builder()
  .forBrowser('chrome')
  .build();

// Parse jsunit html report, exit(1) if there are any failures.
var testHtml = function (htmlString) {
  var regex = /[\d]+\spassed,\s([\d]+)\sfailed./i;
  var numOfFailure = regex.exec(htmlString)[1];
  var regex2 = /Unit Tests for .*]/;
  var testStatus = regex2.exec(htmlString)[0];
  console.log("============Unit Test Summary=================");
  console.log(testStatus);
  var regex3 = /\d+ passed,\s\d+ failed/;
  var detail = regex3.exec(htmlString)[0];
  console.log(detail);
  console.log("============Unit Test Summary=================");
  if (parseInt(numOfFailure) !== 0) {
    console.log(htmlString);
    process.exit(1);
  }
};

var path = process.cwd();

var runTests = async function () {
  try {
    var element, text;

    await browser.get("file://" + path + "/tests/jsunit/vertical_tests.html");
    await browser.sleep(5000);
    element = await browser.findElement({id: "closureTestRunnerLog"});
    text = await element.getText();
    testHtml(text);

    await browser.get("file://" + path + "/tests/jsunit/horizontal_tests.html");
    await browser.sleep(5000);
    element = await browser.findElement({id: "closureTestRunnerLog"});
    text = await element.getText();
    testHtml(text);
  }
  finally {
    await browser.quit();
  }
};

runTests().catch(e => {
  console.error(e);
  process.exit(1);
});
<|endoftext|>


<commit_before><commit_msg>fix: return the notifications in a new promise instead of the combined promise<commit_after><|endoftext|>


<commit_before>/*
* Hangman chat plugin
* By bumbadadabum and Zarel. Art by crobat.
*/

'use strict';

const maxMistakes = 6;

class Hangman extends Rooms.RoomGame {
	constructor(room, user, word, hint) {
		super(room);

		if (room.gameNumber) {
			room.gameNumber++;
		} else {
			room.gameNumber = 1;
		}

		this.gameid = 'hangman';
		this.title = 'Hangman';
		this.creator = user.userid;
		this.word = word;
		this.hint = hint;
		this.incorrectGuesses = 0;

		this.guesses = [];
		this.letterGuesses = [];
		this.lastGuesser = '';
		this.wordSoFar = [];

		for (const letter of word) {
			if (/[a-zA-Z]/.test(letter)) {
				this.wordSoFar.push('_');
			} else {
				this.wordSoFar.push(letter);
			}
		}
	}

	guess(word, user) {
		if (user.userid === this.room.game.creator) return user.sendTo(this.room, "You can't guess in your own hangman game.");

		let sanitized = word.replace(/[^A-Za-z ]/g, '');
		let normalized = toId(sanitized);
		if (normalized.length < 1) return user.sendTo(this.room, "Guess too short.");
		if (sanitized.length > 30) return user.sendTo(this.room, "Guess too long.");

		for (const guessid of this.guesses) {
			if (normalized === toId(guessid)) return user.sendTo(this.room, "Your guess has already been guessed.");
		}

		if (sanitized.length > 1) {
			if (!this.guessWord(sanitized, user.name)) {
				user.sendTo(this.room, "Invalid guess");
			} else {
				this.room.send(user.name + " guessed \"" + sanitized + "\"!");
			}
		} else {
			if (!this.guessLetter(sanitized, user.name)) {
				user.sendTo(this.room, "Invalid guess");
			}
		}
	}

	guessLetter(letter, guesser) {
		letter = letter.toUpperCase();
		if (this.guesses.includes(letter)) return false;
		if (this.word.toUpperCase().includes(letter)) {
			for (let i = 0; i < this.word.length; i++) {
				if (this.word[i].toUpperCase() === letter) {
					this.wordSoFar[i] = this.word[i];
				}
			}

			if (!this.wordSoFar.includes('_')) {
				this.incorrectGuesses = -1;
				this.guesses.push(letter);
				this.letterGuesses.push(letter + '1');
				this.lastGuesser = guesser;
				this.finish();
				return true;
			}
			this.letterGuesses.push(letter + '1');
		} else {
			this.incorrectGuesses++;
			this.letterGuesses.push(letter + '0');
		}

		this.guesses.push(letter);
		this.lastGuesser = guesser;
		this.update();
		return true;
	}

	guessWord(word, guesser) {
		let ourWord = toId(this.word);
		let guessedWord = toId(word);
		if (ourWord === guessedWord) {
			for (let [i, letter] of this.wordSoFar.entries()) {
				if (letter === '_') {
					letter = this.word[i];
				}
			}
			this.incorrectGuesses = -1;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.finish();
			return true;
		} else if (ourWord.length === guessedWord.length) {
			this.incorrectGuesses++;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.update();
			return true;
		}
		return false;
	}

	hangingMan() {
		return '';
	}

	generateWindow() {
		let result = 0;

		if (this.incorrectGuesses === maxMistakes) {
			result = 1;
		} else if (!this.wordSoFar.includes('_')) {
			result = 2;
		}

		let output = '
';
		output += '
' + (result === 1 ? 'Too bad! The mon has been hanged.' : (result === 2 ? 'The word has been guessed. Congratulations!' : 'Hangman')) + '
';
		output += '

' + this.hangingMan() + '	';

		let wordString = this.wordSoFar.join('');
		if (result === 1) {
			let word = this.word;
			wordString = wordString.replace(/_+/g, (match, offset) =>
				'' + word.substr(offset, match.length) + ''
			);
		}

		if (this.hint) output += '
(Hint: ' + Chat.escapeHTML(this.hint) + ')
';
		output += '
' + wordString + '
';
		if (this.guesses.length) {
			if (this.letterGuesses.length) {
				output += 'Letters: ' + this.letterGuesses.map(g =>
					'' + Chat.escapeHTML(g[0]) + ''
				).join(', ');
			}
			if (result === 2) {
				output += '
Winner: ' + Chat.escapeHTML(this.lastGuesser);
			} else if (this.guesses[this.guesses.length - 1].length === 1) {
				// last guess was a letter
				output += ' – ' + Chat.escapeHTML(this.lastGuesser) + '';
			} else {
				output += '
Guessed: ' + this.guesses[this.guesses.length - 1] + ' – ' + Chat.escapeHTML(this.lastGuesser) + '';
			}
		}

		output += '

';

		return output;
	}

	display(user, broadcast) {
		if (broadcast) {
			this.room.add('|uhtml|hangman' + this.room.gameNumber + '|' + this.generateWindow());
		} else {
			user.sendTo(this.room, '|uhtml|hangman' + this.room.gameNumber + '|' + this.generateWindow());
		}
	}

	update() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|' + this.generateWindow());

		if (this.incorrectGuesses === maxMistakes) {
			this.finish();
		}
	}

	end() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|
(The game of hangman was ended.)
');
		this.room.add("The game of hangman was ended.");
		delete this.room.game;
	}

	finish() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|
(The game of hangman has ended – scroll down to see the results)
');
		this.room.add('|html|' + this.generateWindow());
		delete this.room.game;
	}
}

exports.commands = {
	hangman: {
		create: 'new',
		new: function (target, room, user) {
			let params = target.split(',');

			if (!this.can('minigame', null, room)) return false;
			if (room.hangmanDisabled) return this.errorReply("Hangman is disabled for this room.");
			if (!this.canTalk()) return;
			if (room.game) return this.errorReply("There is already a game of " + room.game.title + " in progress in this room.");

			if (!params) return this.errorReply("No word entered.");
			let word = params[0].replace(/[^A-Za-z '-]/g, '');
			if (word.replace(/ /g, '').length < 1) return this.errorReply("Enter a valid word");
			if (word.length > 30) return this.errorReply("Phrase must be less than 30 characters.");
			if (word.split(' ').some(w => w.length > 20)) return this.errorReply("Each word in the phrase must be less than 20 characters.");
			if (!/[a-zA-Z]/.test(word)) return this.errorReply("Word must contain at least one letter.");

			let hint;
			if (params.length > 1) {
				hint = params.slice(1).join(',').trim();
				if (hint.length > 150) return this.errorReply("Hint too long.");
			}

			room.game = new Hangman(room, user, word, hint);
			room.game.display(user, true);

			return this.privateModCommand("(A game of hangman was started by " + user.name + ".)");
		},
		createhelp: ["/hangman create [word], [hint] - Makes a new hangman game. Requires: % @ * # & ~"],

		guess: function (target, room, user) {
			if (!target) return this.parse('/help guess');
			if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");
			if (!this.canTalk()) return;

			room.game.guess(target, user);
		},
		guesshelp: [
			`/hangman guess [letter] - Makes a guess for the letter entered.`,
			`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
		],

		stop: 'end',
		end: function (target, room, user) {
			if (!this.can('minigame', null, room)) return false;
			if (!this.canTalk()) return;
			if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");

			room.game.end();
			return this.privateModCommand("(The game of hangman was ended by " + user.name + ".)");
		},
		endhelp: ["/hangman end - Ends the game of hangman before the man is hanged or word is guessed. Requires: % @ * # & ~"],

		disable: function (target, room, user) {
			if (!this.can('gamemanagement', null, room)) return;
			if (room.hangmanDisabled) {
				return this.errorReply("Hangman is already disabled.");
			}
			room.hangmanDisabled = true;
			if (room.chatRoomData) {
				room.chatRoomData.hangmanDisabled = true;
				Rooms.global.writeChatRoomData();
			}
			return this.sendReply("Hangman has been disabled for this room.");
		},

		enable: function (target, room, user) {
			if (!this.can('gamemanagement', null, room)) return;
			if (!room.hangmanDisabled) {
				return this.errorReply("Hangman is already enabled.");
			}
			delete room.hangmanDisabled;
			if (room.chatRoomData) {
				delete room.chatRoomData.hangmanDisabled;
				Rooms.global.writeChatRoomData();
			}
			return this.sendReply("Hangman has been enabled for this room.");
		},

		display: function (target, room, user) {
			if (!room.game || room.game.title !== 'Hangman') return this.errorReply("There is no game of hangman running in this room.");
			if (!this.runBroadcast()) return;
			room.update();

			room.game.display(user, this.broadcasting);
		},

		'': function (target, room, user) {
			return this.parse('/help hangman');
		},
	},

	hangmanhelp: [
		`/hangman allows users to play the popular game hangman in PS rooms.`,
		`Accepts the following commands:`,
		`/hangman create [word], [hint] - Makes a new hangman game. Requires: % @ * # & ~`,
		`/hangman guess [letter] - Makes a guess for the letter entered.`,
		`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
		`/hangman display - Displays the game.`,
		`/hangman end - Ends the game of hangman before the man is hanged or word is guessed. Requires: % @ * # & ~`,
		`/hangman [enable/disable] - Enables or disables hangman from being started in a room. Requires: # & ~`,
	],

	guess: function (target, room, user) {
		if (!room.game) return this.errorReply("There is no game running in this room.");
		if (!this.canTalk()) return;
		if (!room.game.guess) return this.errorReply("You can't guess anything in this game.");

		room.game.guess(target, user);
	},
	guesshelp: [
		`/guess - Shortcut for /hangman guess.`,
		`/hangman guess [letter] - Makes a guess for the letter entered.`,
		`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
	],
};
<commit_msg>Hangman: Fix a mistake caused by the for...of refactor To anyone else doing these refactors, for...of loops do NOT allow you to edit the element in place<commit_after>/*
* Hangman chat plugin
* By bumbadadabum and Zarel. Art by crobat.
*/

'use strict';

const maxMistakes = 6;

class Hangman extends Rooms.RoomGame {
	constructor(room, user, word, hint) {
		super(room);

		if (room.gameNumber) {
			room.gameNumber++;
		} else {
			room.gameNumber = 1;
		}

		this.gameid = 'hangman';
		this.title = 'Hangman';
		this.creator = user.userid;
		this.word = word;
		this.hint = hint;
		this.incorrectGuesses = 0;

		this.guesses = [];
		this.letterGuesses = [];
		this.lastGuesser = '';
		this.wordSoFar = [];

		for (const letter of word) {
			if (/[a-zA-Z]/.test(letter)) {
				this.wordSoFar.push('_');
			} else {
				this.wordSoFar.push(letter);
			}
		}
	}

	guess(word, user) {
		if (user.userid === this.room.game.creator) return user.sendTo(this.room, "You can't guess in your own hangman game.");

		let sanitized = word.replace(/[^A-Za-z ]/g, '');
		let normalized = toId(sanitized);
		if (normalized.length < 1) return user.sendTo(this.room, "Guess too short.");
		if (sanitized.length > 30) return user.sendTo(this.room, "Guess too long.");

		for (const guessid of this.guesses) {
			if (normalized === toId(guessid)) return user.sendTo(this.room, "Your guess has already been guessed.");
		}

		if (sanitized.length > 1) {
			if (!this.guessWord(sanitized, user.name)) {
				user.sendTo(this.room, "Invalid guess");
			} else {
				this.room.send(user.name + " guessed \"" + sanitized + "\"!");
			}
		} else {
			if (!this.guessLetter(sanitized, user.name)) {
				user.sendTo(this.room, "Invalid guess");
			}
		}
	}

	guessLetter(letter, guesser) {
		letter = letter.toUpperCase();
		if (this.guesses.includes(letter)) return false;
		if (this.word.toUpperCase().includes(letter)) {
			for (let i = 0; i < this.word.length; i++) {
				if (this.word[i].toUpperCase() === letter) {
					this.wordSoFar[i] = this.word[i];
				}
			}

			if (!this.wordSoFar.includes('_')) {
				this.incorrectGuesses = -1;
				this.guesses.push(letter);
				this.letterGuesses.push(letter + '1');
				this.lastGuesser = guesser;
				this.finish();
				return true;
			}
			this.letterGuesses.push(letter + '1');
		} else {
			this.incorrectGuesses++;
			this.letterGuesses.push(letter + '0');
		}

		this.guesses.push(letter);
		this.lastGuesser = guesser;
		this.update();
		return true;
	}

	guessWord(word, guesser) {
		let ourWord = toId(this.word);
		let guessedWord = toId(word);
		if (ourWord === guessedWord) {
			for (let [i, letter] of this.wordSoFar.entries()) {
				if (letter === '_') {
					this.wordSoFar[i] = this.word[i];
				}
			}
			this.incorrectGuesses = -1;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.finish();
			return true;
		} else if (ourWord.length === guessedWord.length) {
			this.incorrectGuesses++;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.update();
			return true;
		}
		return false;
	}

	hangingMan() {
		return '';
	}

	generateWindow() {
		let result = 0;

		if (this.incorrectGuesses === maxMistakes) {
			result = 1;
		} else if (!this.wordSoFar.includes('_')) {
			result = 2;
		}

		let output = '
';
		output += '
' + (result === 1 ? 'Too bad! The mon has been hanged.' : (result === 2 ? 'The word has been guessed. Congratulations!' : 'Hangman')) + '
';
		output += '

' + this.hangingMan() + '	';

		let wordString = this.wordSoFar.join('');
		if (result === 1) {
			let word = this.word;
			wordString = wordString.replace(/_+/g, (match, offset) =>
				'' + word.substr(offset, match.length) + ''
			);
		}

		if (this.hint) output += '
(Hint: ' + Chat.escapeHTML(this.hint) + ')
';
		output += '
' + wordString + '
';
		if (this.guesses.length) {
			if (this.letterGuesses.length) {
				output += 'Letters: ' + this.letterGuesses.map(g =>
					'' + Chat.escapeHTML(g[0]) + ''
				).join(', ');
			}
			if (result === 2) {
				output += '
Winner: ' + Chat.escapeHTML(this.lastGuesser);
			} else if (this.guesses[this.guesses.length - 1].length === 1) {
				// last guess was a letter
				output += ' – ' + Chat.escapeHTML(this.lastGuesser) + '';
			} else {
				output += '
Guessed: ' + this.guesses[this.guesses.length - 1] + ' – ' + Chat.escapeHTML(this.lastGuesser) + '';
			}
		}

		output += '

';

		return output;
	}

	display(user, broadcast) {
		if (broadcast) {
			this.room.add('|uhtml|hangman' + this.room.gameNumber + '|' + this.generateWindow());
		} else {
			user.sendTo(this.room, '|uhtml|hangman' + this.room.gameNumber + '|' + this.generateWindow());
		}
	}

	update() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|' + this.generateWindow());

		if (this.incorrectGuesses === maxMistakes) {
			this.finish();
		}
	}

	end() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|
(The game of hangman was ended.)
');
		this.room.add("The game of hangman was ended.");
		delete this.room.game;
	}

	finish() {
		this.room.add('|uhtmlchange|hangman' + this.room.gameNumber + '|
(The game of hangman has ended – scroll down to see the results)
');
		this.room.add('|html|' + this.generateWindow());
		delete this.room.game;
	}
}

exports.commands = {
	hangman: {
		create: 'new',
		new: function (target, room, user) {
			let params = target.split(',');

			if (!this.can('minigame', null, room)) return false;
			if (room.hangmanDisabled) return this.errorReply("Hangman is disabled for this room.");
			if (!this.canTalk()) return;
			if (room.game) return this.errorReply("There is already a game of " + room.game.title + " in progress in this room.");

			if (!params) return this.errorReply("No word entered.");
			let word = params[0].replace(/[^A-Za-z '-]/g, '');
			if (word.replace(/ /g, '').length < 1) return this.errorReply("Enter a valid word");
			if (word.length > 30) return this.errorReply("Phrase must be less than 30 characters.");
			if (word.split(' ').some(w => w.length > 20)) return this.errorReply("Each word in the phrase must be less than 20 characters.");
			if (!/[a-zA-Z]/.test(word)) return this.errorReply("Word must contain at least one letter.");

			let hint;
			if (params.length > 1) {
				hint = params.slice(1).join(',').trim();
				if (hint.length > 150) return this.errorReply("Hint too long.");
			}

			room.game = new Hangman(room, user, word, hint);
			room.game.display(user, true);

			return this.privateModCommand("(A game of hangman was started by " + user.name + ".)");
		},
		createhelp: ["/hangman create [word], [hint] - Makes a new hangman game. Requires: % @ * # & ~"],

		guess: function (target, room, user) {
			if (!target) return this.parse('/help guess');
			if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");
			if (!this.canTalk()) return;

			room.game.guess(target, user);
		},
		guesshelp: [
			`/hangman guess [letter] - Makes a guess for the letter entered.`,
			`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
		],

		stop: 'end',
		end: function (target, room, user) {
			if (!this.can('minigame', null, room)) return false;
			if (!this.canTalk()) return;
			if (!room.game || room.game.gameid !== 'hangman') return this.errorReply("There is no game of hangman running in this room.");

			room.game.end();
			return this.privateModCommand("(The game of hangman was ended by " + user.name + ".)");
		},
		endhelp: ["/hangman end - Ends the game of hangman before the man is hanged or word is guessed. Requires: % @ * # & ~"],

		disable: function (target, room, user) {
			if (!this.can('gamemanagement', null, room)) return;
			if (room.hangmanDisabled) {
				return this.errorReply("Hangman is already disabled.");
			}
			room.hangmanDisabled = true;
			if (room.chatRoomData) {
				room.chatRoomData.hangmanDisabled = true;
				Rooms.global.writeChatRoomData();
			}
			return this.sendReply("Hangman has been disabled for this room.");
		},

		enable: function (target, room, user) {
			if (!this.can('gamemanagement', null, room)) return;
			if (!room.hangmanDisabled) {
				return this.errorReply("Hangman is already enabled.");
			}
			delete room.hangmanDisabled;
			if (room.chatRoomData) {
				delete room.chatRoomData.hangmanDisabled;
				Rooms.global.writeChatRoomData();
			}
			return this.sendReply("Hangman has been enabled for this room.");
		},

		display: function (target, room, user) {
			if (!room.game || room.game.title !== 'Hangman') return this.errorReply("There is no game of hangman running in this room.");
			if (!this.runBroadcast()) return;
			room.update();

			room.game.display(user, this.broadcasting);
		},

		'': function (target, room, user) {
			return this.parse('/help hangman');
		},
	},

	hangmanhelp: [
		`/hangman allows users to play the popular game hangman in PS rooms.`,
		`Accepts the following commands:`,
		`/hangman create [word], [hint] - Makes a new hangman game. Requires: % @ * # & ~`,
		`/hangman guess [letter] - Makes a guess for the letter entered.`,
		`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
		`/hangman display - Displays the game.`,
		`/hangman end - Ends the game of hangman before the man is hanged or word is guessed. Requires: % @ * # & ~`,
		`/hangman [enable/disable] - Enables or disables hangman from being started in a room. Requires: # & ~`,
	],

	guess: function (target, room, user) {
		if (!room.game) return this.errorReply("There is no game running in this room.");
		if (!this.canTalk()) return;
		if (!room.game.guess) return this.errorReply("You can't guess anything in this game.");

		room.game.guess(target, user);
	},
	guesshelp: [
		`/guess - Shortcut for /hangman guess.`,
		`/hangman guess [letter] - Makes a guess for the letter entered.`,
		`/hangman guess [word] - Same as a letter, but guesses an entire word.`,
	],
};
<|endoftext|>


<commit_before>var browserify = require('browserify')
var coffeeify = require('coffeeify')
var path = require('path')
var fs = require('fs')

module.exports = function (rootFolder) {
  var src = null
  var b = browserify()

  b.transform(coffeeify)

  ;[
    'mongodb',
    'argon2',
    './bundle',
    './routes',
    './router',
    './config',
    './auth',
    'socket.io'
  ].forEach(function (toIgnore) {
    b.ignore(toIgnore)
  })

  fs.readdirSync(rootFolder).filter(function (i) {
    return i.match(/\.js$/) && !i.match(/\.spec\.js$/)
  }).forEach(function (modelName) {
    var modelFile = path.join(rootFolder, modelName)
    b.add(modelFile)
    b.require(modelFile)
  })

  b.bundle(function (err, compiled) {
    if (err) {
      throw err
    }
    src = compiled
    console.log('Invisible: Created bundle')
  })

  return function (req, res, next) {
    if (req.path !== '/invisible.js') {
      return next()
    }
    res.contentType('application/javascript')
    res.send(src)
  }
}
<commit_msg>only add, not add and require<commit_after>var browserify = require('browserify')
var coffeeify = require('coffeeify')
var path = require('path')
var fs = require('fs')

module.exports = function (rootFolder) {
  var src = null
  var b = browserify()

  b.transform(coffeeify)

  ;[
    'mongodb',
    'argon2',
    './bundle',
    './routes',
    './router',
    './config',
    './auth',
    'socket.io'
  ].forEach(function (mod) {
    b.ignore(mod)
  })

  fs.readdirSync(rootFolder).filter(function (i) {
    return i.match(/\.model\.js$/) && !i.match(/\.spec\.js$/)
  }).forEach(function (modelName) {
    var modelFile = path.join(rootFolder, modelName)
    b.add(modelFile)
  })

  b.bundle(function (err, compiled) {
    if (err) {
      throw err
    }
    src = compiled
    console.log('Invisible: Created bundle')
  })

  return function (req, res, next) {
    if (req.path !== '/invisible.js') {
      return next()
    }
    res.contentType('application/javascript')
    res.send(src)
  }
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */

var hoek = require('hoek');

/**
 * Constants.
 */

var LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];

/**
 * Event logger.
 */

function logEvent(ctx, data, request) {
  var obj = {};
  var msg = '';

  if (ctx.includeTags) {
    obj.tags = ctx.joinTags ? data.tags.join(ctx.joinTags) : data.tags;
  }

  if (request) obj.req_id = request.id;

  if (typeof data.data === 'string') {
    msg = data.data;
  } else if (ctx.includeData && data.data !== undefined) {
    if (ctx.mergeData) {
      try {
        hoek.merge(obj, data.data);

        if (obj.id === obj.req_id) delete obj.id;
      } catch (err) {
        obj.data = data.data;
      }
    } else {
      obj.data = data.data;
    }
  } else if (ctx.skipUndefined) {
    return;
  }

  ctx.log[ctx.level](obj, msg);
}

/**
 * Plugin.
 */

function register(server, options, next) {
  if (!options.logger) {
    return next(new Error('logger required'));
  }

  var log = options.logger;
  var handler = options.handler || function() {};

  delete options.logger;
  delete options.handler;

  options = hoek.applyToDefaults({
    includeTags: false,
    includeData: true,
    mergeData: false,
    skipUndefined: true,
  }, options);

  server.ext('onRequest', function(request, next) {
    var rlog = request.log;

    request.bunyan = log.child({ req_id: request.id });

    request.log = function() {
      rlog.apply(request, arguments);
    };

    LEVELS.forEach(function(level) {
      request.log[level] = request.bunyan[level].bind(request.bunyan);
    });

    next();
  });

  exports.register.attributes = {
    name: 'hapi-bunyan'
  };

  server.events.on('log', function(data, tags) {
    var ctx = {
      level: tags.error ? 'error' : 'info',
      log: log,
      includeTags: options.includeTags,
      includeData: options.includeData,
      mergeData: options.mergeData,
      skipUndefined: options.skipUndefined,
      joinTags: options.joinTags,
    };

    if (handler.call(ctx, 'log', data, tags)) {
      return;
    }

    logEvent(ctx, data);
  });

  server.events.on('request', function(request, data, tags) {
    var ctx = {
      level: tags.error ? 'warn' : 'info',
      log: log,
      includeTags: options.includeTags,
      includeData: options.includeData,
      mergeData: options.mergeData,
      skipUndefined: options.skipUndefined,
      joinTags: options.joinTags,
    };

    if (handler.call(ctx, 'request', request, data, tags)) {
      return;
    }

    logEvent(ctx, data, request);
  });

  next();
}

/**
 * Attributes.
 */

register.attributes = {
  pkg: require('../package.json'),
};

/**
 * Module exports.
 */

exports.register = register;
<commit_msg>update events registering to work with hapi8<commit_after>'use strict';

/**
 * Module dependencies.
 */

var hoek = require('hoek');

/**
 * Constants.
 */

var LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];

/**
 * Event logger.
 */

function logEvent(ctx, data, request) {
  var obj = {};
  var msg = '';

  if (ctx.includeTags) {
    obj.tags = ctx.joinTags ? data.tags.join(ctx.joinTags) : data.tags;
  }

  if (request) obj.req_id = request.id;

  if (typeof data.data === 'string') {
    msg = data.data;
  } else if (ctx.includeData && data.data !== undefined) {
    if (ctx.mergeData) {
      try {
        hoek.merge(obj, data.data);

        if (obj.id === obj.req_id) delete obj.id;
      } catch (err) {
        obj.data = data.data;
      }
    } else {
      obj.data = data.data;
    }
  } else if (ctx.skipUndefined) {
    return;
  }

  ctx.log[ctx.level](obj, msg);
}

/**
 * Plugin.
 */

function register(server, options, next) {
  if (!options.logger) {
    return next(new Error('logger required'));
  }

  var log = options.logger;
  var handler = options.handler || function() {};

  delete options.logger;
  delete options.handler;

  options = hoek.applyToDefaults({
    includeTags: false,
    includeData: true,
    mergeData: false,
    skipUndefined: true,
  }, options);

  server.ext('onRequest', function(request, reply) {
    var rlog = request.log;

    request.bunyan = log.child({ req_id: request.id });

    request.log = function() {
      rlog.apply(request, arguments);
    };

    LEVELS.forEach(function(level) {
      request.log[level] = request.bunyan[level].bind(request.bunyan);
    });

    return reply.continue();
  });

  server.on('log', function(data, tags) {
    var ctx = {
      level: tags.error ? 'error' : 'info',
      log: log,
      includeTags: options.includeTags,
      includeData: options.includeData,
      mergeData: options.mergeData,
      skipUndefined: options.skipUndefined,
      joinTags: options.joinTags,
    };

    if (handler.call(ctx, 'log', data, tags)) {
      return;
    }

    logEvent(ctx, data);
  });

  server.on('request', function(request, data, tags) {
    var ctx = {
      level: tags.error ? 'warn' : 'info',
      log: log,
      includeTags: options.includeTags,
      includeData: options.includeData,
      mergeData: options.mergeData,
      skipUndefined: options.skipUndefined,
      joinTags: options.joinTags,
    };

    if (handler.call(ctx, 'request', request, data, tags)) {
      return;
    }

    logEvent(ctx, data, request);
  });

  next();
}

/**
 * Attributes.
 */

register.attributes = {
  pkg: require('../package.json'),
  name: 'hapi-bunyan'
};

/**
 * Module exports.
 */

exports.register = register;
<|endoftext|>


<commit_before>#!/usr/bin/env node
"use strict";

var V86 = require("../../build/libv86.js").V86;
var fs = require("fs");

function readfile(path)
{
    return new Uint8Array(fs.readFileSync(path)).buffer;
}

function Loader(path)
{
    this.buffer = readfile(path);
    this.byteLength = this.buffer.byteLength;
}

Loader.prototype.load = function()
{
    this.onload && this.onload({});
};

var bios = readfile(__dirname + "/../../bios/seabios.bin");
var vga_bios = readfile(__dirname + "/../../bios/vgabios.bin");

var emulator = new V86({
    bios: { buffer: bios },
    vga_bios: { buffer: vga_bios },
    multiboot: new Loader(process.argv[2]),
    autostart: true,
    memory_size: 256 * 1024 * 1024,
});

emulator.bus.register("emulator-started", function()
{
    emulator.v86.cpu.io.register_write_consecutive(0xF4, this,
        function(value)
        {
            console.log("Test exited with code " + value);
            process.exit(value);
        },
        function() {},
        function() {},
        function() {});
});

emulator.add_listener("serial0-output-char", function(chr)
{
    process.stdout.write(chr);
});

<commit_msg>Reduce memory for kvm-unit-test<commit_after>#!/usr/bin/env node
"use strict";

var V86 = require("../../build/libv86.js").V86;
var fs = require("fs");

function readfile(path)
{
    return new Uint8Array(fs.readFileSync(path)).buffer;
}

function Loader(path)
{
    this.buffer = readfile(path);
    this.byteLength = this.buffer.byteLength;
}

Loader.prototype.load = function()
{
    this.onload && this.onload({});
};

var bios = readfile(__dirname + "/../../bios/seabios.bin");
var vga_bios = readfile(__dirname + "/../../bios/vgabios.bin");

var emulator = new V86({
    bios: { buffer: bios },
    vga_bios: { buffer: vga_bios },
    multiboot: new Loader(process.argv[2]),
    autostart: true,
    memory_size: 64 * 1024 * 1024,
});

emulator.bus.register("emulator-started", function()
{
    emulator.v86.cpu.io.register_write_consecutive(0xF4, this,
        function(value)
        {
            console.log("Test exited with code " + value);
            process.exit(value);
        },
        function() {},
        function() {},
        function() {});
});

emulator.add_listener("serial0-output-char", function(chr)
{
    process.stdout.write(chr);
});

<|endoftext|>


<commit_before>var firepad = firepad || { };

/**
 * Helper to parse html into Firepad-compatible lines / text.
 * @type {*}
 */
firepad.ParseHtml = (function () {
  var LIST_TYPE = firepad.LineFormatting.LIST_TYPE;

  /**
   * Represents the current parse state as an immutable structure.  To create a new ParseState, use
   * the withXXX methods.
   *
   * @param opt_listType
   * @param opt_lineFormatting
   * @param opt_textFormatting
   * @constructor
   */
  function ParseState(opt_listType, opt_lineFormatting, opt_textFormatting) {
    this.listType = opt_listType || LIST_TYPE.UNORDERED;
    this.lineFormatting = opt_lineFormatting || firepad.LineFormatting();
    this.textFormatting = opt_textFormatting || firepad.Formatting();
  }

  ParseState.prototype.withTextFormatting = function(textFormatting) {
    return new ParseState(this.listType, this.lineFormatting, textFormatting);
  };

  ParseState.prototype.withLineFormatting = function(lineFormatting) {
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withListType = function(listType) {
    return new ParseState(listType, this.lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withIncreasedIndent = function() {
    var lineFormatting = this.lineFormatting.indent(this.lineFormatting.getIndent() + 1);
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withAlign = function(align) {
    var lineFormatting = this.lineFormatting.align(align);
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  /**
   * Mutable structure representing the current parse output.
   * @constructor
   */
  function ParseOutput() {
    this.lines = [ ];
    this.currentLine = [];
    this.currentLineListItemType = null;
  }

  ParseOutput.prototype.newlineIfNonEmpty = function(state) {
    this.cleanLine_(true);
    if (this.currentLine.length > 0) {
      this.newline(state);
    }
  };

  ParseOutput.prototype.newlineIfNonEmptyOrListItem = function(state) {
    this.cleanLine_(true);
    if (this.currentLine.length > 0 || this.currentLineListItemType !== null) {
      this.newline(state);
    }
  };

  ParseOutput.prototype.newline = function(state) {
    this.cleanLine_();
    var lineFormatting = state.lineFormatting;
    if (this.currentLineListItemType !== null) {
      lineFormatting = lineFormatting.listItem(this.currentLineListItemType);
      this.currentLineListItemType = null;
    }

    this.lines.push(firepad.Line(this.currentLine, lineFormatting));
    this.currentLine = [];
  };

  ParseOutput.prototype.makeListItem = function(type) {
    this.currentLineListItemType = type;
  };

  ParseOutput.prototype.cleanLine_ = function(ignoreNbsps) {
    // Kinda' a hack, but we remove leading and trailing spaces (since these aren't significant in html) and
    // replaces nbsp's with normal spaces.
    if (this.currentLine.length > 0) {
      var last = this.currentLine.length - 1;
      this.currentLine[0].text = this.currentLine[0].text.replace(/^ +/, '');
      this.currentLine[last].text = this.currentLine[last].text.replace(/ +$/g, '');
      if (!ignoreNbsps) {
        for(var i = 0; i < this.currentLine.length; i++) {
          this.currentLine[i].text = this.currentLine[i].text.replace(/\u00a0/g, ' ');
        }
      }
    }
    // If after stripping trailing whitespace, there's nothing left, clear currentLine out.
    if (this.currentLine.length === 1 && this.currentLine[0].text === '') {
      this.currentLine = [];
    }
  };

  var entityManager_, codeMirror_;
  function parseHtml(html, entityManager, codeMirror) {
    html=html.replace(/(\r\n|\n|\r)?(\r\n|\n|\r)?(\r\n|\n|\r)?/, ''); // remove
    html=html.replace(/(\r\n|\n|\r)?<\/body>(\r\n|\n|\r)?<\/html>(\r\n|\n|\r)?/, ''); // remove

    // Create DIV with HTML (as a convenient way to parse it).
    var div = (firepad.document || document).createElement('div');
    div.innerHTML = html;

    // HACK until I refactor this.
    entityManager_ = entityManager;
    codeMirror_ = codeMirror;

    var output = new ParseOutput();
    var state = new ParseState();
    parseNode(div, state, output);

    return output.lines;
  }

  // Fix IE8.
  var Node = Node || {
    ELEMENT_NODE: 1,
    TEXT_NODE: 3
  };

  function parseNode(node, state, output) {
    // Give entity manager first crack at it.
    if (node.nodeType === Node.ELEMENT_NODE) {
      var entity = entityManager_.fromElement(node);
      if (entity) {
        output.currentLine.push(new firepad.Text(
            firepad.sentinelConstants.ENTITY_SENTINEL_CHARACTER,
            new firepad.Formatting(entity.toAttributes())
        ));
        return;
      }
    }

    switch (node.nodeType) {
      case Node.TEXT_NODE:
        // This probably isn't exactly right, but mostly works...
        var text = node.nodeValue.replace(/[ \n\t]+/g, ' ');
        output.currentLine.push(firepad.Text(text, state.textFormatting));
        break;
      case Node.ELEMENT_NODE:
        var style = node.getAttribute('style') || '';
        state = parseStyle(state, style);
        switch (node.nodeName.toLowerCase()) {
          case 'div':
          case 'h1':
          case 'h2':
          case 'h3':
          case 'p':
            output.newlineIfNonEmpty(state);
            parseChildren(node, state, output);
            output.newlineIfNonEmpty(state);
            break;
          case 'center':
            state = state.withAlign('center');
            output.newlineIfNonEmpty(state);
            parseChildren(node, state.withAlign('center'), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'b':
          case 'strong':
            parseChildren(node, state.withTextFormatting(state.textFormatting.bold(true)), output);
            break;
          case 'u':
            parseChildren(node, state.withTextFormatting(state.textFormatting.underline(true)), output);
            break;
          case 'i':
          case 'em':
            parseChildren(node, state.withTextFormatting(state.textFormatting.italic(true)), output);
            break;
          case 's':
            parseChildren(node, state.withTextFormatting(state.textFormatting.strike(true)), output);
            break;
          case 'font':
            var face = node.getAttribute('face');
            var color = node.getAttribute('color');
            var size = parseInt(node.getAttribute('size'));
            if (face) { state = state.withTextFormatting(state.textFormatting.font(face)); }
            if (color) { state = state.withTextFormatting(state.textFormatting.color(color)); }
            if (size) { state = state.withTextFormatting(state.textFormatting.fontSize(size)); }
            parseChildren(node, state, output);
            break;
          case 'br':
            output.newline(state);
            break;
          case 'ul':
            output.newlineIfNonEmptyOrListItem(state);
            var listType = node.getAttribute('class') === 'firepad-todo' ? LIST_TYPE.TODO : LIST_TYPE.UNORDERED;
            parseChildren(node, state.withListType(listType).withIncreasedIndent(), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'ol':
            output.newlineIfNonEmptyOrListItem(state);
            parseChildren(node, state.withListType(LIST_TYPE.ORDERED).withIncreasedIndent(), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'li':
            parseListItem(node, state, output);
            break;
          case 'style': // ignore.
            break;
          default:
            parseChildren(node, state, output);
            break;
        }
        break;
      default:
        // Ignore other nodes (comments, etc.)
        break;
    }
  }

  function parseChildren(node, state, output) {
    if (node.hasChildNodes()) {
      for(var i = 0; i < node.childNodes.length; i++) {
        parseNode(node.childNodes[i], state, output);
      }
    }
  }

  function parseListItem(node, state, output) {
    // Note:
 is weird:
    // * Only the first line in the
 tag should be a list item (i.e. with a bullet or number next to it).
    // *

 should create an empty list item line;

 should create two.

    output.newlineIfNonEmptyOrListItem(state);

    var listType = (node.getAttribute('class') === 'firepad-checked') ? LIST_TYPE.TODOCHECKED : state.listType;
    output.makeListItem(listType);
    var oldLine = output.currentLine;

    parseChildren(node, state, output);

    if (oldLine === output.currentLine || output.currentLine.length > 0) {
      output.newline(state);
    }
  }

  function styleEqual(s1,s2) {
    s1=s1.toLowerCase(); // lower
    s1=s1.split(' ').join(''); // remove spaces
    s1=s1.lastIndexOf(";") == s1.length - 1 ?  s1.substring(0,  s1.length -1 ) : s1; // remove trailing ;
    s2=s2.toLowerCase(); // lower
    s2=s2.split(' ').join(''); // remove spaces
    s2=s2.lastIndexOf(";") == s2.length - 1 ?  s2.substring(0,  s2.length -1 ) : s2; // remove trailing ;
    return s1==s2;
  }

  function parseStyle(state, styleString) {
    if (!this.firepadDefaultStyles) {
      // caching some default styles needed later
      var style = window.getComputedStyle(codeMirror_.getWrapperElement());
      firepadDefaultStyles={
        fontFamily: style.getPropertyValue('font-family'),
        fontSize: style.getPropertyValue('font-size'),
        backgroundColor: style.getPropertyValue('background-color'),
        color: style.getPropertyValue('color'),
        textAlign: style.getPropertyValue('text-align')
      };
    }

    var textFormatting = state.textFormatting;
    var lineFormatting = state.lineFormatting;
    var styles = styleString.split(';');
    for(var i = 0; i < styles.length; i++) {
      var stylePieces = styles[i].split(':');
      if (stylePieces.length !== 2)
        continue;
      var prop = firepad.utils.trim(stylePieces[0]).toLowerCase();
      var val = firepad.utils.trim(stylePieces[1]).toLowerCase();
      switch (prop) {
        case 'text-decoration':
          var underline = val.indexOf('underline') >= 0;
          var strike = val.indexOf('line-through') >= 0;
          textFormatting = textFormatting.underline(underline).strike(strike);
          break;
        case 'font-weight':
          var bold = (val === 'bold') || parseInt(val) >= 600;
          textFormatting = textFormatting.bold(bold);
          break;
        case 'font-style':
          var italic = (val === 'italic' || val === 'oblique');
          textFormatting = textFormatting.italic(italic);
          break;
        case 'color':
          if (styleEqual(val, this.firepadDefaultStyles.color)) break;
          textFormatting = textFormatting.color(val);
          break;
        case 'background-color':
          if (styleEqual(val, this.firepadDefaultStyles.backgroundColor)) break;
          textFormatting = textFormatting.backgroundColor(val);
          break;
        case 'text-align':
          if (styleEqual(val, this.firepadDefaultStyles.textAlign)) break;
          lineFormatting = lineFormatting.align(val);
          break;
        case 'font-size':
          if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break;
          var size = null;
          var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger'];
          if (firepad.utils.stringEndsWith(val, allowedValues)) {
            size = val;
          }
          else if (parseInt(val)) {
            size = parseInt(val)+'px';
          }
          if (size) {
            textFormatting = textFormatting.fontSize(size);
          }
          break;
        case 'font-family':
          if (styleEqual(val, this.firepadDefaultStyles.fontFamily)) break;
          var font = firepad.utils.trim(val.split(',')[0]); // get first font.
          font = font.replace(/['"]/g, ''); // remove quotes.
          font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() });
          textFormatting = textFormatting.font(font);
          break;
      }
    }
    return state.withLineFormatting(lineFormatting).withTextFormatting(textFormatting);
  }

  return parseHtml;
})();
<commit_msg>Update parse-html.js<commit_after>var firepad = firepad || { };

/**
 * Helper to parse html into Firepad-compatible lines / text.
 * @type {*}
 */
firepad.ParseHtml = (function () {
  var LIST_TYPE = firepad.LineFormatting.LIST_TYPE;

  /**
   * Represents the current parse state as an immutable structure.  To create a new ParseState, use
   * the withXXX methods.
   *
   * @param opt_listType
   * @param opt_lineFormatting
   * @param opt_textFormatting
   * @constructor
   */
  function ParseState(opt_listType, opt_lineFormatting, opt_textFormatting) {
    this.listType = opt_listType || LIST_TYPE.UNORDERED;
    this.lineFormatting = opt_lineFormatting || firepad.LineFormatting();
    this.textFormatting = opt_textFormatting || firepad.Formatting();
  }

  ParseState.prototype.withTextFormatting = function(textFormatting) {
    return new ParseState(this.listType, this.lineFormatting, textFormatting);
  };

  ParseState.prototype.withLineFormatting = function(lineFormatting) {
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withListType = function(listType) {
    return new ParseState(listType, this.lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withIncreasedIndent = function() {
    var lineFormatting = this.lineFormatting.indent(this.lineFormatting.getIndent() + 1);
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  ParseState.prototype.withAlign = function(align) {
    var lineFormatting = this.lineFormatting.align(align);
    return new ParseState(this.listType, lineFormatting, this.textFormatting);
  };

  /**
   * Mutable structure representing the current parse output.
   * @constructor
   */
  function ParseOutput() {
    this.lines = [ ];
    this.currentLine = [];
    this.currentLineListItemType = null;
  }

  ParseOutput.prototype.newlineIfNonEmpty = function(state) {
    this.cleanLine_(true);
    if (this.currentLine.length > 0) {
      this.newline(state);
    }
  };

  ParseOutput.prototype.newlineIfNonEmptyOrListItem = function(state) {
    this.cleanLine_(true);
    if (this.currentLine.length > 0 || this.currentLineListItemType !== null) {
      this.newline(state);
    }
  };

  ParseOutput.prototype.newline = function(state) {
    this.cleanLine_();
    var lineFormatting = state.lineFormatting;
    if (this.currentLineListItemType !== null) {
      lineFormatting = lineFormatting.listItem(this.currentLineListItemType);
      this.currentLineListItemType = null;
    }

    this.lines.push(firepad.Line(this.currentLine, lineFormatting));
    this.currentLine = [];
  };

  ParseOutput.prototype.makeListItem = function(type) {
    this.currentLineListItemType = type;
  };

  ParseOutput.prototype.cleanLine_ = function(ignoreNbsps) {
    // Kinda' a hack, but we remove leading and trailing spaces (since these aren't significant in html) and
    // replaces nbsp's with normal spaces.
    if (this.currentLine.length > 0) {
      var last = this.currentLine.length - 1;
      this.currentLine[0].text = this.currentLine[0].text.replace(/^ +/, '');
      this.currentLine[last].text = this.currentLine[last].text.replace(/ +$/g, '');
      if (!ignoreNbsps) {
        for(var i = 0; i < this.currentLine.length; i++) {
          this.currentLine[i].text = this.currentLine[i].text.replace(/\u00a0/g, ' ');
        }
      }
    }
    // If after stripping trailing whitespace, there's nothing left, clear currentLine out.
    if (this.currentLine.length === 1 && this.currentLine[0].text === '') {
      this.currentLine = [];
    }
  };

  var entityManager_, codeMirror_;
  function parseHtml(html, entityManager, codeMirror) {
    html=html.replace(/(\r\n|\n|\r)?(\r\n|\n|\r)?(\r\n|\n|\r)?/, ''); // remove
    html=html.replace(/(\r\n|\n|\r)?<\/body>(\r\n|\n|\r)?<\/html>(\r\n|\n|\r)?/, ''); // remove

    // Create DIV with HTML (as a convenient way to parse it).
    var div = (firepad.document || document).createElement('div');
    div.innerHTML = html;

    // HACK until I refactor this.
    entityManager_ = entityManager;
    codeMirror_ = codeMirror;

    var output = new ParseOutput();
    var state = new ParseState();
    parseNode(div, state, output);

    return output.lines;
  }

  // Fix IE8.
  var Node = Node || {
    ELEMENT_NODE: 1,
    TEXT_NODE: 3
  };

  function parseNode(node, state, output) {
    // Give entity manager first crack at it.
    if (node.nodeType === Node.ELEMENT_NODE) {
      var entity = entityManager_.fromElement(node);
      if (entity) {
        output.currentLine.push(new firepad.Text(
            firepad.sentinelConstants.ENTITY_SENTINEL_CHARACTER,
            new firepad.Formatting(entity.toAttributes())
        ));
        return;
      }
    }

    switch (node.nodeType) {
      case Node.TEXT_NODE:
        // replace spaces with   so they can withstand cleanLine_
        var text = node.nodeValue.replace(/ /g, '\u00a0');
        output.currentLine.push(firepad.Text(text, state.textFormatting));
        break;
      case Node.ELEMENT_NODE:
        var style = node.getAttribute('style') || '';
        state = parseStyle(state, style);
        switch (node.nodeName.toLowerCase()) {
          case 'div':
          case 'h1':
          case 'h2':
          case 'h3':
          case 'p':
            output.newlineIfNonEmpty(state);
            parseChildren(node, state, output);
            output.newlineIfNonEmpty(state);
            break;
          case 'center':
            state = state.withAlign('center');
            output.newlineIfNonEmpty(state);
            parseChildren(node, state.withAlign('center'), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'b':
          case 'strong':
            parseChildren(node, state.withTextFormatting(state.textFormatting.bold(true)), output);
            break;
          case 'u':
            parseChildren(node, state.withTextFormatting(state.textFormatting.underline(true)), output);
            break;
          case 'i':
          case 'em':
            parseChildren(node, state.withTextFormatting(state.textFormatting.italic(true)), output);
            break;
          case 's':
            parseChildren(node, state.withTextFormatting(state.textFormatting.strike(true)), output);
            break;
          case 'font':
            var face = node.getAttribute('face');
            var color = node.getAttribute('color');
            var size = parseInt(node.getAttribute('size'));
            if (face) { state = state.withTextFormatting(state.textFormatting.font(face)); }
            if (color) { state = state.withTextFormatting(state.textFormatting.color(color)); }
            if (size) { state = state.withTextFormatting(state.textFormatting.fontSize(size)); }
            parseChildren(node, state, output);
            break;
          case 'br':
            output.newline(state);
            break;
          case 'ul':
            output.newlineIfNonEmptyOrListItem(state);
            var listType = node.getAttribute('class') === 'firepad-todo' ? LIST_TYPE.TODO : LIST_TYPE.UNORDERED;
            parseChildren(node, state.withListType(listType).withIncreasedIndent(), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'ol':
            output.newlineIfNonEmptyOrListItem(state);
            parseChildren(node, state.withListType(LIST_TYPE.ORDERED).withIncreasedIndent(), output);
            output.newlineIfNonEmpty(state);
            break;
          case 'li':
            parseListItem(node, state, output);
            break;
          case 'style': // ignore.
            break;
          default:
            parseChildren(node, state, output);
            break;
        }
        break;
      default:
        // Ignore other nodes (comments, etc.)
        break;
    }
  }

  function parseChildren(node, state, output) {
    if (node.hasChildNodes()) {
      for(var i = 0; i < node.childNodes.length; i++) {
        parseNode(node.childNodes[i], state, output);
      }
    }
  }

  function parseListItem(node, state, output) {
    // Note:
 is weird:
    // * Only the first line in the
 tag should be a list item (i.e. with a bullet or number next to it).
    // *

 should create an empty list item line;

 should create two.

    output.newlineIfNonEmptyOrListItem(state);

    var listType = (node.getAttribute('class') === 'firepad-checked') ? LIST_TYPE.TODOCHECKED : state.listType;
    output.makeListItem(listType);
    var oldLine = output.currentLine;

    parseChildren(node, state, output);

    if (oldLine === output.currentLine || output.currentLine.length > 0) {
      output.newline(state);
    }
  }

  function styleEqual(s1,s2) {
    s1=s1.toLowerCase(); // lower
    s1=s1.split(' ').join(''); // remove spaces
    s1=s1.lastIndexOf(";") == s1.length - 1 ?  s1.substring(0,  s1.length -1 ) : s1; // remove trailing ;
    s2=s2.toLowerCase(); // lower
    s2=s2.split(' ').join(''); // remove spaces
    s2=s2.lastIndexOf(";") == s2.length - 1 ?  s2.substring(0,  s2.length -1 ) : s2; // remove trailing ;
    return s1==s2;
  }

  function parseStyle(state, styleString) {
    if (!this.firepadDefaultStyles) {
      // caching some default styles needed later
      var style = window.getComputedStyle(codeMirror_.getWrapperElement());
      firepadDefaultStyles={
        fontFamily: style.getPropertyValue('font-family'),
        fontSize: style.getPropertyValue('font-size'),
        backgroundColor: style.getPropertyValue('background-color'),
        color: style.getPropertyValue('color'),
        textAlign: style.getPropertyValue('text-align')
      };
    }

    var textFormatting = state.textFormatting;
    var lineFormatting = state.lineFormatting;
    var styles = styleString.split(';');
    for(var i = 0; i < styles.length; i++) {
      var stylePieces = styles[i].split(':');
      if (stylePieces.length !== 2)
        continue;
      var prop = firepad.utils.trim(stylePieces[0]).toLowerCase();
      var val = firepad.utils.trim(stylePieces[1]).toLowerCase();
      switch (prop) {
        case 'text-decoration':
          var underline = val.indexOf('underline') >= 0;
          var strike = val.indexOf('line-through') >= 0;
          textFormatting = textFormatting.underline(underline).strike(strike);
          break;
        case 'font-weight':
          var bold = (val === 'bold') || parseInt(val) >= 600;
          textFormatting = textFormatting.bold(bold);
          break;
        case 'font-style':
          var italic = (val === 'italic' || val === 'oblique');
          textFormatting = textFormatting.italic(italic);
          break;
        case 'color':
          if (styleEqual(val, this.firepadDefaultStyles.color)) break;
          textFormatting = textFormatting.color(val);
          break;
        case 'background-color':
          if (styleEqual(val, this.firepadDefaultStyles.backgroundColor)) break;
          textFormatting = textFormatting.backgroundColor(val);
          break;
        case 'text-align':
          if (styleEqual(val, this.firepadDefaultStyles.textAlign)) break;
          lineFormatting = lineFormatting.align(val);
          break;
        case 'font-size':
          if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break;
          var size = null;
          var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger'];
          if (firepad.utils.stringEndsWith(val, allowedValues)) {
            size = val;
          }
          else if (parseInt(val)) {
            size = parseInt(val)+'px';
          }
          if (size) {
            textFormatting = textFormatting.fontSize(size);
          }
          break;
        case 'font-family':
          if (styleEqual(val, this.firepadDefaultStyles.fontFamily)) break;
          var font = firepad.utils.trim(val.split(',')[0]); // get first font.
          font = font.replace(/['"]/g, ''); // remove quotes.
          font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() });
          textFormatting = textFormatting.font(font);
          break;
      }
    }
    return state.withLineFormatting(lineFormatting).withTextFormatting(textFormatting);
  }

  return parseHtml;
})();
<|endoftext|>