<commit_before>'use strict'

var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
var counter = 0
var notDecreased = 0

/**
 * Used for lengths and numbers only, faster perf on arrays / bulks
 * @param parser
 * @returns {*}
 */
function parseSimpleNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length
  var number = 0
  var sign = false

  if (parser.buffer[offset] === 45) {
    sign = true
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13 && parser.buffer[offset] === 10) { // \r\n
      parser.offset = offset + 1
      return sign ? -number : number
    }
    number = (number * 10) + (c1 - 48)
  }
}

/**
 * Used for integer numbers in case of the returnNumbers option
 * @param parser
 * @returns {*}
 */
function parseStringNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length
  var number = ''

  if (parser.buffer[offset] === 45) {
    number += '-'
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13 && parser.buffer[offset] === 10) { // \r\n
      parser.offset = offset + 1
      return number
    }
    number += c1 - 48
  }
}

/**
 * Returns a string or buffer of the provided offset start and
 * end ranges. Checks `optionReturnBuffers`.
 *
 * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 *
 * @param parser
 * @param start
 * @param end
 * @returns {*}
 */
function convertBufferRange (parser, start, end) {
  parser.offset = end + 2
  if (parser.optionReturnBuffers === true) {
    return parser.buffer.slice(start, end)
  }

  return parser.buffer.toString('utf-8', start, end)
}

/**
 * Parse a '+' redis simple string response but forward the offsets
 * onto convertBufferRange to generate a string.
 * @param parser
 * @returns {*}
 */
function parseSimpleStringViaOffset (parser) {
  var start = parser.offset
  var offset = parser.offset
  var length = parser.buffer.length
  var buffer = parser.buffer

  while (offset < length) {
    if (buffer[offset++] === 10) { // \r\n
      return convertBufferRange(parser, start, offset - 2)
    }
  }
}

/**
 * Returns the string length via parseSimpleNumbers
 * @param parser
 * @returns {*}
 */
function parseLength (parser) {
  var string = parseSimpleNumbers(parser)
  if (string !== undefined) {
    return +string
  }
}

/**
 * Parse a ':' redis integer response
 *
 * If stringNumbers is activated the parser always returns numbers as string
 * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 * are 64bit floating point numbers with reduced precision
 *
 * @param parser
 * @returns {*}
 */
function parseInteger (parser) {
  if (parser.optionStringNumbers) {
    return parseStringNumbers(parser)
  }
  return parseSimpleNumbers(parser)
}

/**
 * Parse a '$' redis bulk string response
 * @param parser
 * @returns {*}
 */
function parseBulkString (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var offsetEnd = parser.offset + length
  if (offsetEnd + 2 > parser.buffer.length) {
    parser.bigStrSize = offsetEnd + 2
    parser.bigOffset = parser.offset
    parser.totalChunkSize = parser.buffer.length
    parser.bufferCache.push(parser.buffer)
    return
  }

  return convertBufferRange(parser, parser.offset, offsetEnd)
}

/**
 * Parse a '-' redis error response
 * @param parser
 * @returns {Error}
 */
function parseError (parser) {
  var string = parseSimpleStringViaOffset(parser)
  if (string !== undefined) {
    if (parser.optionReturnBuffers === true) {
      string = string.toString()
    }
    return new ReplyError(string)
  }
}

/**
 * Parsing error handler, resets parser buffer
 * @param parser
 * @param error
 */
function handleError (parser, error) {
  parser.buffer = null
  parser.returnFatalError(error)
}

/**
 * Parse a '*' redis array response
 * @param parser
 * @returns {*}
 */
function parseArray (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }

  var responses = new Array(length)
  var bufferLength = parser.buffer.length
  for (var i = 0; i < length; i++) {
    if (parser.offset >= bufferLength) {
      return
    }
    var response = parseType(parser, parser.buffer[parser.offset++])
    if (response === undefined) {
      return
    }
    responses[i] = response
  }

  return responses
}

/**
 * Called the appropriate parser for the specified type.
 * @param parser
 * @param type
 * @returns {*}
 */
function parseType (parser, type) {
  switch (type) {
    case 36: // $
      return parseBulkString(parser)
    case 58: // :
      return parseInteger(parser)
    case 43: // +
      return parseSimpleStringViaOffset(parser)
    case 42: // *
      return parseArray(parser)
    case 45: // -
      return parseError(parser)
    default:
      const err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
      err.offset = parser.offset
      err.buffer = JSON.stringify(parser.buffer)
      return handleError(parser, err)
  }
}

// All allowed options including their typeof value
var optionTypes = {
  returnError: 'function',
  returnFatalError: 'function',
  returnReply: 'function',
  returnBuffers: 'boolean',
  stringNumbers: 'boolean',
  name: 'string'
}

/**
 * Javascript Redis Parser
 * @param options
 * @constructor
 */
function JavascriptRedisParser (options) {
  if (!(this instanceof JavascriptRedisParser)) {
    return new JavascriptRedisParser(options)
  }
  if (!options || !options.returnError || !options.returnReply) {
    throw new TypeError('Please provide all return functions while initiating the parser')
  }
  for (var key in options) {
    if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
      throw new TypeError('The options argument contains the property "' + key + '" that is either unkown or of a wrong type')
    }
  }
  if (options.name === 'hiredis') {
    /* istanbul ignore next: hiredis is only supported for legacy usage */
    try {
      var Hiredis = require('./hiredis')
      console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
      return new Hiredis(options)
    } catch (e) {
      console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
    }
  }
  this.optionReturnBuffers = !!options.returnBuffers
  this.optionStringNumbers = !!options.stringNumbers
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'javascript'
  this.offset = 0
  this.buffer = null
  this.bigStrSize = 0
  this.bigOffset = 0
  this.totalChunkSize = 0
  this.bufferCache = []
}

/**
 * Concat a bulk string containing multiple chunks
 *
 * Notes:
 * 1) The first chunk might contain the whole bulk string including the \r
 * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 *
 * @param parser
 * @param buffer
 * @returns {String}
 */
function concatBulkString (parser) {
  var list = parser.bufferCache
  var chunks = list.length
  var offset = parser.bigStrSize - parser.totalChunkSize
  parser.offset = offset
  if (offset === 1) {
    if (chunks === 2) {
      return list[0].toString('utf8', parser.bigOffset, list[0].length - 1)
    }
  } else {
    chunks++
  }
  var res = decoder.write(list[0].slice(parser.bigOffset))
  for (var i = 1; i < chunks - 2; i++) {
    res += decoder.write(list[i])
  }
  res += decoder.end(list[i].slice(0, offset === 1 ? list[i].length - 1 : offset - 2))
  return res
}

/**
 * Decrease the bufferPool size over time
 * @returns {undefined}
 */
function decreaseBufferPool () {
  if (bufferPool.length > 50 * 1024) {
    // Balance between increasing and decreasing the bufferPool
    if (counter === 1 || notDecreased > counter * 2) {
      // Decrease the bufferPool by 10% by removing the first 10% of the current pool
      var sliceLength = Math.floor(bufferPool.length / 10)
      if (bufferOffset <= sliceLength) {
        bufferOffset = 0
      } else {
        bufferOffset -= sliceLength
      }
      bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
    } else {
      notDecreased++
      counter--
    }
  } else {
    clearInterval(interval)
    counter = 0
    notDecreased = 0
    interval = null
  }
}

/**
 * Concat the collected chunks from parser.bufferCache
 * @param parser
 * @param length
 * @returns {Buffer}
 */
function concatBuffer (parser, length) {
  var list = parser.bufferCache
  var pos = bufferOffset
  length -= parser.offset
  if (bufferPool.length < length + bufferOffset) {
    // Increase the bufferPool size
    var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
    if (bufferOffset > 1024 * 1024 * 120) {
      bufferOffset = 1024 * 1024 * 50
    }
    bufferPool = new Buffer(length * multiplier + bufferOffset)
    bufferOffset = 0
    counter++
    pos = 0
    if (interval === null) {
      interval = setInterval(decreaseBufferPool, 50).unref()
    }
  }
  list[0].copy(bufferPool, pos, parser.offset, list[0].length)
  pos += list[0].length - parser.offset
  for (var i = 1; i < list.length; i++) {
    list[i].copy(bufferPool, pos)
    pos += list[i].length
  }
  var buffer = bufferPool.slice(bufferOffset, length + bufferOffset)
  bufferOffset += length
  return buffer
}

/**
 * Parse the redis buffer
 * @param buffer
 * @returns {undefined}
 */
JavascriptRedisParser.prototype.execute = function execute (buffer) {
  if (this.buffer === null) {
    this.buffer = buffer
    this.offset = 0
  } else if (this.bigStrSize === 0) {
    var oldLength = this.buffer.length
    var remainingLength = oldLength - this.offset
    var newBuffer = new Buffer(remainingLength + buffer.length)
    this.buffer.copy(newBuffer, 0, this.offset, oldLength)
    buffer.copy(newBuffer, remainingLength, 0, buffer.length)
    this.buffer = newBuffer
    this.offset = 0
  } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
    this.bufferCache.push(buffer)
    // The returned type might be Array * (42) and in that case we can't improve the parsing currently
    if (this.optionReturnBuffers === false && this.buffer[this.offset] === 36) {
      this.returnReply(concatBulkString(this))
      this.buffer = buffer
    } else { // This applies for arrays too
      this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
      this.offset = 0
    }
    this.bigStrSize = 0
    this.totalChunkSize = 0
    this.bufferCache = []
  } else {
    this.bufferCache.push(buffer)
    this.totalChunkSize += buffer.length
    return
  }

  while (this.offset < this.buffer.length) {
    var offset = this.offset
    var type = this.buffer[this.offset++]
    var response = parseType(this, type)
    if (response === undefined) {
      this.offset = offset
      return
    }

    if (type === 45) {
      this.returnError(response)
    } else {
      this.returnReply(response)
    }
  }

  this.buffer = null
}

module.exports = JavascriptRedisParser
<commit_msg>Parsing Issue #16 change const to var (for support nodejs 0.10.x)<commit_after>'use strict'

var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
var counter = 0
var notDecreased = 0

/**
 * Used for lengths and numbers only, faster perf on arrays / bulks
 * @param parser
 * @returns {*}
 */
function parseSimpleNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length
  var number = 0
  var sign = false

  if (parser.buffer[offset] === 45) {
    sign = true
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13 && parser.buffer[offset] === 10) { // \r\n
      parser.offset = offset + 1
      return sign ? -number : number
    }
    number = (number * 10) + (c1 - 48)
  }
}

/**
 * Used for integer numbers in case of the returnNumbers option
 * @param parser
 * @returns {*}
 */
function parseStringNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length
  var number = ''

  if (parser.buffer[offset] === 45) {
    number += '-'
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13 && parser.buffer[offset] === 10) { // \r\n
      parser.offset = offset + 1
      return number
    }
    number += c1 - 48
  }
}

/**
 * Returns a string or buffer of the provided offset start and
 * end ranges. Checks `optionReturnBuffers`.
 *
 * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 *
 * @param parser
 * @param start
 * @param end
 * @returns {*}
 */
function convertBufferRange (parser, start, end) {
  parser.offset = end + 2
  if (parser.optionReturnBuffers === true) {
    return parser.buffer.slice(start, end)
  }

  return parser.buffer.toString('utf-8', start, end)
}

/**
 * Parse a '+' redis simple string response but forward the offsets
 * onto convertBufferRange to generate a string.
 * @param parser
 * @returns {*}
 */
function parseSimpleStringViaOffset (parser) {
  var start = parser.offset
  var offset = parser.offset
  var length = parser.buffer.length
  var buffer = parser.buffer

  while (offset < length) {
    if (buffer[offset++] === 10) { // \r\n
      return convertBufferRange(parser, start, offset - 2)
    }
  }
}

/**
 * Returns the string length via parseSimpleNumbers
 * @param parser
 * @returns {*}
 */
function parseLength (parser) {
  var string = parseSimpleNumbers(parser)
  if (string !== undefined) {
    return +string
  }
}

/**
 * Parse a ':' redis integer response
 *
 * If stringNumbers is activated the parser always returns numbers as string
 * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 * are 64bit floating point numbers with reduced precision
 *
 * @param parser
 * @returns {*}
 */
function parseInteger (parser) {
  if (parser.optionStringNumbers) {
    return parseStringNumbers(parser)
  }
  return parseSimpleNumbers(parser)
}

/**
 * Parse a '$' redis bulk string response
 * @param parser
 * @returns {*}
 */
function parseBulkString (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var offsetEnd = parser.offset + length
  if (offsetEnd + 2 > parser.buffer.length) {
    parser.bigStrSize = offsetEnd + 2
    parser.bigOffset = parser.offset
    parser.totalChunkSize = parser.buffer.length
    parser.bufferCache.push(parser.buffer)
    return
  }

  return convertBufferRange(parser, parser.offset, offsetEnd)
}

/**
 * Parse a '-' redis error response
 * @param parser
 * @returns {Error}
 */
function parseError (parser) {
  var string = parseSimpleStringViaOffset(parser)
  if (string !== undefined) {
    if (parser.optionReturnBuffers === true) {
      string = string.toString()
    }
    return new ReplyError(string)
  }
}

/**
 * Parsing error handler, resets parser buffer
 * @param parser
 * @param error
 */
function handleError (parser, error) {
  parser.buffer = null
  parser.returnFatalError(error)
}

/**
 * Parse a '*' redis array response
 * @param parser
 * @returns {*}
 */
function parseArray (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }

  var responses = new Array(length)
  var bufferLength = parser.buffer.length
  for (var i = 0; i < length; i++) {
    if (parser.offset >= bufferLength) {
      return
    }
    var response = parseType(parser, parser.buffer[parser.offset++])
    if (response === undefined) {
      return
    }
    responses[i] = response
  }

  return responses
}

/**
 * Called the appropriate parser for the specified type.
 * @param parser
 * @param type
 * @returns {*}
 */
function parseType (parser, type) {
  switch (type) {
    case 36: // $
      return parseBulkString(parser)
    case 58: // :
      return parseInteger(parser)
    case 43: // +
      return parseSimpleStringViaOffset(parser)
    case 42: // *
      return parseArray(parser)
    case 45: // -
      return parseError(parser)
    default:
      var err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
      err.offset = parser.offset
      err.buffer = JSON.stringify(parser.buffer)
      return handleError(parser, err)
  }
}

// All allowed options including their typeof value
var optionTypes = {
  returnError: 'function',
  returnFatalError: 'function',
  returnReply: 'function',
  returnBuffers: 'boolean',
  stringNumbers: 'boolean',
  name: 'string'
}

/**
 * Javascript Redis Parser
 * @param options
 * @constructor
 */
function JavascriptRedisParser (options) {
  if (!(this instanceof JavascriptRedisParser)) {
    return new JavascriptRedisParser(options)
  }
  if (!options || !options.returnError || !options.returnReply) {
    throw new TypeError('Please provide all return functions while initiating the parser')
  }
  for (var key in options) {
    if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
      throw new TypeError('The options argument contains the property "' + key + '" that is either unkown or of a wrong type')
    }
  }
  if (options.name === 'hiredis') {
    /* istanbul ignore next: hiredis is only supported for legacy usage */
    try {
      var Hiredis = require('./hiredis')
      console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
      return new Hiredis(options)
    } catch (e) {
      console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
    }
  }
  this.optionReturnBuffers = !!options.returnBuffers
  this.optionStringNumbers = !!options.stringNumbers
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'javascript'
  this.offset = 0
  this.buffer = null
  this.bigStrSize = 0
  this.bigOffset = 0
  this.totalChunkSize = 0
  this.bufferCache = []
}

/**
 * Concat a bulk string containing multiple chunks
 *
 * Notes:
 * 1) The first chunk might contain the whole bulk string including the \r
 * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 *
 * @param parser
 * @param buffer
 * @returns {String}
 */
function concatBulkString (parser) {
  var list = parser.bufferCache
  var chunks = list.length
  var offset = parser.bigStrSize - parser.totalChunkSize
  parser.offset = offset
  if (offset === 1) {
    if (chunks === 2) {
      return list[0].toString('utf8', parser.bigOffset, list[0].length - 1)
    }
  } else {
    chunks++
  }
  var res = decoder.write(list[0].slice(parser.bigOffset))
  for (var i = 1; i < chunks - 2; i++) {
    res += decoder.write(list[i])
  }
  res += decoder.end(list[i].slice(0, offset === 1 ? list[i].length - 1 : offset - 2))
  return res
}

/**
 * Decrease the bufferPool size over time
 * @returns {undefined}
 */
function decreaseBufferPool () {
  if (bufferPool.length > 50 * 1024) {
    // Balance between increasing and decreasing the bufferPool
    if (counter === 1 || notDecreased > counter * 2) {
      // Decrease the bufferPool by 10% by removing the first 10% of the current pool
      var sliceLength = Math.floor(bufferPool.length / 10)
      if (bufferOffset <= sliceLength) {
        bufferOffset = 0
      } else {
        bufferOffset -= sliceLength
      }
      bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
    } else {
      notDecreased++
      counter--
    }
  } else {
    clearInterval(interval)
    counter = 0
    notDecreased = 0
    interval = null
  }
}

/**
 * Concat the collected chunks from parser.bufferCache
 * @param parser
 * @param length
 * @returns {Buffer}
 */
function concatBuffer (parser, length) {
  var list = parser.bufferCache
  var pos = bufferOffset
  length -= parser.offset
  if (bufferPool.length < length + bufferOffset) {
    // Increase the bufferPool size
    var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
    if (bufferOffset > 1024 * 1024 * 120) {
      bufferOffset = 1024 * 1024 * 50
    }
    bufferPool = new Buffer(length * multiplier + bufferOffset)
    bufferOffset = 0
    counter++
    pos = 0
    if (interval === null) {
      interval = setInterval(decreaseBufferPool, 50).unref()
    }
  }
  list[0].copy(bufferPool, pos, parser.offset, list[0].length)
  pos += list[0].length - parser.offset
  for (var i = 1; i < list.length; i++) {
    list[i].copy(bufferPool, pos)
    pos += list[i].length
  }
  var buffer = bufferPool.slice(bufferOffset, length + bufferOffset)
  bufferOffset += length
  return buffer
}

/**
 * Parse the redis buffer
 * @param buffer
 * @returns {undefined}
 */
JavascriptRedisParser.prototype.execute = function execute (buffer) {
  if (this.buffer === null) {
    this.buffer = buffer
    this.offset = 0
  } else if (this.bigStrSize === 0) {
    var oldLength = this.buffer.length
    var remainingLength = oldLength - this.offset
    var newBuffer = new Buffer(remainingLength + buffer.length)
    this.buffer.copy(newBuffer, 0, this.offset, oldLength)
    buffer.copy(newBuffer, remainingLength, 0, buffer.length)
    this.buffer = newBuffer
    this.offset = 0
  } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
    this.bufferCache.push(buffer)
    // The returned type might be Array * (42) and in that case we can't improve the parsing currently
    if (this.optionReturnBuffers === false && this.buffer[this.offset] === 36) {
      this.returnReply(concatBulkString(this))
      this.buffer = buffer
    } else { // This applies for arrays too
      this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
      this.offset = 0
    }
    this.bigStrSize = 0
    this.totalChunkSize = 0
    this.bufferCache = []
  } else {
    this.bufferCache.push(buffer)
    this.totalChunkSize += buffer.length
    return
  }

  while (this.offset < this.buffer.length) {
    var offset = this.offset
    var type = this.buffer[this.offset++]
    var response = parseType(this, type)
    if (response === undefined) {
      this.offset = offset
      return
    }

    if (type === 45) {
      this.returnError(response)
    } else {
      this.returnReply(response)
    }
  }

  this.buffer = null
}

module.exports = JavascriptRedisParser
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const pegjs = require('pegjs');
const ParserError = require('./parser_error');
const errorCode = require('./error_code');

class Parser
{
	constructor(options = {})
	{
		this.options = options;
	}

	*init()
	{
		for (const grammar of [ 'scene', 'cast' ])
		{
			const grammarData = yield fs.readFileAsync(`${__dirname}/../grammars/${grammar}.pegjs`, 'utf8');
			this[`${grammar}Parser`] = pegjs.buildParser(grammarData);
		}
	}

	parseCast(castData)
	{
		return this.castParser.parse(castData);
	}

	parseGame(scenes, cast)
	{
		if (!cast)
		{
			throw new Error('No cast provided');
		}

		const sceneIDs = new Set(Object.keys(scenes));

		const ret = {};
		for (const sceneID in scenes)
		{
			const sceneData = scenes[sceneID];

			let ast;
			try
			{
				ast = this.sceneParser.parse(sceneData);
			}
			catch (err)
			{
				throw new ParserError(sceneID, err.location, err.found, errorCode.syntaxError, err.message);
			}

			ret[sceneID] = ast;

			const blocks = ast.filter(n => n.type === 'block');
			const blockIDList = blocks.map(b => b.data.id);
			const blockIDs = new Set(blockIDList);

			const verifyNode = (node) =>
			{
				const currentNodeError = (code, message) =>
				{
					return new ParserError(sceneID, node.debug.location, node.debug.raw, code, message);
				};

				const verifyBlockID = (node, blockID) =>
				{
					if (!blockIDs.has(blockID) && blockID !== 'exit')
					{
						throw currentNodeError(errorCode.blockNotFound, `Block ID "${blockID}" is invalid`);
					}
				};

				switch (node.type)
				{
					case 'block':
					{
						for (const child of node.data.entries)
						{
							verifyNode(child);
						}
					}
					break;

					case 'line':
					if (node.data.name && !cast[node.data.name])
					{
						throw currentNodeError(errorCode.castNotFound, `No cast entry found for "${node.data.name}"`);
					}
					break;

					case 'gotoBlock':
					{
						verifyBlockID(node, node.data.blockID);
						node.data.blockIndex = blockIDList.indexOf(node.data.blockID);
					}
					break;

					case 'gotoScene':
					{
						if (!sceneIDs.has(node.data.sceneID))
						{
							throw currentNodeError(errorCode.sceneNotFound, `Scene ID "${node.data.sceneID}" is invalid`);
						}
					}
					break;

					case 'choiceList':
					{
						for (const choice of node.data.choices)
						{
							verifyNode(choice);
						}
					}
					break;

					case 'choice':
					{
						verifyNode(node.data.target);
					}
					break;

					default: throw currentNodeError(errorCode.sceneNotFound, `Unhandled node type: ${node.type}`);
				}

				if (this.options.stripDebugInfo)
				{
					delete node.debug;
				}
			};

			for (const block of blocks)
			{
				verifyNode(block);
			}
		}

		return ret;
	}

	recreateText(scene)
	{
		const recreateNode = function(node)
		{
			switch (node.type)
			{
				case 'block':
				{
					const header = `# ${node.data.id}`;
					const entries = node.data.entries.map(e => recreateNode(e));
					return [ header ].concat(entries).join('\n');
				}

				case 'line':
				{
					const prefix = node.data.name ? `${node.data.name}: ` : '';
					return `${prefix}"${node.data.line}"`;
				}

				case 'gotoBlock': return `=> ${node.data.blockID}`;
				case 'gotoScene': return `=> scene.${node.data.sceneID}`;
				case 'choiceList': return node.data.choices.map(c => recreateNode(c)).join('\n');
				case 'choice': return recreateNode(node.data.target);
			}
		};

		return scene.map(b => recreateNode(b)).join('\n');
	}
}

module.exports = Parser;<commit_msg>Fixed incorrect error code<commit_after>'use strict';

const fs = require('fs');
const pegjs = require('pegjs');
const ParserError = require('./parser_error');
const errorCode = require('./error_code');

class Parser
{
	constructor(options = {})
	{
		this.options = options;
	}

	*init()
	{
		for (const grammar of [ 'scene', 'cast' ])
		{
			const grammarData = yield fs.readFileAsync(`${__dirname}/../grammars/${grammar}.pegjs`, 'utf8');
			this[`${grammar}Parser`] = pegjs.buildParser(grammarData);
		}
	}

	parseCast(castData)
	{
		return this.castParser.parse(castData);
	}

	parseGame(scenes, cast)
	{
		if (!cast)
		{
			throw new Error('No cast provided');
		}

		const sceneIDs = new Set(Object.keys(scenes));

		const ret = {};
		for (const sceneID in scenes)
		{
			const sceneData = scenes[sceneID];

			let ast;
			try
			{
				ast = this.sceneParser.parse(sceneData);
			}
			catch (err)
			{
				throw new ParserError(sceneID, err.location, err.found, errorCode.syntaxError, err.message);
			}

			ret[sceneID] = ast;

			const blocks = ast.filter(n => n.type === 'block');
			const blockIDList = blocks.map(b => b.data.id);
			const blockIDs = new Set(blockIDList);

			const verifyNode = (node) =>
			{
				const currentNodeError = (code, message) =>
				{
					return new ParserError(sceneID, node.debug.location, node.debug.raw, code, message);
				};

				const verifyBlockID = (node, blockID) =>
				{
					if (!blockIDs.has(blockID) && blockID !== 'exit')
					{
						throw currentNodeError(errorCode.blockNotFound, `Block ID "${blockID}" is invalid`);
					}
				};

				switch (node.type)
				{
					case 'block':
					{
						for (const child of node.data.entries)
						{
							verifyNode(child);
						}
					}
					break;

					case 'line':
					if (node.data.name && !cast[node.data.name])
					{
						throw currentNodeError(errorCode.castNotFound, `No cast entry found for "${node.data.name}"`);
					}
					break;

					case 'gotoBlock':
					{
						verifyBlockID(node, node.data.blockID);
						node.data.blockIndex = blockIDList.indexOf(node.data.blockID);
					}
					break;

					case 'gotoScene':
					{
						if (!sceneIDs.has(node.data.sceneID))
						{
							throw currentNodeError(errorCode.sceneNotFound, `Scene ID "${node.data.sceneID}" is invalid`);
						}
					}
					break;

					case 'choiceList':
					{
						for (const choice of node.data.choices)
						{
							verifyNode(choice);
						}
					}
					break;

					case 'choice':
					{
						verifyNode(node.data.target);
					}
					break;

					default: throw currentNodeError(errorCode.invalidNodeType, `Unhandled node type: ${node.type}`);
				}

				if (this.options.stripDebugInfo)
				{
					delete node.debug;
				}
			};

			for (const block of blocks)
			{
				verifyNode(block);
			}
		}

		return ret;
	}

	recreateText(scene)
	{
		const recreateNode = function(node)
		{
			switch (node.type)
			{
				case 'block':
				{
					const header = `# ${node.data.id}`;
					const entries = node.data.entries.map(e => recreateNode(e));
					return [ header ].concat(entries).join('\n');
				}

				case 'line':
				{
					const prefix = node.data.name ? `${node.data.name}: ` : '';
					return `${prefix}"${node.data.line}"`;
				}

				case 'gotoBlock': return `=> ${node.data.blockID}`;
				case 'gotoScene': return `=> scene.${node.data.sceneID}`;
				case 'choiceList': return node.data.choices.map(c => recreateNode(c)).join('\n');
				case 'choice': return recreateNode(node.data.target);
			}
		};

		return scene.map(b => recreateNode(b)).join('\n');
	}
}

module.exports = Parser;<|endoftext|>


<commit_before>/* global module */
module.exports = {
  scenarios: [
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};
<commit_msg>define allowedToFail Settings for ember-try config<commit_after>/* global module */
module.exports = {
  scenarios: [
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      allowedToFail: false,
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      allowedToFail: true,
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};
<|endoftext|>


<commit_before>'use strict';
/*
 * gconfig.path
 * https://github.com/goliatone/gconfig
 *
 * Copyright (c) 2013 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {

    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
    //TODO: Get rid of jquery!
}(this, 'gconfig.path', function() {

    /**
     * Returns the value of a path in dot notation
     * to a `target`s property.
     *
     * @param   {Object} target
     * @param   {String} path
     * @param   {Object} defaultValue
     * @return  {Object}
     * @private
     */
    var _resolvePropertyChain = function(target, path, defaultValue) {
        if (typeof path === 'string') path = path.split('.');
        var l = path.length,
            i = 0,
            p = '';
        for (; i < l; ++i) {
            p = path[i];
            if (target.hasOwnProperty(p)) target = target[p];
            else return defaultValue;
        }
        return target;
    };

    var _setPropertyChain = function(target, key, value) {
        if (!target) throw new Error('WE NEED A VALID TARGET');
        var keys = key.split('.');
        key = keys.pop();
        keys.forEach(function(prop) {
            if (!target[prop]) target[prop] = {};
            target = target[prop];
        });

        target[key] = value;
    };

    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    /**
     * GCPPath constructor
     *
     * @param  {object} config Configuration object.
     */
    var GCPPath = {};
    GCPPath.ID = 'GCPPath';
    GCPPath.VERSION = '0.3.0';

    GCPPath.register = function(GConfig) {
        /*
         * Keep a reference to the original
         * `set` method.
         */
        var _set = GConfig.prototype.set;

        /**
         * Gets the value of a property addressed with
         * dot notation.
         * @param  {String} key          Configuration key
         * @param  {Mixed} defaultValue  Default value
         * @return {Mixed}
         */
        GConfig.prototype.resolve = function(path, defaultValue) {
            return _resolvePropertyChain(this.data, path, defaultValue);
        };

        /**
         * Override original set
         * Set a key value under the given namespace
         * @param {String} key       Property value
         * @param {Object} value     Value to be added
         * @param  {String} namespace Namespace id
         * @return {GConfig}          Fluid interface.
         */
        GConfig.prototype.set = function(key, value, namespace) {
            if (!key) return this;
            if (key.indexOf('.') === -1) return _set.call(this, key, value, namespace);

            /*
             * If we have a namespace, ensure that is not also
             * present in the key path.
             * If we do not have a namespace, then by making up
             * a non existent namespace and providing
             * this.data to `getNamespace` we ensure that we
             * append to this.data.
             * @see getNamespace
             */
            if (namespace) {
                key = key.replace(new RegExp('^' + namespace + '\\.'), '');
            } else namespace = 'SKIP_DEFAULT_NAMESAPCE_GET__DATA_OBJECT';

            _setPropertyChain(this.getNamespace(namespace, this.data), key, value);
            return this;
        };
    };

    /******************************************************
     * EXPOSE HELPER METHODS FOR UNIT TESTING.
    /******************************************************/
    GCPPath.h = {};
    GCPPath.h.setPropertyChain = _setPropertyChain;
    GCPPath.h.resolvePropertyChain = _resolvePropertyChain;

    return GCPPath;
}));<commit_msg>Check path plugin so we only add it once.<commit_after>'use strict';
/*
 * gconfig.path
 * https://github.com/goliatone/gconfig
 *
 * Copyright (c) 2013 goliatone
 * Licensed under the MIT license.
 */
/* jshint strict: false, plusplus: true */
/*global define: false, require: false, module: false, exports: false */
(function(root, name, deps, factory) {

    // Node
    if (typeof deps === 'function') {
        factory = deps;
        deps = [];
    }

    if (typeof exports === 'object') {
        module.exports = factory.apply(root, deps.map(require));
    } else if (typeof define === 'function' && 'amd' in define) {
        //require js, here we assume the file is named as the lower
        //case module name.
        define(name.toLowerCase(), deps, factory);
    } else {
        // Browser
        var d, i = 0,
            global = root,
            old = global[name],
            mod;
        while ((d = deps[i]) !== undefined) deps[i++] = root[d];
        global[name] = mod = factory.apply(global, deps);
        //Export no 'conflict module', aliases the module.
        mod.noConflict = function() {
            global[name] = old;
            return mod;
        };
    }
}(this, 'gconfig.path', function() {

    /**
     * Returns the value of a path in dot notation
     * to a `target`s property.
     *
     * @param   {Object} target
     * @param   {String} path
     * @param   {Object} defaultValue
     * @return  {Object}
     * @private
     */
    var _resolvePropertyChain = function(target, path, defaultValue) {
        if (!target || !path) return false;
        path = path.split('.');
        var l = path.length,
            i = 0,
            p = '';
        for (; i < l; ++i) {
            p = path[i];
            if (target.hasOwnProperty(p)) target = target[p];
            else return defaultValue;
        }
        return target;
    };

    var _setPropertyChain = function(target, key, value) {
        if (!target) return false;

        var keys = key.split('.');
        key = keys.pop();
        keys.forEach(function(prop) {
            if (!target[prop]) target[prop] = {};
            target = target[prop];
        });

        target[key] = value;
    };

    ///////////////////////////////////////////////////
    // CONSTRUCTOR
    ///////////////////////////////////////////////////

    /**
     * GCPPath constructor
     *
     * @param  {object} config Configuration object.
     */
    var GCPPath = {};
    GCPPath.ID = 'GCPPath';
    GCPPath.VERSION = '0.3.0';

    GCPPath.register = function(GConfig) {
        if(GConfig.PLUGINS[this.ID]) return true;

        /*
         * Keep a reference to the original
         * `set` method.
         */
        var _set = GConfig.prototype.set;

        /**
         * Gets the value of a property addressed with
         * dot notation.
         * @param  {String} key          Configuration key
         * @param  {Mixed} defaultValue  Default value
         * @return {Mixed}
         */
        GConfig.prototype.resolve = function(path, defaultValue) {
            /*if (path.indexOf('.') !== -1) {
                var namespace = path.split('.')[0];
                if (!this.data.hasOwnProperty(namespace)) this.logger.warn('Invalid path');
            }*/
            return _resolvePropertyChain(this.data, path, defaultValue);
        };

        /**
         * Override original set
         * Set a key value under the given namespace
         * @param {String} key       Property value
         * @param {Object} value     Value to be added
         * @param  {String} namespace Namespace id
         * @return {GConfig}          Fluid interface.
         */
        GConfig.prototype.set = function(key, value, namespace) {
            if (!key) return this;
            if (key.indexOf('.') === -1) return _set.call(this, key, value, namespace);

            /*
             * If we have a namespace, ensure that is not also
             * present in the key path.
             * If we do not have a namespace, then by making up
             * a non existent namespace and providing
             * this.data to `getNamespace` we ensure that we
             * append to this.data.
             * @see getNamespace
             */
            if (namespace) {
                key = key.replace(new RegExp('^' + namespace + '\\.'), '');
            } else namespace = 'SKIP_DEFAULT_NAMESAPCE_GET__DATA_OBJECT';

            _setPropertyChain(this.getNamespace(namespace, this.data), key, value);
            return this;
        };
    };

    /******************************************************
     * EXPOSE HELPER METHODS FOR UNIT TESTING.
    /******************************************************/
    GCPPath.h = {};
    GCPPath.h.setPropertyChain = _setPropertyChain;
    GCPPath.h.resolvePropertyChain = _resolvePropertyChain;

    return GCPPath;
}));<|endoftext|>


<commit_before>'use strict'

const getChannelURL = require('ember-source-channel-url')

module.exports = function() {
  return Promise.all([
    getChannelURL('release'),
    getChannelURL('beta'),
    getChannelURL('canary'),
  ]).then(urls => {
    return {
      scenarios: [
        {
          name: 'ember-lts-2.18',
          env: {
            EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }),
          },
          npm: {
            devDependencies: {
              '@ember/jquery': '^0.5.1',
              'ember-source': '~2.18.0',
            },
          },
        },
        {
          name: 'ember-lts-3.4',
          npm: {
            devDependencies: {
              'ember-source': '~3.4.0',
            },
          },
        },
        {
          name: 'ember-release',
          npm: {
            devDependencies: {
              'ember-source': urls[0],
            },
          },
        },
        {
          name: 'ember-beta',
          npm: {
            devDependencies: {
              'ember-source': urls[1],
            },
          },
        },
        {
          name: 'ember-canary',
          npm: {
            devDependencies: {
              'ember-source': urls[2],
            },
          },
        },
        // The default `.travis.yml` runs this scenario via `npm test`,
        // not via `ember try`. It's still included here so that running
        // `ember try:each` manually or from a customized CI config will run it
        // along with all the other scenarios.
        {
          name: 'ember-default',
          npm: {
            devDependencies: {},
          },
        },
        {
          name: 'ember-default-with-jquery',
          env: {
            EMBER_OPTIONAL_FEATURES: JSON.stringify({
              'jquery-integration': true,
            }),
          },
          npm: {
            devDependencies: {
              '@ember/jquery': '^0.5.1',
            },
          },
        },
      ],
    }
  })
}
<commit_msg>ensure ember-try uses yarn to install dependencies<commit_after>'use strict'

const getChannelURL = require('ember-source-channel-url')

module.exports = function() {
  return Promise.all([
    getChannelURL('release'),
    getChannelURL('beta'),
    getChannelURL('canary'),
  ]).then(urls => {
    return {
      useYarn: true,
      scenarios: [
        {
          name: 'ember-lts-2.18',
          env: {
            EMBER_OPTIONAL_FEATURES: JSON.stringify({ 'jquery-integration': true }),
          },
          npm: {
            devDependencies: {
              '@ember/jquery': '^0.5.1',
              'ember-source': '~2.18.0',
            },
          },
        },
        {
          name: 'ember-lts-3.4',
          npm: {
            devDependencies: {
              'ember-source': '~3.4.0',
            },
          },
        },
        {
          name: 'ember-release',
          npm: {
            devDependencies: {
              'ember-source': urls[0],
            },
          },
        },
        {
          name: 'ember-beta',
          npm: {
            devDependencies: {
              'ember-source': urls[1],
            },
          },
        },
        {
          name: 'ember-canary',
          npm: {
            devDependencies: {
              'ember-source': urls[2],
            },
          },
        },
        // The default `.travis.yml` runs this scenario via `npm test`,
        // not via `ember try`. It's still included here so that running
        // `ember try:each` manually or from a customized CI config will run it
        // along with all the other scenarios.
        {
          name: 'ember-default',
          npm: {
            devDependencies: {},
          },
        },
        {
          name: 'ember-default-with-jquery',
          env: {
            EMBER_OPTIONAL_FEATURES: JSON.stringify({
              'jquery-integration': true,
            }),
          },
          npm: {
            devDependencies: {
              '@ember/jquery': '^0.5.1',
            },
          },
        },
      ],
    }
  })
}
<|endoftext|>


<commit_before>/* @flow */
import defaultADB from 'adbkit';

import {
  isErrorWithCode,
  UsageError,
  WebExtError,
} from '../errors';
import {createLogger} from '../util/logger';

const log = createLogger(__filename);

export type ADBUtilsParams = {|
  adb?: typeof defaultADB,
  // ADB configs.
  adbBin?: string,
  adbHost?: string,
  adbPort?: string,
  adbDevice?: string,
|};

export type DiscoveryParams = {|
  maxDiscoveryTime: number,
  retryInterval: number,
|};

// Helper function used to raise an UsageError when the adb binary has not been found.
async function wrapADBCall(asyncFn: (...any) => Promise<any>): Promise<any> {
  try {
    return await asyncFn();
  } catch (error) {
    if (isErrorWithCode('ENOENT', error) &&
        error.message.includes('spawn adb')) {
      throw new UsageError(
        'No adb executable has been found. ' +
          'You can Use --adb-bin, --adb-host/--adb-port ' +
          'to configure it manually if needed.');
    }

    throw error;
  }
}

export default class ADBUtils {
  params: ADBUtilsParams;
  adb: typeof defaultADB;
  adbClient: any; // TODO: better flow typing here.

  // Map<deviceId -> artifactsDir>
  artifactsDirMap: Map<string, string>;
  // Toggled when the user wants to abort the RDP Unix Socket discovery loop
  // while it is still executing.
  userAbortDiscovery: boolean;

  constructor(params: ADBUtilsParams) {
    this.params = params;

    const {
      adb,
      adbBin,
      adbHost,
      adbPort,
    } = params;

    this.adb = adb || defaultADB;

    this.adbClient = this.adb.createClient({
      bin: adbBin,
      host: adbHost,
      port: adbPort,
    });

    this.artifactsDirMap = new Map();

    this.userAbortDiscovery = false;
  }

  runShellCommand(
    deviceId: string, cmd: string | Array<string>
  ): Promise<string> {
    const {adb, adbClient} = this;

    log.debug(`Run adb shell command on ${deviceId}: ${JSON.stringify(cmd)}`);

    return wrapADBCall(async () => {
      return await adbClient.shell(deviceId, cmd).then(adb.util.readAll);
    }).then((res) => res.toString());
  }

  async discoverDevices(): Promise<Array<string>> {
    const {adbClient} = this;

    let devices = [];

    log.debug('Listing android devices');
    devices = await wrapADBCall(async () => adbClient.listDevices());

    return devices.map((dev) => dev.id);
  }

  async discoverInstalledFirefoxAPKs(
    deviceId: string,
    firefoxApk?: string
  ): Promise<Array<string>> {
    log.debug(`Listing installed Firefox APKs on ${deviceId}`);

    const pmList = await this.runShellCommand(deviceId, [
      'pm', 'list', 'packages',
    ]);

    return pmList.split('\n')
      .map((line) => line.replace('package:', '').trim())
      .filter((line) => {
        // Look for an exact match if firefoxApk is defined.
        if (firefoxApk) {
          return line === firefoxApk;
        }
        // Match any package name that starts with the package name of a Firefox for Android browser.
        return (
          line.startsWith('org.mozilla.fennec') ||
            line.startsWith('org.mozilla.firefox')
        );
      });
  }

  async getAndroidVersionNumber(deviceId: string): Promise<number> {
    const androidVersion = (await this.runShellCommand(deviceId, [
      'getprop', 'ro.build.version.sdk',
    ])).trim();

    const androidVersionNumber = parseInt(androidVersion);

    // No need to check the granted runtime permissions on Android versions < Lollypop.
    if (isNaN(androidVersionNumber)) {
      throw new WebExtError(
        'Unable to discovery android version on ' +
        `${deviceId}: ${androidVersion}`
      );
    }

    return androidVersionNumber;
  }

  // Raise an UsageError when the given APK does not have the required runtime permissions.
  async ensureRequiredAPKRuntimePermissions(
    deviceId: string, apk: string, permissions: Array<string>
  ): Promise<void> {
    const permissionsMap = {};

    // Initialize every permission to false in the permissions map.
    for (const perm of permissions) {
      permissionsMap[perm] = false;
    }

    // Retrieve the permissions information for the given apk.
    const pmDumpLogs = (await this.runShellCommand(deviceId, [
      'pm', 'dump', apk,
    ])).split('\n');

    // Set to true the required permissions that have been granted.
    for (const line of pmDumpLogs) {
      for (const perm of permissions) {
        if (line.includes(`${perm}: granted=true`)) {
          permissionsMap[perm] = true;
        }
      }
    }

    for (const perm of permissions) {
      if (!permissionsMap[perm]) {
        throw new UsageError(
          `Required ${perm} has not be granted for ${apk}. ` +
          'Please grant them using the Android Settings ' +
          'or using the following adb command:\n' +
          `\t adb shell pm grant ${apk} ${perm}\n`
        );
      }
    }
  }

  async amForceStopAPK(deviceId: string, apk: string): Promise<void> {
    await this.runShellCommand(deviceId, [
      'am', 'force-stop', apk,
    ]);
  }

  async getOrCreateArtifactsDir(deviceId: string): Promise<string> {
    let artifactsDir = this.artifactsDirMap.get(deviceId);

    if (artifactsDir) {
      return artifactsDir;
    }

    artifactsDir = `/sdcard/web-ext-artifacts-${Date.now()}`;

    const testDirOut = (await this.runShellCommand(
      deviceId, `test -d ${artifactsDir} ; echo $?`
    )).trim();

    if (testDirOut !== '1') {
      throw new WebExtError(
        `Cannot create artifacts directory ${artifactsDir} ` +
        `because it exists on ${deviceId}.`
      );
    }

    await this.runShellCommand(deviceId, ['mkdir', '-p', artifactsDir]);

    this.artifactsDirMap.set(deviceId, artifactsDir);

    return artifactsDir;
  }

  async clearArtifactsDir(deviceId: string): Promise<void> {
    const artifactsDir = this.artifactsDirMap.get(deviceId);

    if (!artifactsDir) {
      // nothing to do here.
      return;
    }

    this.artifactsDirMap.delete(deviceId);

    log.debug(
      `Removing ${artifactsDir} artifacts directory on ${deviceId} device`
    );

    await this.runShellCommand(deviceId, [
      'rm', '-rf', artifactsDir,
    ]);
  }

  async pushFile(
    deviceId: string, localPath: string, devicePath: string
  ): Promise<void> {
    const {adbClient} = this;

    log.debug(`Pushing ${localPath} to ${devicePath} on ${deviceId}`);

    await wrapADBCall(async () => {
      await adbClient.push(deviceId, localPath, devicePath)
        .then(function(transfer) {
          return new Promise((resolve) => {
            transfer.on('end', resolve);
          });
        });
    });
  }

  async startFirefoxAPK(
    deviceId: string, apk: string, deviceProfileDir: string
  ): Promise<void> {
    const {adbClient} = this;

    log.debug(
      `Starting ${apk} with profile ${deviceProfileDir} on ${deviceId}`
    );

    await wrapADBCall(async () => {
      await adbClient.startActivity(deviceId, {
        wait: true,
        action: 'android.activity.MAIN',
        component: `${apk}/.App`,
        extras: [
          {
            key: 'args',
            value: `-profile ${deviceProfileDir}`,
          },
        ],
      });
    });
  }

  setUserAbortDiscovery(value: boolean) {
    this.userAbortDiscovery = value;
  }

  async discoverRDPUnixSocket(
    deviceId: string, apk: string,
    {maxDiscoveryTime, retryInterval}: DiscoveryParams = {}
  ): Promise<string> {
    let rdpUnixSockets = [];

    const discoveryStartedAt = Date.now();

    while (rdpUnixSockets.length === 0) {
      if (this.userAbortDiscovery) {
        throw new UsageError(
          'Exiting Firefox Remote Debugging socket discovery on user request'
        );
      }

      if (Date.now() - discoveryStartedAt > maxDiscoveryTime) {
        throw new WebExtError(
          'Timeout while waiting for the Android Firefox Debugger Socket'
        );
      }

      rdpUnixSockets = (await this.runShellCommand(deviceId, [
        'cat', '/proc/net/unix',
      ])).split('\n').filter((line) => {
        // The RDP unix socket is expected to be a path in the form:
        //   /data/data/org.mozilla.fennec_rpl/firefox-debugger-socket
        return line.trim().endsWith(`${apk}/firefox-debugger-socket`);
      });

      if (rdpUnixSockets.length === 0) {
        await new Promise((resolve) => setTimeout(resolve, retryInterval));
      }
    }

    // Convert into an array of unix socket filenames.
    rdpUnixSockets = rdpUnixSockets.map((line) => {
      return line.trim().split(/\s/).pop();
    });

    if (rdpUnixSockets.length > 1) {
      throw new WebExtError(
        'Unexpected multiple RDP sockets: ' +
        `${JSON.stringify(rdpUnixSockets)}`
      );
    }

    return rdpUnixSockets[0];
  }

  async setupForward(deviceId: string, remote: string, local: string) {
    const {adbClient} = this;

    // TODO(rpl): we should use adb.listForwards and reuse the existing one if any (especially
    // because adbkit doesn't seem to support `adb forward --remote` yet).
    log.debug(`Configuring ADB forward for ${deviceId}: ${remote} -> ${local}`);

    await wrapADBCall(async () => {
      await adbClient.forward(deviceId, local, remote);
    });
  }
}
<commit_msg>fix: Allow comma as separator when checking permissions via adb<commit_after>/* @flow */
import defaultADB from 'adbkit';

import {
  isErrorWithCode,
  UsageError,
  WebExtError,
} from '../errors';
import {createLogger} from '../util/logger';

const log = createLogger(__filename);

export type ADBUtilsParams = {|
  adb?: typeof defaultADB,
  // ADB configs.
  adbBin?: string,
  adbHost?: string,
  adbPort?: string,
  adbDevice?: string,
|};

export type DiscoveryParams = {|
  maxDiscoveryTime: number,
  retryInterval: number,
|};

// Helper function used to raise an UsageError when the adb binary has not been found.
async function wrapADBCall(asyncFn: (...any) => Promise<any>): Promise<any> {
  try {
    return await asyncFn();
  } catch (error) {
    if (isErrorWithCode('ENOENT', error) &&
        error.message.includes('spawn adb')) {
      throw new UsageError(
        'No adb executable has been found. ' +
          'You can Use --adb-bin, --adb-host/--adb-port ' +
          'to configure it manually if needed.');
    }

    throw error;
  }
}

export default class ADBUtils {
  params: ADBUtilsParams;
  adb: typeof defaultADB;
  adbClient: any; // TODO: better flow typing here.

  // Map<deviceId -> artifactsDir>
  artifactsDirMap: Map<string, string>;
  // Toggled when the user wants to abort the RDP Unix Socket discovery loop
  // while it is still executing.
  userAbortDiscovery: boolean;

  constructor(params: ADBUtilsParams) {
    this.params = params;

    const {
      adb,
      adbBin,
      adbHost,
      adbPort,
    } = params;

    this.adb = adb || defaultADB;

    this.adbClient = this.adb.createClient({
      bin: adbBin,
      host: adbHost,
      port: adbPort,
    });

    this.artifactsDirMap = new Map();

    this.userAbortDiscovery = false;
  }

  runShellCommand(
    deviceId: string, cmd: string | Array<string>
  ): Promise<string> {
    const {adb, adbClient} = this;

    log.debug(`Run adb shell command on ${deviceId}: ${JSON.stringify(cmd)}`);

    return wrapADBCall(async () => {
      return await adbClient.shell(deviceId, cmd).then(adb.util.readAll);
    }).then((res) => res.toString());
  }

  async discoverDevices(): Promise<Array<string>> {
    const {adbClient} = this;

    let devices = [];

    log.debug('Listing android devices');
    devices = await wrapADBCall(async () => adbClient.listDevices());

    return devices.map((dev) => dev.id);
  }

  async discoverInstalledFirefoxAPKs(
    deviceId: string,
    firefoxApk?: string
  ): Promise<Array<string>> {
    log.debug(`Listing installed Firefox APKs on ${deviceId}`);

    const pmList = await this.runShellCommand(deviceId, [
      'pm', 'list', 'packages',
    ]);

    return pmList.split('\n')
      .map((line) => line.replace('package:', '').trim())
      .filter((line) => {
        // Look for an exact match if firefoxApk is defined.
        if (firefoxApk) {
          return line === firefoxApk;
        }
        // Match any package name that starts with the package name of a Firefox for Android browser.
        return (
          line.startsWith('org.mozilla.fennec') ||
            line.startsWith('org.mozilla.firefox')
        );
      });
  }

  async getAndroidVersionNumber(deviceId: string): Promise<number> {
    const androidVersion = (await this.runShellCommand(deviceId, [
      'getprop', 'ro.build.version.sdk',
    ])).trim();

    const androidVersionNumber = parseInt(androidVersion);

    // No need to check the granted runtime permissions on Android versions < Lollypop.
    if (isNaN(androidVersionNumber)) {
      throw new WebExtError(
        'Unable to discovery android version on ' +
        `${deviceId}: ${androidVersion}`
      );
    }

    return androidVersionNumber;
  }

  // Raise an UsageError when the given APK does not have the required runtime permissions.
  async ensureRequiredAPKRuntimePermissions(
    deviceId: string, apk: string, permissions: Array<string>
  ): Promise<void> {
    const permissionsMap = {};

    // Initialize every permission to false in the permissions map.
    for (const perm of permissions) {
      permissionsMap[perm] = false;
    }

    // Retrieve the permissions information for the given apk.
    const pmDumpLogs = (await this.runShellCommand(deviceId, [
      'pm', 'dump', apk,
    ])).split('\n');

    // Set to true the required permissions that have been granted.
    for (const line of pmDumpLogs) {
      for (const perm of permissions) {
        if (line.includes(`${perm}: granted=true`) ||
            line.includes(`${perm}, granted=true`)) {
          permissionsMap[perm] = true;
        }
      }
    }

    for (const perm of permissions) {
      if (!permissionsMap[perm]) {
        throw new UsageError(
          `Required ${perm} has not be granted for ${apk}. ` +
          'Please grant them using the Android Settings ' +
          'or using the following adb command:\n' +
          `\t adb shell pm grant ${apk} ${perm}\n`
        );
      }
    }
  }

  async amForceStopAPK(deviceId: string, apk: string): Promise<void> {
    await this.runShellCommand(deviceId, [
      'am', 'force-stop', apk,
    ]);
  }

  async getOrCreateArtifactsDir(deviceId: string): Promise<string> {
    let artifactsDir = this.artifactsDirMap.get(deviceId);

    if (artifactsDir) {
      return artifactsDir;
    }

    artifactsDir = `/sdcard/web-ext-artifacts-${Date.now()}`;

    const testDirOut = (await this.runShellCommand(
      deviceId, `test -d ${artifactsDir} ; echo $?`
    )).trim();

    if (testDirOut !== '1') {
      throw new WebExtError(
        `Cannot create artifacts directory ${artifactsDir} ` +
        `because it exists on ${deviceId}.`
      );
    }

    await this.runShellCommand(deviceId, ['mkdir', '-p', artifactsDir]);

    this.artifactsDirMap.set(deviceId, artifactsDir);

    return artifactsDir;
  }

  async clearArtifactsDir(deviceId: string): Promise<void> {
    const artifactsDir = this.artifactsDirMap.get(deviceId);

    if (!artifactsDir) {
      // nothing to do here.
      return;
    }

    this.artifactsDirMap.delete(deviceId);

    log.debug(
      `Removing ${artifactsDir} artifacts directory on ${deviceId} device`
    );

    await this.runShellCommand(deviceId, [
      'rm', '-rf', artifactsDir,
    ]);
  }

  async pushFile(
    deviceId: string, localPath: string, devicePath: string
  ): Promise<void> {
    const {adbClient} = this;

    log.debug(`Pushing ${localPath} to ${devicePath} on ${deviceId}`);

    await wrapADBCall(async () => {
      await adbClient.push(deviceId, localPath, devicePath)
        .then(function(transfer) {
          return new Promise((resolve) => {
            transfer.on('end', resolve);
          });
        });
    });
  }

  async startFirefoxAPK(
    deviceId: string, apk: string, deviceProfileDir: string
  ): Promise<void> {
    const {adbClient} = this;

    log.debug(
      `Starting ${apk} with profile ${deviceProfileDir} on ${deviceId}`
    );

    await wrapADBCall(async () => {
      await adbClient.startActivity(deviceId, {
        wait: true,
        action: 'android.activity.MAIN',
        component: `${apk}/.App`,
        extras: [
          {
            key: 'args',
            value: `-profile ${deviceProfileDir}`,
          },
        ],
      });
    });
  }

  setUserAbortDiscovery(value: boolean) {
    this.userAbortDiscovery = value;
  }

  async discoverRDPUnixSocket(
    deviceId: string, apk: string,
    {maxDiscoveryTime, retryInterval}: DiscoveryParams = {}
  ): Promise<string> {
    let rdpUnixSockets = [];

    const discoveryStartedAt = Date.now();

    while (rdpUnixSockets.length === 0) {
      if (this.userAbortDiscovery) {
        throw new UsageError(
          'Exiting Firefox Remote Debugging socket discovery on user request'
        );
      }

      if (Date.now() - discoveryStartedAt > maxDiscoveryTime) {
        throw new WebExtError(
          'Timeout while waiting for the Android Firefox Debugger Socket'
        );
      }

      rdpUnixSockets = (await this.runShellCommand(deviceId, [
        'cat', '/proc/net/unix',
      ])).split('\n').filter((line) => {
        // The RDP unix socket is expected to be a path in the form:
        //   /data/data/org.mozilla.fennec_rpl/firefox-debugger-socket
        return line.trim().endsWith(`${apk}/firefox-debugger-socket`);
      });

      if (rdpUnixSockets.length === 0) {
        await new Promise((resolve) => setTimeout(resolve, retryInterval));
      }
    }

    // Convert into an array of unix socket filenames.
    rdpUnixSockets = rdpUnixSockets.map((line) => {
      return line.trim().split(/\s/).pop();
    });

    if (rdpUnixSockets.length > 1) {
      throw new WebExtError(
        'Unexpected multiple RDP sockets: ' +
        `${JSON.stringify(rdpUnixSockets)}`
      );
    }

    return rdpUnixSockets[0];
  }

  async setupForward(deviceId: string, remote: string, local: string) {
    const {adbClient} = this;

    // TODO(rpl): we should use adb.listForwards and reuse the existing one if any (especially
    // because adbkit doesn't seem to support `adb forward --remote` yet).
    log.debug(`Configuring ADB forward for ${deviceId}: ${remote} -> ${local}`);

    await wrapADBCall(async () => {
      await adbClient.forward(deviceId, local, remote);
    });
  }
}
<|endoftext|>


<commit_before>/* eslint-env node */
module.exports = {
  useYarn: true,
  scenarios: [
    {
      name: 'ember-1.10',
      bower: {
        dependencies: {
          'ember': 'components/ember#1.10.0'
        },
        resolutions: {
          'ember': '1.10.0'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-1.13',
      bower: {
        dependencies: {
          'ember': 'components/ember#1.13.13'
        },
        resolutions: {
          'ember': '1.13.13'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.12',
      npm: {
        devDependencies: {
          'ember-source': '~2.12.0'
        }
      }
    },
    {
      name: 'ember-lts-2.16',
      npm: {
        devDependencies: {
          'ember-source': '~2.16.0'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]
};
<commit_msg>Update ember-try.js<commit_after>/* eslint-env node */
module.exports = {
  useYarn: true,
  scenarios: [
    {
      name: 'ember-1.13',
      bower: {
        dependencies: {
          'ember': 'components/ember#1.13.13'
        },
        resolutions: {
          'ember': '1.13.13'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.12',
      npm: {
        devDependencies: {
          'ember-source': '~2.12.0'
        }
      }
    },
    {
      name: 'ember-lts-2.16',
      npm: {
        devDependencies: {
          'ember-source': '~2.16.0'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]
};
<|endoftext|>


<commit_before>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'ember-release',
      bower: {
        devDependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source':  null,
          'ember-data': 'emberjs/data#release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        devDependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source':  null,
          'ember-data': 'emberjs/data#beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null,
          'ember-data': 'emberjs/data#canary'
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]};
;
<commit_msg>there is no ember-data canary use master instead<commit_after>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'ember-release',
      bower: {
        devDependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source':  null,
          'ember-data': 'emberjs/data#release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        devDependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source':  null,
          'ember-data': 'emberjs/data#beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null,
          'ember-data': 'emberjs/data#master'
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]};
;
<|endoftext|>


<commit_before>var p = Ent.Project.create('polaris', 'js');
p.setVersion(1, 1, 0);
p.setBolt('library', '**/*.js');
p.setBoltTest();

<commit_msg>TBIO-257: bumped version<commit_after>var p = Ent.Project.create('polaris', 'js');
p.setVersion(1, 2, 0);
p.setBolt('library', '**/*.js');
p.setBoltTest();

<|endoftext|>


<commit_before>(function() {

  // List of ports and the devices they map to
  var portToDevice = [];
  var currentDevice = undefined;

  // called when data received
  var readListener = undefined;

  // For throttled write
  var slowWrite = true;
  var writeData = undefined;
  var writeTimeout = undefined;

  function init() {
    Espruino.Core.Config.add("BAUD_RATE", {
      section : "Communications",
      name : "Baud Rate",
      description : "When connecting over serial, this is the baud rate that is used. 9600 is the default for Espruino",
      type : {9600:9600,14400:14400,19200:19200,28800:28800,38400:38400,57600:57600,115200:115200},
      defaultValue : 9600,
    });

    var devices = Espruino.Core.Serial.devices;
    for (var i=0;i<devices.length;i++)
      if (devices[i].init)
        devices[i].init();
  }

  var startListening=function(callback) {
    var oldListener = readListener;
    readListener = callback;
    return oldListener;
  };

  var getPorts=function(callback) {
    var ports = [];
    portToDevice = [];
    // get all devices
    var responses = 0;
    var devices = Espruino.Core.Serial.devices;
    devices.forEach(function (device) {
      device.getPorts(function(devicePorts) {
        if (devicePorts) {
          devicePorts.forEach(function(port) {
            ports.push(port);
            portToDevice[port] = device;
          });
        }
        responses++;
        if (responses == devices.length)
          callback(ports);
      });
    });
  };

  var openSerial=function(serialPort, connectCallback, disconnectCallback) {
    if (!serialPort in portToDevice) {
      console.error("Port "+JSON.stringify(serialPort)+" not found");
      return connectCallback(undefined);
    }
    currentDevice = portToDevice[serialPort];
    if (!currentDevice) {
      console.error("Could not get device for port "+JSON.stringify(serialPort));
      return connectCallback(undefined);
    }
    currentDevice.open(serialPort, function(cInfo) {
      // CONNECT
      if (!cInfo) {
//        Espruino.Core.Notifications.error("Unable to connect");
        console.error("Unable to open device (connectionInfo="+cInfo+")");
        connectCallback(undefined);
      } else {
        connectionInfo = cInfo;
        connectedPort = serialPort;
        console.log("Connected", cInfo);
        Espruino.callProcessor("connected", undefined, function() {
          connectCallback(cInfo);
        });
      }
    }, function(data) {
      // RECEIEVE DATA
      if (readListener) readListener(data);
    }, function() {
      // DISCONNECT
      currentDevice = undefined;
      if (writeTimeout!==undefined)
        clearTimeout(writeTimeout);
      writeTimeout = undefined;
      writeData = undefined;

      Espruino.callProcessor("disconnected", undefined, function() {
        disconnectCallback();
      });
    });
  };

  var str2ab=function(str) {
    var buf=new ArrayBuffer(str.length);
    var bufView=new Uint8Array(buf);
    for (var i=0; i<str.length; i++) {
      var ch = str.charCodeAt(i);
      if (ch>=256) {
        console.warn("Attempted to send non-8 bit character - code "+ch);
        ch = "?".charCodeAt(0);
      }
      bufView[i] = ch;
    }
    return buf;
  };

  var closeSerial=function() {
    if (currentDevice)
      currentDevice.close();
    else
      console.error("Close called, but serial port not open");
  };

  var isConnected = function() {
    return currentDevice!==undefined;
  };

  // Throttled serial write
  var writeSerial = function(data, showStatus) {
    if (!isConnected()) return; // throw data away
    if (showStatus===undefined) showStatus=true;

    // Queue our data to write
    var wasSending = false;
    if (writeData == undefined) {
      writeData = data;
    } else {
      writeData += data;
      wasSending = true;
    }

    /* if we're throttling our writes we want to send small
     * blocks of data at once */
    var blockSize = slowWrite ? 15 : 512;

    showStatus &= writeData.length>blockSize;
    if (showStatus) {
      Espruino.Core.Status.setStatus("Sending...", writeData.length);
      console.log("---> "+JSON.stringify(data));
    }

    function sender() {
      writeTimeout = undefined; // we've been called

      if (writeData!==undefined) {
        var d = undefined;
        if (writeData.length>blockSize) {
          d = writeData.substr(0,blockSize);
          writeData = writeData.substr(blockSize);
        } else {
          d = writeData;
          writeData = undefined;
        }
        // update status
        if (showStatus)
          Espruino.Core.Status.incrementProgress(d.length);
        // actually write data
        currentDevice.write(d, function() {
          // now written...
          if (writeData!==undefined) {
            writeTimeout = setTimeout(sender, 50);
          } else {
            if (showStatus)
              Espruino.Core.Status.setStatus("Sent");
          }
        });
      }
    }

    if (!wasSending) {
      sender(); // start sending instantly
    }
  };

  // ----------------------------------------------------------
  Espruino.Core.Serial = {
    "devices" : [], // List of devices that can provide a serial API
    "init" : init,
    "getPorts": getPorts,
    "open": openSerial,
    "isConnected": isConnected,
    "startListening": startListening,
    "write": writeSerial,
    "close": closeSerial,
	"isSlowWrite": function() { return slowWrite; },
	"setSlowWrite": function(isOn, force) {
        if ((!force) && Espruino.Config.SERIAL_THROTTLE_SEND) {
          console.log("ForceThrottle option is set - set Slow Write = true");
          isOn = true;
        } else
  	    console.log("Set Slow Write = "+isOn);
	  slowWrite = isOn;
	},
  };
})();
<commit_msg>fixed check whether port exists<commit_after>(function() {

  // List of ports and the devices they map to
  var portToDevice = [];
  var currentDevice = undefined;

  // called when data received
  var readListener = undefined;

  // For throttled write
  var slowWrite = true;
  var writeData = undefined;
  var writeTimeout = undefined;

  function init() {
    Espruino.Core.Config.add("BAUD_RATE", {
      section : "Communications",
      name : "Baud Rate",
      description : "When connecting over serial, this is the baud rate that is used. 9600 is the default for Espruino",
      type : {9600:9600,14400:14400,19200:19200,28800:28800,38400:38400,57600:57600,115200:115200},
      defaultValue : 9600,
    });

    var devices = Espruino.Core.Serial.devices;
    for (var i=0;i<devices.length;i++)
      if (devices[i].init)
        devices[i].init();
  }

  var startListening=function(callback) {
    var oldListener = readListener;
    readListener = callback;
    return oldListener;
  };

  var getPorts=function(callback) {
    var ports = [];
    portToDevice = [];
    // get all devices
    var responses = 0;
    var devices = Espruino.Core.Serial.devices;
    devices.forEach(function (device) {
      device.getPorts(function(devicePorts) {
        if (devicePorts) {
          devicePorts.forEach(function(port) {
            ports.push(port);
            portToDevice[port] = device;
          });
        }
        responses++;
        if (responses == devices.length)
          callback(ports);
      });
    });
  };

  var openSerial=function(serialPort, connectCallback, disconnectCallback) {
    if (!(serialPort in portToDevice)) {
      console.error("Port "+JSON.stringify(serialPort)+" not found");
      return connectCallback(undefined);
    }
    currentDevice = portToDevice[serialPort];
    currentDevice.open(serialPort, function(cInfo) {
      // CONNECT
      if (!cInfo) {
//        Espruino.Core.Notifications.error("Unable to connect");
        console.error("Unable to open device (connectionInfo="+cInfo+")");
        connectCallback(undefined);
      } else {
        connectionInfo = cInfo;
        connectedPort = serialPort;
        console.log("Connected", cInfo);
        Espruino.callProcessor("connected", undefined, function() {
          connectCallback(cInfo);
        });
      }
    }, function(data) {
      // RECEIEVE DATA
      if (readListener) readListener(data);
    }, function() {
      // DISCONNECT
      currentDevice = undefined;
      if (writeTimeout!==undefined)
        clearTimeout(writeTimeout);
      writeTimeout = undefined;
      writeData = undefined;

      Espruino.callProcessor("disconnected", undefined, function() {
        disconnectCallback();
      });
    });
  };

  var str2ab=function(str) {
    var buf=new ArrayBuffer(str.length);
    var bufView=new Uint8Array(buf);
    for (var i=0; i<str.length; i++) {
      var ch = str.charCodeAt(i);
      if (ch>=256) {
        console.warn("Attempted to send non-8 bit character - code "+ch);
        ch = "?".charCodeAt(0);
      }
      bufView[i] = ch;
    }
    return buf;
  };

  var closeSerial=function() {
    if (currentDevice)
      currentDevice.close();
    else
      console.error("Close called, but serial port not open");
  };

  var isConnected = function() {
    return currentDevice!==undefined;
  };

  // Throttled serial write
  var writeSerial = function(data, showStatus) {
    if (!isConnected()) return; // throw data away
    if (showStatus===undefined) showStatus=true;

    // Queue our data to write
    var wasSending = false;
    if (writeData == undefined) {
      writeData = data;
    } else {
      writeData += data;
      wasSending = true;
    }

    /* if we're throttling our writes we want to send small
     * blocks of data at once */
    var blockSize = slowWrite ? 15 : 512;

    showStatus &= writeData.length>blockSize;
    if (showStatus) {
      Espruino.Core.Status.setStatus("Sending...", writeData.length);
      console.log("---> "+JSON.stringify(data));
    }

    function sender() {
      writeTimeout = undefined; // we've been called

      if (writeData!==undefined) {
        var d = undefined;
        if (writeData.length>blockSize) {
          d = writeData.substr(0,blockSize);
          writeData = writeData.substr(blockSize);
        } else {
          d = writeData;
          writeData = undefined;
        }
        // update status
        if (showStatus)
          Espruino.Core.Status.incrementProgress(d.length);
        // actually write data
        currentDevice.write(d, function() {
          // now written...
          if (writeData!==undefined) {
            writeTimeout = setTimeout(sender, 50);
          } else {
            if (showStatus)
              Espruino.Core.Status.setStatus("Sent");
          }
        });
      }
    }

    if (!wasSending) {
      sender(); // start sending instantly
    }
  };

  // ----------------------------------------------------------
  Espruino.Core.Serial = {
    "devices" : [], // List of devices that can provide a serial API
    "init" : init,
    "getPorts": getPorts,
    "open": openSerial,
    "isConnected": isConnected,
    "startListening": startListening,
    "write": writeSerial,
    "close": closeSerial,
	"isSlowWrite": function() { return slowWrite; },
	"setSlowWrite": function(isOn, force) {
        if ((!force) && Espruino.Config.SERIAL_THROTTLE_SEND) {
          console.log("ForceThrottle option is set - set Slow Write = true");
          isOn = true;
        } else
  	    console.log("Set Slow Write = "+isOn);
	  slowWrite = isOn;
	},
  };
})();
<|endoftext|>