<commit_before>'use strict';

/*
 * Simple API tests
 */

/*
 * Could also check out the nock package to record / replay http interactions
 */

var restbase = require('../lib/server.js');
var preq = require('preq');
var assert = require('assert');
var hostPort = 'http://localhost:7231';
var baseURL = hostPort + '/v1/en.wikipedia.org';
var bucketURL = baseURL + '/test101';

function deepEqual (result, expected) {
    try {
        assert.deepEqual(result, expected);
    } catch (e) {
        console.log('Expected:\n' + JSON.stringify(expected,null,2));
        console.log('Result:\n' + JSON.stringify(result,null,2));
        throw e;
    }
}

describe('Simple API tests', function () {
    this.timeout(20000);
    before(function() {
        return restbase({
            logging: {
                name: 'restbase-tests',
                level: 'warn'
            }
        });
    });
    describe('Domain & bucket creation', function() {
        it('should create a domain', function() {
            return preq.put({
                uri: hostPort + '/v1/en.wikipedia.org',
                headers: { 'content-type': 'application/json' },
                body: {}
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            });
        });
    });
    describe('Bucket creation', function() {
        it('should require a bucket type', function() {
            this.timeout(20000);
            return preq.put({
                uri: bucketURL,
                headers: { 'content-type': 'application/json' },
            })
            .catch(function(e) {
                deepEqual(e.status, 400);
                deepEqual(e.body.title, 'Invalid bucket spec.');
            });
        });
        it('should create a page bucket', function() {
            this.timeout(20000);
            return preq.put({
                uri: bucketURL,
                headers: { 'content-type': 'application/json' },
                body: { type: 'pagecontent' }
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            });
        });
    });
    describe('Item requests', function() {
        //it('should not accept a new html save without a revision', function() {
        //    return preq.put({
        //        uri: bucketURL + '/Foo/html',
        //        headers: { 'content-type': 'text/html' },
        //        body: 'Hello there'
        //    })
        //    .then(function(res) {
        //        deepEqual(res.status, 404);
        //    });
        //});
        it('should transparently create a new HTML revision', function() {
            this.timeout(20000);
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477',
                headers: { 'content-type': 'text/html' },
                body: 'Hello there'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should return HTML just created', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should transparently create data-parsoid, rev 2', function() {
            this.timeout(20000);
            return preq.get({
                uri: bucketURL + '/Foobar/html/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should return HTML just created, rev 2', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'text/html; charset=UTF-8');
            });
        });
        it('should return data-parsoid just created, rev 2', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/data-parsoid/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'application/json; profile=mediawiki.org/specs/data-parsoid/1.0');
            });
        });
        it('should accept a new html save with a revision', function() {
            return preq.put({
                uri: bucketURL + '/Foobar/html/76f22880-362c-11e4-9234-0123456789ab',
                headers: { 'content-type': 'text/html; charset=UTF-8' },
                body: 'Hello there'
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            })
            .catch(function(e) {
                console.dir(e);
                throw e;
            });
        });
        it('should return the HTML revision just created', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'text/html; charset=UTF-8');
                deepEqual(res.headers.etag, '76f22880-362c-11e4-9234-0123456789ab');
                deepEqual(res.body, 'Hello there');
            });
        });
    });
    describe('404 handling', function() {
        it('should return a proper 404 when trying to retrieve a non-existing domain', function() {
            return preq.get({
                uri: hostPort + '/v1/foobar.com'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when trying to list a non-existing domain', function() {
            return preq.get({
                uri: hostPort + '/v1/foobar.com/'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when accessing an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when trying to list an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket/'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when accessing an item in an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket/item'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
    });
});
<commit_msg>Split invalid bucket test into [missing, invalid] bucket test<commit_after>'use strict';

/*
 * Simple API tests
 */

/*
 * Could also check out the nock package to record / replay http interactions
 */

var restbase = require('../lib/server.js');
var preq = require('preq');
var assert = require('assert');
var hostPort = 'http://localhost:7231';
var baseURL = hostPort + '/v1/en.wikipedia.org';
var bucketURL = baseURL + '/test101';

function deepEqual (result, expected) {
    try {
        assert.deepEqual(result, expected);
    } catch (e) {
        console.log('Expected:\n' + JSON.stringify(expected,null,2));
        console.log('Result:\n' + JSON.stringify(result,null,2));
        throw e;
    }
}

describe('Simple API tests', function () {
    this.timeout(20000);
    before(function() {
        return restbase({
            logging: {
                name: 'restbase-tests',
                level: 'warn'
            }
        });
    });
    describe('Domain & bucket creation', function() {
        it('should create a domain', function() {
            return preq.put({
                uri: hostPort + '/v1/en.wikipedia.org',
                headers: { 'content-type': 'application/json' },
                body: {}
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            });
        });
    });
    describe('Bucket creation', function() {
        it('should require a bucket type', function() {
            this.timeout(20000);
            return preq.put({
                uri: bucketURL,
                headers: { 'content-type': 'application/json' },
                body: {}
            })
            .catch(function(e) {
                deepEqual(e.status, 400);
                deepEqual(e.body.title, 'Invalid bucket spec.');
            });
        });
        it('should require a valid bucket type', function() {
            this.timeout(20000);
            return preq.put({
                uri: bucketURL,
                headers: { 'content-type': 'application/json' },
                body: { type: 'wazzle' }
            })
            .catch(function(e) {
                deepEqual(e.status, 400);
                deepEqual(e.body.title, 'Invalid bucket spec.');
            });
        });
        it('should create a page bucket', function() {
            this.timeout(20000);
            return preq.put({
                uri: bucketURL,
                headers: { 'content-type': 'application/json' },
                body: { type: 'pagecontent' }
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            });
        });
    });
    describe('Item requests', function() {
        //it('should not accept a new html save without a revision', function() {
        //    return preq.put({
        //        uri: bucketURL + '/Foo/html',
        //        headers: { 'content-type': 'text/html' },
        //        body: 'Hello there'
        //    })
        //    .then(function(res) {
        //        deepEqual(res.status, 404);
        //    });
        //});
        it('should transparently create a new HTML revision', function() {
            this.timeout(20000);
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477',
                headers: { 'content-type': 'text/html' },
                body: 'Hello there'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should return HTML just created', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should transparently create data-parsoid, rev 2', function() {
            this.timeout(20000);
            return preq.get({
                uri: bucketURL + '/Foobar/html/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
            });
        });
        it('should return HTML just created, rev 2', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'text/html; charset=UTF-8');
            });
        });
        it('should return data-parsoid just created, rev 2', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/data-parsoid/624165266'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'application/json; profile=mediawiki.org/specs/data-parsoid/1.0');
            });
        });
        it('should accept a new html save with a revision', function() {
            return preq.put({
                uri: bucketURL + '/Foobar/html/76f22880-362c-11e4-9234-0123456789ab',
                headers: { 'content-type': 'text/html; charset=UTF-8' },
                body: 'Hello there'
            })
            .then(function(res) {
                deepEqual(res.status, 201);
            })
            .catch(function(e) {
                console.dir(e);
                throw e;
            });
        });
        it('should return the HTML revision just created', function() {
            return preq.get({
                uri: bucketURL + '/Foobar/html/624484477'
            })
            .then(function(res) {
                deepEqual(res.status, 200);
                deepEqual(res.headers['content-type'], 'text/html; charset=UTF-8');
                deepEqual(res.headers.etag, '76f22880-362c-11e4-9234-0123456789ab');
                deepEqual(res.body, 'Hello there');
            });
        });
    });
    describe('404 handling', function() {
        it('should return a proper 404 when trying to retrieve a non-existing domain', function() {
            return preq.get({
                uri: hostPort + '/v1/foobar.com'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when trying to list a non-existing domain', function() {
            return preq.get({
                uri: hostPort + '/v1/foobar.com/'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when accessing an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when trying to list an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket/'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
        it('should return a proper 404 when accessing an item in an unknown bucket', function() {
            return preq.get({
                uri: baseURL + '/some_nonexisting_bucket/item'
            })
            .catch(function(e) {
                deepEqual(e.status, 404);
                deepEqual(e.headers['content-type'], 'application/problem+json');
            });
        });
    });
});
<|endoftext|>


<commit_before>/*
  Parser
  Parses clean source. Generates clean AST.
*/
const opPrec = require('./operatorPrecedence')
const estemplate = require('./estemplate')
const updateAst = require('./astupdate')

/*  Utility functions  */
const mayBe = (value, func) => value === null ? null : func(...value)
const parenCheck = src => mayBe(parser.regex(/^(\))((.|\n)*)$/)(src),
                              (m, val, rest) => returnRest(val, src, src.str))

const returnRest = (val, input, rest, field) => {
  const output = JSON.parse(JSON.stringify(input))
  if (field) {
    let value = field.value
    switch (field.name) {
      case 'indent':
        output.indent = value
        output.column = value
        break
      case 'return':
        output.column = 1
        output.line += value
        break
      case 'column':
        output.column += value
        break
    }
  }
  output.str = rest
  return [val, output]
}

/*  Predefined regexes */
const spaceRegex = /^( +)((.|\n)*)$/
const returnRegex = /^(\n)((.|\n)*)$/
const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^((?:\d+(?:\.\d*)?|\.\d+)(?:[e][+-]?\d+)?)((.|\n)*)$/
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^('[^\\']*(?:\\.[^\\']*)*')((.|\n)*)$/
const binaryOperatorRegex = /^(\+|-|\/|\*|<<|<=|<|>>>|>>|>=|>|&&|&|\|\||\||\^|==)((.|\n)*)$/
const unaryOperatorRegex = /^(:type|-|!)((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src.str),
  (m, val, rest) => returnRest(val, src, rest, {'name': 'column', 'value': val.length})
)

parser.bind = (mv, mf) => input => mayBe(
  mv(input),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/
var spaceParser = parser.regex(spaceRegex)

var equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

var thinArrowParser = parser.regex(/^(\s*->\s+)((.|\n)*)$/)

var slashParser = parser.regex(/^(\s*\\)((.|\n)*)$/)

var letParser = parser.regex(/^(\s*let\s+)((.|\n)*)$/)

var inParser = parser.regex(/^(\s*in\s+)((.|\n)*)$/)

var dotParser = parser.regex(/^(\.)((.|\n)*)$/)

var ifParser = parser.regex(/^(if\s+)((.|\n)*)$/)
var thenParser = parser.regex(/^(then\s+)((.|\n)*)$/)
var elseParser = parser.regex(/^(else\s+)((.|\n)*)$/)

var indentParser = input => mayBe(
  spaceRegex.exec(input),
  (a, space, rest) => [space, rest]
)

var returnParser = input => mayBe(
  returnRegex.exec(input.str),
  (a, newLine, rest) => returnRest(newLine, input, rest, {'name': 'return', 'value': newLine.length})
)

var numberParser = input => mayBe(
  numRegex.exec(input.str),
  (a, num, rest) => returnRest(estemplate.literal(num, num), input, rest, {'name': 'column', 'value': num.length})
)

var identifierParser = input => mayBe(
  idRegex.exec(input.str),
  (a, name, rest) => returnRest(estemplate.identifier(name), input, rest, {'name': 'column', 'value': name.length})
)

var stringParser = input => mayBe(
  stringRegex.exec(input.str),
  (a, string, rest) => returnRest(estemplate.stringLiteral(string.replace(/(^')/, '').replace(/('$)/, '')),
                                    input, rest, {'name': 'column', 'value': string.length})
)

var booleanParser = input => mayBe(
    boolRegex.exec(input.str),
    (a, bool, rest) => returnRest(estemplate.boolLiteral(bool), input, rest, {'name': 'column', 'value': bool.length})
  )

var parenthesisParser = parser.bind(
  parser.bind(
  parser.bind(
  parser.bind(
    input => input.str.startsWith('(')
      ? returnRest('(', input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null,
    val => input => {
      const space = spaceParser(input)
      return space ? [space[0], space[1]] : [null, input]
    }),
    val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser, unaryExprParser)(input),
      (v, rest) => returnRest(v, input, rest.str)
    )),
    val => input => {
      var space = spaceParser(input)
      return space ? [val, space[1]] : [val, input]
    }),
    val => input => input.str.startsWith(')')
      ? returnRest(val, input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null
)

var expressionParser = parser.any(parenthesisParser, booleanParser, identifierParser, numberParser, stringParser)

var unaryOperatorParser = input => mayBe(
    unaryOperatorRegex.exec(input.str),
    (m, operator, rest) => returnRest(operator, input, rest, {'name': 'column', 'value': operator.length})
  )

var unaryExprParser = parser.bind(
  parser.bind(
    unaryOperatorParser,
    operator => rest => operator === ':type'
      ? spaceParser(rest) !== null ? ['typeof', spaceParser(rest)[1]] : null
      : [operator, rest]),
  operator => rest => mayBe(expressionParser(rest),
    (argument, rest) => [estemplate.unaryExpression(operator, argument), rest])
)

var binaryOperatorParser = parser.regex(binaryOperatorRegex)

var declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
        parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser, unaryExprParser)(input),
        (v, rest) => {
          return returnRest(estemplate.declaration(val, v), input, rest.str)
        }
      )
)

var paramParser = parser.all(spaceParser, parser.any(identifierParser, numberParser))

var paramsParser = (str, paramArray = []) => {
  var param = paramParser(str)
  return (param !== null) ? paramsParser(param[1], paramArray.concat(param[0][1]))
                          : [paramArray, str]
}

var fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => input => mayBe(
        paramsParser(input),
        (params, rest) => returnRest(estemplate.funcDeclaration(val, params), input, rest.str)
      )
    ),
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.declarations[0].init.body = v
        return returnRest(val, input, rest.str)
      }
    )
)

var lambdaParamParser = parser.all(identifierParser, spaceParser)

var lambdaParamsParser = (str, lambdaparamArray = []) => {
  var arg = lambdaParamParser(str)
  if (arg !== null) return lambdaParamsParser(arg[1], lambdaparamArray.concat(arg[0][0]))
  return [lambdaparamArray, str]
}

var lambdaParser = parser.bind(
  parser.bind(
    parser.bind(
      slashParser,
      val => input => mayBe(
        lambdaParamsParser(input),
        (params, rest) => returnRest(estemplate.lambda(params), input, rest.str)
       )
      ),
    val => input => mayBe(
      thinArrowParser(input),
      (v, rest) => returnRest(val, input, rest.str)
    )
   ),
   val => input => mayBe(
     expressionParser(input),
     (v, rest) => {
       val.expression.body = v
       return returnRest(val, input, rest.str)
     }
   )
 )

var letParamParser = parser.all(expressionParser, equalSignParser, expressionParser, spaceParser)

var letParamsParser = (str, letIdArray = [], letLiteralArray = []) => {
  var param = letParamParser(str)
  if (param !== null) return letParamsParser(param[1], letIdArray.concat(param[0][0]), letLiteralArray.concat(param[0][2]))
  return [letIdArray, letLiteralArray, str]
}

var letExpressionParser = parser.bind(
  parser.bind(
    parser.bind(
      letParser,
      val => input => mayBe(
          letParamsParser(input),
          (val1, val2, rest) => returnRest(estemplate.letExpression(val1, val2), input, rest.str)
       )
    ),
    val => input => mayBe(
        inParser(input),
        (v, rest) => returnRest(val, input, rest.str)
      )
  ),
  val => input => mayBe(
      parser.any(binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.expression.callee.body = v
        return returnRest(val, input, rest.str)
      }
    )
  )

var memberPropParser = parser.all(identifierParser, dotParser)

var memberPropsParser = (str, memberArray = []) => {
  var result = memberPropParser(str)
  if (result !== null) return memberPropsParser(result[1], memberArray.concat(result[0][0]))
  if (result === null && memberArray.length === 0) return null
  var property = identifierParser(str)
  if (property === null) return null
  let tree = memberExprParser(memberArray.concat(property[0]))
  return [tree, property[1]]
}

var memberExprParser = (str, tree = {}) => {
  let memExpr = estemplate.memberExpression
  tree = memExpr(str[0], str[1])
  for (let i = 2; i < str.length; i++) {
    tree = memExpr(tree.expression, str[i])
  }
  return tree
}

var argsParser = (input, argArray = []) => {
  var arg = parser.all(
              parser.any(unaryExprParser, expressionParser),
              parser.any(returnParser, parenCheck, spaceParser))(input)
  if (arg === null) return argArray.length === 0 ? null : [argArray, input]
  let [[argument, nextChar], rest] = arg
  if (nextChar === '\n' || nextChar === ')') {
    rest.str = nextChar === '\n' ? '\n' + rest.str : rest.str
    return [argArray.concat(argument), rest]
  }
  return argsParser(rest, argArray.concat(argument))
}

var fnCallParser = parser.bind(
  parser.all(parser.any(memberPropsParser, identifierParser), spaceParser),
  val => input => mayBe(
      argsParser(input),
      (args, rest) => val[1] === 'n' ? null : returnRest(estemplate.fnCall(val[0], args), input, rest.str)
    )
)

var ifExprParser = input => mayBe(
  parser.all(
    ifParser, expressionParser,
    spaceParser, thenParser, expressionParser,
    spaceParser, elseParser, expressionParser)(input),
    (val, rest) => {
      let [, condition, , , consequent, , , alternate] = val
      return returnRest(estemplate.ifthenelse(condition, consequent, alternate), input, rest.str)
    }
)
var multiLineParser = src => {
  if (src.length < 2) return src
  if (!indentParser(src[1])) return src
  const lead = src.splice(0, 1)
  const follow = src.splice(0, 1)
  src.unshift(lead + follow)
  return multiLineParser(src)
}

var statementParser = parser.any(returnParser, letExpressionParser, declParser, ifExprParser, fnDeclParser, fnCallParser, lambdaParser)

var programParser = (input, ast = estemplate.ast()) => {
  let result = statementParser(returnRest('', input, multiLineParser(input.str.split('\n')).join('\n'))[1])
  if (result === null) return updateAst(ast)
  if (typeof result[0] !== 'string') ast.body.push(result[0])
  return programParser(result[1], ast)
}

/* helper functions for binaryExprParser */
const precedence = operator => opPrec[operator].prec
const associativity = operator => opPrec[operator].assoc
const current = parser.any(binaryOperatorParser, spaceParser, fnCallParser, unaryExprParser, expressionParser)
const isBinaryOperator = token => opPrec[token] !== undefined
const isExpression = token => token.type !== undefined

var binaryExprParser = src => {
  let opStack = ['$']
  let operandStack = []
  let biExpr = estemplate.binaryExpression
  var descender = s => {
    let currentToken = current(s)
    let [token, rest] = currentToken === null ? [null, s] : currentToken
    let topOfOpStack = opStack[opStack.length - 1]

    if (token === null) {
      if (topOfOpStack !== '$') {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        return descender(rest)
      }
      return operandStack.length === 0 && opStack.length === 1 ? null : [operandStack.pop(), rest]
    }

    if (isExpression(token)) {
      operandStack.push(token)
      return descender(rest)
    }
    if (isBinaryOperator(token)) { // check operator stack
      if (precedence(token) > precedence(topOfOpStack)) {
        opStack.push(token)
        return descender(rest)
      }

      if (precedence(token) < precedence(topOfOpStack)) {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
      if (associativity(token) === associativity(topOfOpStack)) {
        if (associativity(token) === 'R') {
          opStack.push(token)
          return descender(rest)
        }
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
    }
    return descender(rest)
  }
  let [val, rest] = descender(src)
  return val === null ? null : [val, rest]
}
/*  Module Exports programParser  */
module.exports = programParser
<commit_msg>Add following regex<commit_after>/*
  Parser
  Parses clean source. Generates clean AST.
*/
const opPrec = require('./operatorPrecedence')
const estemplate = require('./estemplate')
const updateAst = require('./astupdate')

/*  Utility functions  */
const mayBe = (value, func) => value === null ? null : func(...value)
const parenCheck = src => mayBe(parser.regex(/^(\))((.|\n)*)$/)(src),
                              (m, val, rest) => returnRest(val, src, src.str))

const returnRest = (val, input, rest, field) => {
  const output = JSON.parse(JSON.stringify(input))
  if (field) {
    let value = field.value
    switch (field.name) {
      case 'indent':
        output.indent = value
        output.column = value
        break
      case 'return':
        output.column = 1
        output.line += value
        break
      case 'column':
        output.column += value
        break
    }
  }
  output.str = rest
  return [val, output]
}

/*  Predefined regexes */
const spaceRegex = /^( +)((.|\n)*)$/
const returnRegex = /^(\n)((.|\n)*)$/
const idRegex = /^([a-zA-Z]+[a-zA-Z0-9_]*)((.|\n)*)$/
const numRegex = /^((?:\d+(?:\.\d*)?|\.\d+)(?:[e][+-]?\d+)?)((.|\n)*)$/
const boolRegex = /^(true|false)((.|\n)*)$/i
const stringRegex = /^('[^\\']*(?:\\.[^\\']*)*')((.|\n)*)$/
const binaryOperatorRegex = /^(\+|-|\/|\*|<<|<=|<|>>>|>>|>=|>|&&|&|\|\||\||\^|==)((.|\n)*)$/
const unaryOperatorRegex = /^(:type|-|!)((.|\n)*)$/
const openCurlyRegex = /^({)((.|\n)*)$/
const closeCurlyRegex = /^(})((.|\n)*)$/
const commaRegex = /^(,)((.|\n)*)$/
const colonRegex = /^(:)((.|\n)*)$/

/*
  Parser factory. Used to create parsers
  parser.regex - creates parser for given regex
  parser.bind - applies monadic bind on parsers
  parser.all - All parsers supplied MUST match
  parser.any - ANY ONE of the parsers need match
*/

const parser = {}

parser.regex = regex => src => mayBe(
  regex.exec(src.str),
  (m, val, rest) => returnRest(val, src, rest, {'name': 'column', 'value': val.length})
)

parser.bind = (mv, mf) => input => mayBe(
  mv(input),
  (val, rest) => mf(val)(rest)
)

parser.all = (...parsers) => src => {
  var rest = src
  var values = []
  for (let parser of parsers) {
    let result = parser(rest)
    if (result === null) return null
    values.push(result[0])
    rest = result[1]
  }
  return [values, rest]
}

parser.any = (...parsers) => src => {
  for (let parser of parsers) {
    let result = parser(src)
    if (result !== null) return result
  }
  return null
}

/*
  All required parsers are created below
*/
var spaceParser = parser.regex(spaceRegex)

var equalSignParser = parser.regex(/^(\s+=\s+)((.|\n)*)$/)

var thinArrowParser = parser.regex(/^(\s*->\s+)((.|\n)*)$/)

var slashParser = parser.regex(/^(\s*\\)((.|\n)*)$/)

var letParser = parser.regex(/^(\s*let\s+)((.|\n)*)$/)

var inParser = parser.regex(/^(\s*in\s+)((.|\n)*)$/)

var dotParser = parser.regex(/^(\.)((.|\n)*)$/)

var ifParser = parser.regex(/^(if\s+)((.|\n)*)$/)
var thenParser = parser.regex(/^(then\s+)((.|\n)*)$/)
var elseParser = parser.regex(/^(else\s+)((.|\n)*)$/)

var indentParser = input => mayBe(
  spaceRegex.exec(input),
  (a, space, rest) => [space, rest]
)

var returnParser = input => mayBe(
  returnRegex.exec(input.str),
  (a, newLine, rest) => returnRest(newLine, input, rest, {'name': 'return', 'value': newLine.length})
)

var numberParser = input => mayBe(
  numRegex.exec(input.str),
  (a, num, rest) => returnRest(estemplate.literal(num, num), input, rest, {'name': 'column', 'value': num.length})
)

var identifierParser = input => mayBe(
  idRegex.exec(input.str),
  (a, name, rest) => returnRest(estemplate.identifier(name), input, rest, {'name': 'column', 'value': name.length})
)

var stringParser = input => mayBe(
  stringRegex.exec(input.str),
  (a, string, rest) => returnRest(estemplate.stringLiteral(string.replace(/(^')/, '').replace(/('$)/, '')),
                                    input, rest, {'name': 'column', 'value': string.length})
)

var booleanParser = input => mayBe(
    boolRegex.exec(input.str),
    (a, bool, rest) => returnRest(estemplate.boolLiteral(bool), input, rest, {'name': 'column', 'value': bool.length})
  )

var parenthesisParser = parser.bind(
  parser.bind(
  parser.bind(
  parser.bind(
    input => input.str.startsWith('(')
      ? returnRest('(', input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null,
    val => input => {
      const space = spaceParser(input)
      return space ? [space[0], space[1]] : [null, input]
    }),
    val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser, unaryExprParser)(input),
      (v, rest) => returnRest(v, input, rest.str)
    )),
    val => input => {
      var space = spaceParser(input)
      return space ? [val, space[1]] : [val, input]
    }),
    val => input => input.str.startsWith(')')
      ? returnRest(val, input, input.str.substr(1), {'name': 'column', 'value': 1})
      : null
)

var expressionParser = parser.any(parenthesisParser, booleanParser, identifierParser, numberParser, stringParser)

var unaryOperatorParser = input => mayBe(
    unaryOperatorRegex.exec(input.str),
    (m, operator, rest) => returnRest(operator, input, rest, {'name': 'column', 'value': operator.length})
  )

var unaryExprParser = parser.bind(
  parser.bind(
    unaryOperatorParser,
    operator => rest => operator === ':type'
      ? spaceParser(rest) !== null ? ['typeof', spaceParser(rest)[1]] : null
      : [operator, rest]),
  operator => rest => mayBe(expressionParser(rest),
    (argument, rest) => [estemplate.unaryExpression(operator, argument), rest])
)

var binaryOperatorParser = parser.regex(binaryOperatorRegex)

var declParser = parser.bind(
  parser.bind(
    identifierParser,
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
        parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser, unaryExprParser)(input),
        (v, rest) => {
          return returnRest(estemplate.declaration(val, v), input, rest.str)
        }
      )
)

var paramParser = parser.all(spaceParser, parser.any(identifierParser, numberParser))

var paramsParser = (str, paramArray = []) => {
  var param = paramParser(str)
  return (param !== null) ? paramsParser(param[1], paramArray.concat(param[0][1]))
                          : [paramArray, str]
}

var fnDeclParser = parser.bind(
  parser.bind(
    parser.bind(
      identifierParser,
      val => input => mayBe(
        paramsParser(input),
        (params, rest) => returnRest(estemplate.funcDeclaration(val, params), input, rest.str)
      )
    ),
    val => input => mayBe(
        equalSignParser(input),
        (v, rest) => [val, rest]
      )
  ),
  val => input => mayBe(
      parser.any(ifExprParser, binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.declarations[0].init.body = v
        return returnRest(val, input, rest.str)
      }
    )
)

var lambdaParamParser = parser.all(identifierParser, spaceParser)

var lambdaParamsParser = (str, lambdaparamArray = []) => {
  var arg = lambdaParamParser(str)
  if (arg !== null) return lambdaParamsParser(arg[1], lambdaparamArray.concat(arg[0][0]))
  return [lambdaparamArray, str]
}

var lambdaParser = parser.bind(
  parser.bind(
    parser.bind(
      slashParser,
      val => input => mayBe(
        lambdaParamsParser(input),
        (params, rest) => returnRest(estemplate.lambda(params), input, rest.str)
       )
      ),
    val => input => mayBe(
      thinArrowParser(input),
      (v, rest) => returnRest(val, input, rest.str)
    )
   ),
   val => input => mayBe(
     expressionParser(input),
     (v, rest) => {
       val.expression.body = v
       return returnRest(val, input, rest.str)
     }
   )
 )

var letParamParser = parser.all(expressionParser, equalSignParser, expressionParser, spaceParser)

var letParamsParser = (str, letIdArray = [], letLiteralArray = []) => {
  var param = letParamParser(str)
  if (param !== null) return letParamsParser(param[1], letIdArray.concat(param[0][0]), letLiteralArray.concat(param[0][2]))
  return [letIdArray, letLiteralArray, str]
}

var letExpressionParser = parser.bind(
  parser.bind(
    parser.bind(
      letParser,
      val => input => mayBe(
          letParamsParser(input),
          (val1, val2, rest) => returnRest(estemplate.letExpression(val1, val2), input, rest.str)
       )
    ),
    val => input => mayBe(
        inParser(input),
        (v, rest) => returnRest(val, input, rest.str)
      )
  ),
  val => input => mayBe(
      parser.any(binaryExprParser, fnCallParser, expressionParser)(input),
      (v, rest) => {
        val.expression.callee.body = v
        return returnRest(val, input, rest.str)
      }
    )
  )

var memberPropParser = parser.all(identifierParser, dotParser)

var memberPropsParser = (str, memberArray = []) => {
  var result = memberPropParser(str)
  if (result !== null) return memberPropsParser(result[1], memberArray.concat(result[0][0]))
  if (result === null && memberArray.length === 0) return null
  var property = identifierParser(str)
  if (property === null) return null
  let tree = memberExprParser(memberArray.concat(property[0]))
  return [tree, property[1]]
}

var memberExprParser = (str, tree = {}) => {
  let memExpr = estemplate.memberExpression
  tree = memExpr(str[0], str[1])
  for (let i = 2; i < str.length; i++) {
    tree = memExpr(tree.expression, str[i])
  }
  return tree
}

var argsParser = (input, argArray = []) => {
  var arg = parser.all(
              parser.any(unaryExprParser, expressionParser),
              parser.any(returnParser, parenCheck, spaceParser))(input)
  if (arg === null) return argArray.length === 0 ? null : [argArray, input]
  let [[argument, nextChar], rest] = arg
  if (nextChar === '\n' || nextChar === ')') {
    rest.str = nextChar === '\n' ? '\n' + rest.str : rest.str
    return [argArray.concat(argument), rest]
  }
  return argsParser(rest, argArray.concat(argument))
}

var fnCallParser = parser.bind(
  parser.all(parser.any(memberPropsParser, identifierParser), spaceParser),
  val => input => mayBe(
      argsParser(input),
      (args, rest) => val[1] === 'n' ? null : returnRest(estemplate.fnCall(val[0], args), input, rest.str)
    )
)

var ifExprParser = input => mayBe(
  parser.all(
    ifParser, expressionParser,
    spaceParser, thenParser, expressionParser,
    spaceParser, elseParser, expressionParser)(input),
    (val, rest) => {
      let [, condition, , , consequent, , , alternate] = val
      return returnRest(estemplate.ifthenelse(condition, consequent, alternate), input, rest.str)
    }
)
var multiLineParser = src => {
  if (src.length < 2) return src
  if (!indentParser(src[1])) return src
  const lead = src.splice(0, 1)
  const follow = src.splice(0, 1)
  src.unshift(lead + follow)
  return multiLineParser(src)
}

var statementParser = parser.any(returnParser, letExpressionParser, declParser, ifExprParser, fnDeclParser, fnCallParser, lambdaParser)

var programParser = (input, ast = estemplate.ast()) => {
  let result = statementParser(returnRest('', input, multiLineParser(input.str.split('\n')).join('\n'))[1])
  if (result === null) return updateAst(ast)
  if (typeof result[0] !== 'string') ast.body.push(result[0])
  return programParser(result[1], ast)
}

/* helper functions for binaryExprParser */
const precedence = operator => opPrec[operator].prec
const associativity = operator => opPrec[operator].assoc
const current = parser.any(binaryOperatorParser, spaceParser, fnCallParser, unaryExprParser, expressionParser)
const isBinaryOperator = token => opPrec[token] !== undefined
const isExpression = token => token.type !== undefined

var binaryExprParser = src => {
  let opStack = ['$']
  let operandStack = []
  let biExpr = estemplate.binaryExpression
  var descender = s => {
    let currentToken = current(s)
    let [token, rest] = currentToken === null ? [null, s] : currentToken
    let topOfOpStack = opStack[opStack.length - 1]

    if (token === null) {
      if (topOfOpStack !== '$') {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        return descender(rest)
      }
      return operandStack.length === 0 && opStack.length === 1 ? null : [operandStack.pop(), rest]
    }

    if (isExpression(token)) {
      operandStack.push(token)
      return descender(rest)
    }
    if (isBinaryOperator(token)) { // check operator stack
      if (precedence(token) > precedence(topOfOpStack)) {
        opStack.push(token)
        return descender(rest)
      }

      if (precedence(token) < precedence(topOfOpStack)) {
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
      if (associativity(token) === associativity(topOfOpStack)) {
        if (associativity(token) === 'R') {
          opStack.push(token)
          return descender(rest)
        }
        let [rightOperand, operator, leftOperand] = [operandStack.pop(), opStack.pop(), operandStack.pop()]
        operandStack.push(biExpr(leftOperand, operator, rightOperand))
        opStack.push(token)
        return descender(rest)
      }
    }
    return descender(rest)
  }
  let [val, rest] = descender(src)
  return val === null ? null : [val, rest]
}
/*  Module Exports programParser  */
module.exports = programParser
<|endoftext|>


<commit_before>;(function() {

	var
		stream = require('stream')
		, util = require('util')
		, scheck = require('./stream-check')
	;

	/**
	* HID packet object
	*/
	var hidPacket = function hidPacket() {

		return {

			modKeys : []
			, keyCodes : []
			, charCodes : []
			, errorStatus : false // keyboard rollover
		};
	};

	var parseData = function parseData(data) {

		if(!scheck.bind(this)) { return null; }

		var parsed = new hidPacket();

		parseModifiers(parsed, data.shift()); // parse modifiers (bit field)

		data.shift(); // the next element is always 0 (I think...)
		parseKeyCodes(parsed, data); // parse keycodes from the rest
		parseCharCodes(parsed, data); // parse charcodes from keycodes
		parseErrorState(parsed, data); // detect rollover error

		return parsed;
	};

	/**
	* Convert modifier key bits into
	* array of human-readable identifiers
	*/
	var parseModifiers = function parseModifiers(arr, bits) {

		var push = function(mod) { arr.modKeys.push(mod); };

		if(bits & 1) { push('ctrl'); }
		if(bits & 2) { push('shift'); }
		if(bits & 4) { push('alt'); }
		if(bits & 8) { push('meta'); }
		if(bits & 16) { push('r-ctrl'); }
		if(bits & 32) { push('r-shift'); }
		if(bits & 64) { push('r-alt'); }
		if(bits & 128) { push('r-meta'); }

	};

	/**
	* Slice HID keycodes into separate array
	*/
	var parseKeyCodes = function parseKeyCodes(arr, keys) {

		if(typeof keys != "object") { return false; }

		var push = function(key) {

			if(key > 3) { arr.keyCodes.push(key); }
		};

		keys.forEach(push);

		return true;
	};

	var parseCharCodes = function parseCharCodes(arr, keys) {

		if(typeof keys != "object") { return false; }

		var
			push = function(dat) { arr.charCodes.push(dat); }
			, range = function(lower, upper) {

				return this > lower && this < upper;
			}
			, character = function(num) { return String.fromCharCode(num); }
			, parse = function(key) {

				// range check key shorthand
				var r = range.bind(key);
				if(r(3, 30)) { // alpha

					push(character(key + 93));
				}
				else if(r(29, 40)) { // numeric

					// tacky hack to fix 0 because it's not in order
					push(character(key + (key == 39 ? 9 : 19)));
				}
				else if(key == 40) { // enter

					push("\n");
				}
				else if(key == 57) { // capslock

				}
				else if(r(40, 45)) { // controls

				}
				else if(r(44, 50)) { // symbols

				}
				else if(r(49, 57)) { // punctuation

				}
				else if(r(57, 70)) { // F keys

				}
				else if(r(69, 83)) { // control

				}
				else if(r(82, 104)) { // keypad

				}
			}
		;

		keys.forEach(parse);
		return true;

	};

	/**
	* Detect keyboard rollover error
	* This occurs when the user has pressed too many keys for the
	* particular device to handle
	*/
	var parseErrorState = function parseErrorState(arr, state) {

		var
			states = 0
			, check = function(s) { if(s == 1) { ++states; } }
		;

		state.forEach(check);
		if(states == 6) {

			arr.errorStatus = true;
			arr.modKeys = [];
			arr.keyCodes = [];
			arr.charCodes = [];
		}
	};

	var parser = {

		raw : function raw(err, data) {

			if(err) { return err; }
			var parsed = parseData.call(this, data);

			this.emit("data", parsed);
			this.device.read(this.parser);
		}
		, newline : function newline(data) {

			if(err) { return err; }
			var parsed = parseData.call(this, data);

			this.emit("data", parsed);
			this.device.read(this.parser);
		}
	};

	module.exports = parser;
})();
<commit_msg>Added newline parser. Newline parser for things like barcode scanners and magstripe readers!<commit_after>;(function() {

	var
		stream = require('stream')
		, util = require('util')
		, scheck = require('./stream-check')
		, input = ''
	;

	/**
	* HID packet object
	*/
	var hidPacket = function hidPacket() {

		return {

			modKeys : []
			, keyCodes : []
			, charCodes : []
			, errorStatus : false // keyboard rollover
		};
	};

	var parseData = function parseData(data) {

		if(!scheck.bind(this)) { return null; }

		var parsed = new hidPacket();

		parseModifiers(parsed, data.shift()); // parse modifiers (bit field)

		data.shift(); // the next element is always 0 (I think...)
		parseKeyCodes(parsed, data); // parse keycodes from the rest
		parseCharCodes(parsed, data); // parse charcodes from keycodes
		parseErrorState(parsed, data); // detect rollover error

		return parsed;
	};

	/**
	* Convert modifier key bits into
	* array of human-readable identifiers
	*/
	var parseModifiers = function parseModifiers(arr, bits) {

		var push = function(mod) { arr.modKeys.push(mod); };

		if(bits & 1) { push('ctrl'); }
		if(bits & 2) { push('shift'); }
		if(bits & 4) { push('alt'); }
		if(bits & 8) { push('meta'); }
		if(bits & 16) { push('r-ctrl'); }
		if(bits & 32) { push('r-shift'); }
		if(bits & 64) { push('r-alt'); }
		if(bits & 128) { push('r-meta'); }

	};

	/**
	* Slice HID keycodes into separate array
	*/
	var parseKeyCodes = function parseKeyCodes(arr, keys) {

		if(typeof keys != "object") { return false; }

		var push = function(key) {

			if(key > 3) { arr.keyCodes.push(key); }
		};

		keys.forEach(push);

		return true;
	};

	var parseCharCodes = function parseCharCodes(arr, keys) {

		if(typeof keys != "object") { return false; }

		var
			push = function(dat) { arr.charCodes.push(dat); }
			, range = function(lower, upper) {

				return this > lower && this < upper;
			}
			, character = function(num) { return String.fromCharCode(num); }
			, parse = function(key) {

				// range check key shorthand
				var r = range.bind(key);
				if(r(3, 30)) { // alpha

					push(character(key + 93));
				}
				else if(r(29, 40)) { // numeric

					// tacky hack to fix 0 because it's not in order
					push(character(key + (key == 39 ? 9 : 19)));
				}
				else if(key == 40) { // enter

					push("\n");
				}
				else if(key == 57) { // capslock

				}
				else if(r(40, 45)) { // controls

				}
				else if(r(44, 50)) { // symbols

				}
				else if(r(49, 57)) { // punctuation

				}
				else if(r(57, 70)) { // F keys

				}
				else if(r(69, 83)) { // control

				}
				else if(r(82, 104)) { // keypad

				}
			}
		;

		keys.forEach(parse);
		return true;

	};

	/**
	* Detect keyboard rollover error
	* This occurs when the user has pressed too many keys for the
	* particular device to handle
	*/
	var parseErrorState = function parseErrorState(arr, state) {

		var
			states = 0
			, check = function(s) { if(s == 1) { ++states; } }
		;

		state.forEach(check);
		if(states == 6) {

			arr.errorStatus = true;
			arr.modKeys = [];
			arr.keyCodes = [];
			arr.charCodes = [];
		}
	};

	var parser = {

		raw : function raw(err, data) {

			if(err) { return err; }
			var parsed = parseData.call(this, data);

			this.emit("data", parsed);
			this.device.read(this.parser);
		}
		, newline : function newline(err, data) {

			if(err) { return err; }
			var
				enter = function(key) { return key == "\n"; }
				, noop = function() {

					if(this.modKeys.length || this.keyCodes.length
					|| this.charCodes.length) {

						return false;
					}

					return true;
				}
				, parsed = parseData.call(this, data)
				, emit = function() {

					this.emit("data", input);
					input = '';
				}
			;

			/**
			* no keys pressed
			*/
			if(noop.call(parsed)) {

				// we could do something with this!
			}

			/**
			* at least one key was pressed
			* ignore all but the first
			* (scanners only emit one at a time (I think))
			*/
			else {

				var newChar = parsed.charCodes[0];
				if(enter(newChar)) {

					emit.call(this);
				}
				else {

					input += newChar;
				}
			}
			this.device.read(this.parser);
		}
	};

	module.exports = parser;
})();
<|endoftext|>


<commit_before>// Copyright 2012 The Obvious Corporation.

/*
 * Modules used
 */

"use strict";

var assert = require("assert");
var events = require("events");

var Slicer = require("../").Slicer;

var CallbackCollector = require("./cbcoll").CallbackCollector;
var emit = require("./emit").emit;

/*
 * Tests
 */

/**
 * Make sure the constructor doesn't fail off the bat.
 */
function constructor() {
  new Slicer(new events.EventEmitter());
  new Slicer(new events.EventEmitter(), "hex");
}

/**
 * Test expected constructor failures.
 */
function constructorFailures() {
  function f1() {
    new Slicer();
  }
  assert.throws(f1, /Missing source/);

  function f2() {
    new Slicer("non-emitter");
  }
  assert.throws(f2, /Source not an EventEmitter/);

  function f3() {
    new Slicer(new events.EventEmitter(), "bad-encoding");
  }
  assert.throws(f3, /Invalid encoding name/);
}

/**
 * Test the transition from readable to un-readable. This also checks
 * to make sure the upstream source is un-listened immediately upon
 * receipt of an end-type event.
 */
function readableTransition() {
  var theData = new Buffer("Scones are delicious.");

  tryWith(false, "end");
  tryWith(false, "close");
  tryWith(false, "close", new Error("oy"));
  tryWith(false, "error", new Error("oof"));
  tryWith(true, "end");
  tryWith(true, "close");
  tryWith(true, "close", new Error("oy"));
  tryWith(true, "error", new Error("oof"));

  function tryWith(doData, endEvent, endArg) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    assert.ok(slicer.readable);

    if (doData) {
      source.emit("data", theData);
      assert.ok(slicer.readable);
    }

    emit(source, endEvent, endArg);
    assert.equal(source.listeners("close").length, 0);
    assert.equal(source.listeners("data").length, 0);
    assert.equal(source.listeners("end").length, 0);
    assert.equal(source.listeners("error").length, 0);

    if (doData) {
      assert.ok(slicer.readable);
      slicer.readAll(coll.callback);
      assert.equal(coll.callbacks.length, 1);
      coll.assertCallback(0, undefined, theData.length, theData, 0);
    }

    assert.ok(!slicer.readable);
  }
}

/**
 * Tests that `destroy()` properly forces pending reads to get called
 * back and un-listens to the upstream source.
 */
function destroy() {
  var theData = new Buffer("muffins");

  for (var i = 1; i <= 10; i++) {
    tryWith(i);
  }

  function tryWith(count) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    for (var i = 0; i < count; i++) {
      slicer.read(10, coll.callback);
    }

    assert.equal(coll.callbacks.length, 0);
    source.emit("data", theData);
    assert.equal(coll.callbacks.length, 0);

    slicer.destroy();
    assert.ok(!slicer.readable);
    assert.equal(source.listeners("close").length, 0);
    assert.equal(source.listeners("data").length, 0);
    assert.equal(source.listeners("end").length, 0);
    assert.equal(source.listeners("error").length, 0);

    coll.assertCallback(0, undefined, theData.length, theData, 0);
    for (var i = 1; i < count; i++) {
      coll.assertCallback(i, undefined, 0, new Buffer(0), 0);
    }
  }
}

/**
 * Test that `getError()` and `gotError()` work as expected.
 */
function getErrorGotError() {
  tryWith(false, false, "end");
  tryWith(false, false, "close");
  tryWith(false, false, "close", false);
  tryWith(false, true, "end");
  tryWith(false, true, "close");
  tryWith(false, true, "close", false);

  tryWith(true, false, "error");
  tryWith(true, false, "error", false);
  tryWith(true, false, "error", new Error("yikes"));
  tryWith(true, false, "close", true);
  tryWith(true, false, "close", new Error("stuff"));

  tryWith(true, true, "error");
  tryWith(true, true, "error", new Error("spaztastic"));
  tryWith(true, true, "close", true);

  function tryWith(expectError, doData, endEvent, endArg) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);

    assert.ok(!slicer.gotError());
    assert.equal(slicer.getError(), undefined);

    if (doData) {
      source.emit("data", "I'm a sucker for a good biscuit.");
      assert.ok(!slicer.gotError());
      assert.equal(slicer.getError(), undefined);
    }

    emit(source, endEvent, endArg);

    if (expectError) {
      assert.ok(slicer.gotError());
      assert.equal(slicer.getError(), endArg);
    } else {
      assert.ok(!slicer.gotError());
      assert.equal(slicer.getError(), undefined);
    }
  }
}

/**
 * Tests the no-data case of `readAll()`.
 */
function readAllNoData() {
  var theData = new Buffer("scone");

  tryWith(false, 1);
  tryWith(false, 2);
  tryWith(false, 10);
  tryWith(true, 1);
  tryWith(true, 2);
  tryWith(true, 10);

  function tryWith(doData, count) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    if (doData) {
      slicer.read(theData.length, coll.callback);
      assert.equal(coll.callbacks.length, 0);
    }

    for (var i = 0; i < count; i++) {
      slicer.readAll(coll.callback);
    }

    if (doData) {
      assert.equal(coll.callbacks.length, 0);
      source.emit("data", theData);
      coll.assertCallback(0, undefined, theData.length, theData, 0);
      coll.callbacks.shift();
    }

    assert.equal(coll.callbacks.length, count);

    for (var i = 0; i < count; i++) {
      coll.assertCallback(i, undefined, 0, new Buffer(0), 0);
    }
  }
}

/**
 * Tests the immediately-available data case of `readAll()`.
 */
function readAllImmediateData() {
  var theData = new Buffer("Who wants a cupcake?");

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  source.emit("data", theData);
  slicer.readAll(coll.callback);
  slicer.readAll(coll.callback);

  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, theData.length, theData, 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
}

/**
 * Tests the case of a `readAll()` that grabs the end chunk of
 * some data behind a fixed-length `read()`.
 */
function readAllAfterReadWithLength() {
  var theData = new Buffer("Try the shortbread.");
  var data0 = theData.slice(0, 10);
  var data1 = theData.slice(10);

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  slicer.read(10, coll.callback);
  slicer.readAll(coll.callback);

  assert.equal(coll.callbacks.length, 0);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);

  coll.assertCallback(0, undefined, data0.length, data0, 0);
  coll.assertCallback(1, undefined, data1.length, data1, 0);
}

/**
 * Tests that `read()` with length 0 always succeeds when it's
 * first in the queue.
 */
function readWithZeroLength() {
  var theData = new Buffer("Banana nut muffins: total travesty");

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  // Test with an empty read queue and nothing pending.
  slicer.read(0, coll.callback);
  assert.equal(coll.callbacks.length, 1);
  coll.assertCallback(0, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with an empty read queue and some data pending.
  source.emit("data", theData);
  slicer.read(0, coll.callback);
  assert.equal(coll.callbacks.length, 1);
  coll.assertCallback(0, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with a non-empty read queue and an initial read that
  // doesn't end up consuming all the data.
  slicer = new Slicer(source);
  slicer.read(10, coll.callback);
  slicer.read(0, coll.callback);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, 10, theData.slice(0, 10), 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with a non-empty read queue and an initial read that
  // *does* end up consuming all the data.
  slicer = new Slicer(source);
  slicer.read(theData.length, coll.callback);
  slicer.read(0, coll.callback);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, theData.length, theData, 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
}

function test() {
  constructor();
  constructorFailures();
  readableTransition();
  destroy();
  getErrorGotError();
  readAllNoData();
  readAllImmediateData();
  readAllAfterReadWithLength();
  readWithZeroLength();
}

module.exports = {
  test: test
};
<commit_msg>Add another `read()` test.<commit_after>// Copyright 2012 The Obvious Corporation.

/*
 * Modules used
 */

"use strict";

var assert = require("assert");
var events = require("events");

var Slicer = require("../").Slicer;

var CallbackCollector = require("./cbcoll").CallbackCollector;
var emit = require("./emit").emit;

/*
 * Tests
 */

/**
 * Make sure the constructor doesn't fail off the bat.
 */
function constructor() {
  new Slicer(new events.EventEmitter());
  new Slicer(new events.EventEmitter(), "hex");
}

/**
 * Test expected constructor failures.
 */
function constructorFailures() {
  function f1() {
    new Slicer();
  }
  assert.throws(f1, /Missing source/);

  function f2() {
    new Slicer("non-emitter");
  }
  assert.throws(f2, /Source not an EventEmitter/);

  function f3() {
    new Slicer(new events.EventEmitter(), "bad-encoding");
  }
  assert.throws(f3, /Invalid encoding name/);
}

/**
 * Test the transition from readable to un-readable. This also checks
 * to make sure the upstream source is un-listened immediately upon
 * receipt of an end-type event.
 */
function readableTransition() {
  var theData = new Buffer("Scones are delicious.");

  tryWith(false, "end");
  tryWith(false, "close");
  tryWith(false, "close", new Error("oy"));
  tryWith(false, "error", new Error("oof"));
  tryWith(true, "end");
  tryWith(true, "close");
  tryWith(true, "close", new Error("oy"));
  tryWith(true, "error", new Error("oof"));

  function tryWith(doData, endEvent, endArg) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    assert.ok(slicer.readable);

    if (doData) {
      source.emit("data", theData);
      assert.ok(slicer.readable);
    }

    emit(source, endEvent, endArg);
    assert.equal(source.listeners("close").length, 0);
    assert.equal(source.listeners("data").length, 0);
    assert.equal(source.listeners("end").length, 0);
    assert.equal(source.listeners("error").length, 0);

    if (doData) {
      assert.ok(slicer.readable);
      slicer.readAll(coll.callback);
      assert.equal(coll.callbacks.length, 1);
      coll.assertCallback(0, undefined, theData.length, theData, 0);
    }

    assert.ok(!slicer.readable);
  }
}

/**
 * Tests that `destroy()` properly forces pending reads to get called
 * back and un-listens to the upstream source.
 */
function destroy() {
  var theData = new Buffer("muffins");

  for (var i = 1; i <= 10; i++) {
    tryWith(i);
  }

  function tryWith(count) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    for (var i = 0; i < count; i++) {
      slicer.read(10, coll.callback);
    }

    assert.equal(coll.callbacks.length, 0);
    source.emit("data", theData);
    assert.equal(coll.callbacks.length, 0);

    slicer.destroy();
    assert.ok(!slicer.readable);
    assert.equal(source.listeners("close").length, 0);
    assert.equal(source.listeners("data").length, 0);
    assert.equal(source.listeners("end").length, 0);
    assert.equal(source.listeners("error").length, 0);

    coll.assertCallback(0, undefined, theData.length, theData, 0);
    for (var i = 1; i < count; i++) {
      coll.assertCallback(i, undefined, 0, new Buffer(0), 0);
    }
  }
}

/**
 * Test that `getError()` and `gotError()` work as expected.
 */
function getErrorGotError() {
  tryWith(false, false, "end");
  tryWith(false, false, "close");
  tryWith(false, false, "close", false);
  tryWith(false, true, "end");
  tryWith(false, true, "close");
  tryWith(false, true, "close", false);

  tryWith(true, false, "error");
  tryWith(true, false, "error", false);
  tryWith(true, false, "error", new Error("yikes"));
  tryWith(true, false, "close", true);
  tryWith(true, false, "close", new Error("stuff"));

  tryWith(true, true, "error");
  tryWith(true, true, "error", new Error("spaztastic"));
  tryWith(true, true, "close", true);

  function tryWith(expectError, doData, endEvent, endArg) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);

    assert.ok(!slicer.gotError());
    assert.equal(slicer.getError(), undefined);

    if (doData) {
      source.emit("data", "I'm a sucker for a good biscuit.");
      assert.ok(!slicer.gotError());
      assert.equal(slicer.getError(), undefined);
    }

    emit(source, endEvent, endArg);

    if (expectError) {
      assert.ok(slicer.gotError());
      assert.equal(slicer.getError(), endArg);
    } else {
      assert.ok(!slicer.gotError());
      assert.equal(slicer.getError(), undefined);
    }
  }
}

/**
 * Tests the no-data case of `readAll()`.
 */
function readAllNoData() {
  var theData = new Buffer("scone");

  tryWith(false, 1);
  tryWith(false, 2);
  tryWith(false, 10);
  tryWith(true, 1);
  tryWith(true, 2);
  tryWith(true, 10);

  function tryWith(doData, count) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    if (doData) {
      slicer.read(theData.length, coll.callback);
      assert.equal(coll.callbacks.length, 0);
    }

    for (var i = 0; i < count; i++) {
      slicer.readAll(coll.callback);
    }

    if (doData) {
      assert.equal(coll.callbacks.length, 0);
      source.emit("data", theData);
      coll.assertCallback(0, undefined, theData.length, theData, 0);
      coll.callbacks.shift();
    }

    assert.equal(coll.callbacks.length, count);

    for (var i = 0; i < count; i++) {
      coll.assertCallback(i, undefined, 0, new Buffer(0), 0);
    }
  }
}

/**
 * Tests the immediately-available data case of `readAll()`.
 */
function readAllImmediateData() {
  var theData = new Buffer("Who wants a cupcake?");

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  source.emit("data", theData);
  slicer.readAll(coll.callback);
  slicer.readAll(coll.callback);

  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, theData.length, theData, 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
}

/**
 * Tests the case of a `readAll()` that grabs the end chunk of
 * some data behind a fixed-length `read()`.
 */
function readAllAfterReadWithLength() {
  var theData = new Buffer("Try the shortbread.");
  var data0 = theData.slice(0, 10);
  var data1 = theData.slice(10);

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  slicer.read(10, coll.callback);
  slicer.readAll(coll.callback);

  assert.equal(coll.callbacks.length, 0);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);

  coll.assertCallback(0, undefined, data0.length, data0, 0);
  coll.assertCallback(1, undefined, data1.length, data1, 0);
}

/**
 * Tests that `read()` with length 0 always succeeds when it's
 * first in the queue.
 */
function readWithZeroLength() {
  var theData = new Buffer("Banana nut muffins: total travesty");

  var source = new events.EventEmitter();
  var slicer = new Slicer(source);
  var coll = new CallbackCollector();

  // Test with an empty read queue and nothing pending.
  slicer.read(0, coll.callback);
  assert.equal(coll.callbacks.length, 1);
  coll.assertCallback(0, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with an empty read queue and some data pending.
  source.emit("data", theData);
  slicer.read(0, coll.callback);
  assert.equal(coll.callbacks.length, 1);
  coll.assertCallback(0, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with a non-empty read queue and an initial read that
  // doesn't end up consuming all the data.
  slicer = new Slicer(source);
  slicer.read(10, coll.callback);
  slicer.read(0, coll.callback);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, 10, theData.slice(0, 10), 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
  coll.reset();

  // Test with a non-empty read queue and an initial read that
  // *does* end up consuming all the data.
  slicer = new Slicer(source);
  slicer.read(theData.length, coll.callback);
  slicer.read(0, coll.callback);
  source.emit("data", theData);
  assert.equal(coll.callbacks.length, 2);
  coll.assertCallback(0, undefined, theData.length, theData, 0);
  coll.assertCallback(1, undefined, 0, new Buffer(0), 0);
}

/**
 * Test a spectrum of cases of reading, where the requested
 * size of the reads and the size of the buffers passed through in
 * data events vary. This is meant to cover cases where the length
 * to be read is larger, the same, and smaller than the length of
 * the buffers being emitted in events.
 */
function readLengthSpectrum() {
  var EMIT_COUNT = 200;

  for (var readLength = 1; readLength < 100; readLength += 7) {
    for (var emitLength = 1; emitLength < 100; emitLength += 7) {
      tryWith(readLength, emitLength);
    }
  }

  function tryWith(readLength, emitLength) {
    var source = new events.EventEmitter();
    var slicer = new Slicer(source);
    var coll = new CallbackCollector();

    var buffer = makeEmitBuf(EMIT_COUNT * emitLength);
    doEmit(source, buffer, emitLength);

    while (buffer.length >= readLength) {
      var expectBuf = buffer.slice(0, readLength);
      slicer.read(readLength, coll.callback);
      assert.equal(coll.callbacks.length, 1);
      coll.assertCallback(0, undefined, readLength, expectBuf, 0);
      coll.reset();
      buffer = buffer.slice(readLength);
    }
  }

  function doEmit(source, buffer, emitLength) {
    while (buffer.length !== 0) {
      source.emit("data", buffer.slice(0, emitLength));
      buffer = buffer.slice(emitLength);
    }
  }

  function makeEmitBuf(length) {
    var result = new Buffer(length);
    var ch = 0x41; // 'A'
    for (var i = 0; i < length; i++) {
      result[i] = ch;
      ch++;
      if (ch > 0x5a /* 'Z' */) {
        ch = 0x41;
      }
    }

    return result;
  }
}

function test() {
  constructor();
  constructorFailures();
  readableTransition();
  destroy();
  getErrorGotError();
  readAllNoData();
  readAllImmediateData();
  readAllAfterReadWithLength();
  readWithZeroLength();
  readLengthSpectrum();
}

module.exports = {
  test: test
};
<|endoftext|>


<commit_before>"use strict";

var processor = require("./processor");
var xmlUtil = require("./xml");
var util = require('util');

var xpath = xmlUtil.xpath;

function Parser() {}

Parser.prototype.init = function (jsPath, cardinality, xpath, component, sourceKey) {
    var range = cardinality.split(/\.\./);
    var lower = range[0];
    var upper = range[range.length - 1];

    this.xpath = xpath;
    this.cardinality = cardinality;

    this.required = lower === '*' || parseInt(lower) > 0;
    this.multiple = upper === '*' || parseInt(upper) > 1;

    this.jsPath = jsPath;
    this.component = component || null;
    this.sourceKey = sourceKey;
    return this;
};

Parser.prototype.run = function (parentInstance, node, sourceKey) {
    var component = this.component;
    var matches = xpath(node, this.xpath);
    var jsVal = matches.map(function (match, i) {
        if (component && component.componentName) {
            var instance = component.instance(parentInstance);
            if (component.hasParsers()) {
                instance.run(match, sourceKey);
            } else {
                instance.run(processor.asString(match), sourceKey);
            }
            return instance;
        } else if (component) {
            return component(match);
        } else {
            return processor.asString(match);
        }
    });

    var msg;
    if (!this.multiple && jsVal.length > 1) {
        msg = util.format("cardinality error: found %d when expecting %s at %s", jsVal.length, this.cardinality, this.xpath);
        parentInstance.errors.push(msg);
    }

    if (this.required && jsVal.length === 0) {
        msg = parentInstance.pathToTop().map(function (a) {
            return a.component.componentName;
        });
        parentInstance.errors.push("nullFlavor alert:  missing but required " + this.jsPath + " in " + msg.join(" -> "));
    }

    if (!this.multiple) {
        jsVal = (jsVal.length === 0 ? null : jsVal[0]);
    }

    parentInstance.setJs(this.jsPath, jsVal);

    return this;
};

module.exports = Parser;
<commit_msg>parse nullFlavors depending on cardinality<commit_after>"use strict";

var processor = require("./processor");
var xmlUtil = require("./xml");
var util = require('util');

var xpath = xmlUtil.xpath;

function Parser() {}

Parser.prototype.init = function (jsPath, cardinality, xpath, component, sourceKey) {
    var range = cardinality.split(/\.\./);
    var lower = range[0];
    var upper = range[range.length - 1];

    this.xpath = xpath;
    this.cardinality = cardinality;

    this.required = lower === '*' || parseInt(lower) > 0;
    this.multiple = upper === '*' || parseInt(upper) > 1;

    this.jsPath = jsPath;
    this.component = component || null;
    this.sourceKey = sourceKey;
    return this;
};

Parser.prototype.run = function (parentInstance, node, sourceKey) {
    var nullFlavorDisplayNames = {
        "NI": "no information",
        "NA": "not applicable",
        "UNK": "unknown",
        "ASKU": "asked, but not known",
        "NAV": "temporarily unavailable",
        "NASK": "not asked",
        "TRC": "trace",
        "OTH": "other",
        "PINF": "positive infinity",
        "NINF": "negative infinity",
        "MSK": "masked"
    };

    var component = this.component;
    var matches = xpath(node, this.xpath);
    var _this = this;
    var jsVal = matches.map(function (match, i) {
        // parse nullFlavors
        if (match.type() === 'element') {
            var val = xpath(match, "@nullFlavor");
            if (val.length > 0) {
                if (_this.required) {
                    var nF = val[0].value();
                    return {
                        code: nF,
                        code_system_name: "Null Flavor",
                        name: nullFlavorDisplayNames[nF]
                    };
                } else {
                    return {
                        'js': null
                    };
                }
            }
        }
        if (component && component.componentName) {
            var instance = component.instance(parentInstance);
            if (component.hasParsers()) {
                instance.run(match, sourceKey);
            } else {
                instance.run(processor.asString(match), sourceKey);
            }
            return instance;
        } else if (component) {
            return component(match);
        } else {
            return processor.asString(match);
        }
    });

    var msg;
    if (!this.multiple && jsVal.length > 1) {
        msg = util.format("cardinality error: found %d when expecting %s at %s", jsVal.length, this.cardinality, this.xpath);
        parentInstance.errors.push(msg);
    }

    if (this.required && jsVal.length === 0) {
        msg = parentInstance.pathToTop().map(function (a) {
            return a.component.componentName;
        });
        parentInstance.errors.push("nullFlavor alert:  missing but required " + this.jsPath + " in " + msg.join(" -> "));
    }

    if (!this.multiple) {
        jsVal = (jsVal.length === 0 ? null : jsVal[0]);
    }

    parentInstance.setJs(this.jsPath, jsVal);

    return this;
};

module.exports = Parser;
<|endoftext|>


<commit_before>module.exports = function() {
  var parseVariables,
      gonzales = require('gonzales-pe'),
      gonzo = require('gonzales-ast');

  function astToSrc(ast, syntax) {
    return gonzales.astToSrc({
      ast: ast,
      syntax: syntax
    });
  }

  // Parse SASS variables to object
  parseVariables = function(string, syntax) {

    syntax = syntax || 'scss'

    var out = {},
      ast = gonzales.srcToAST({
        src: string,
        syntax: syntax
      });

    gonzo.traverse(ast, [{
      test: function(name, nodes) {
        return name === 'declaration' && nodes[1][0] === 'variable';
      },
      process: function(nodes) {
        var varName = nodes[1][1][1].filter(function(element) {
            return element != 'ident'
          })[0],
          varVal = astToSrc(nodes[4], syntax);

        out[varName] = varVal;
      }
    }, {
      test: function(name, nodes) {
        return name === 'atrules';
      },
      process: function(nodes) {
        var varName = nodes[1][1][1],
          varVal = astToSrc(nodes[4], 'scss');
        out[varName] = varVal;
      }
    }]);

    return out;
  }

  // Modifies string so that variables passed in object are updated
  setVariables = function(string, variables) {
    var sorted = [], lines = [];
    // TODO: This is temporary solution that just re-generates whole file
    for (var variableName in variables) {
      if (variables.hasOwnProperty(variableName)) {
        sorted.push(variableName);
      }
    }
    sorted.sort();
    for (var i = 0; i < sorted.length; i++) {
      lines.push('$' + sorted[i] + ': ' + variables[sorted[i]] + ';');
    }
    return lines.join('\n');
  }

  return {
    parseVariables: parseVariables,
    setVariables: setVariables
  }
}
<commit_msg>Hack LESS parser due to LESS AST bug<commit_after>module.exports = function() {
  var parseVariables,
      gonzales = require('gonzales-pe'),
      gonzo = require('gonzales-ast');

  function astToSrc(ast, syntax) {
    return gonzales.astToSrc({
      ast: ast,
      syntax: syntax
    });
  }

  // Parse SASS variables to object
  parseVariables = function(string, syntax) {

    syntax = syntax || 'scss'

    var out = {},
      ast = gonzales.srcToAST({
        src: string,
        syntax: syntax
      });

    gonzo.traverse(ast, [{
      test: function(name, nodes) {
        return name === 'declaration' && nodes[1][0] === 'variable';
      },
      process: function(nodes) {
        var varName = nodes[1][1][1].filter(function(element) {
            return element != 'ident'
          })[0],
          varVal = astToSrc(nodes[4], syntax);

        out[varName] = varVal;
      }
    }, {
      test: function(name, nodes) {
        return name === 'atrules';
      },
      process: function(nodes) {
        var varName = nodes[1][1][1],
          varVal = '';

        /* Grabs all the listed values
         * Fix then https://github.com/tonyganch/gonzales-pe/issues/17 is fixed */
        nodes.forEach(function(element){
          if (
            element == 'atrules' ||
            element[0] == 'atkeyword'
          ) {
            return
          };
          if (
            element[0] == 'operator' &&
            element[1] == ':'
          ) {
            return;
          }
          varVal += astToSrc(element, 'less');
        });
        varVal = varVal.trim();
        out[varName] = varVal;
      }
    }]);

    return out;
  }

  // Modifies string so that variables passed in object are updated
  setVariables = function(string, variables) {
    var sorted = [], lines = [];
    // TODO: This is temporary solution that just re-generates whole file
    for (var variableName in variables) {
      if (variables.hasOwnProperty(variableName)) {
        sorted.push(variableName);
      }
    }
    sorted.sort();
    for (var i = 0; i < sorted.length; i++) {
      lines.push('$' + sorted[i] + ': ' + variables[sorted[i]] + ';');
    }
    return lines.join('\n');
  }

  return {
    parseVariables: parseVariables,
    setVariables: setVariables
  }
}
<|endoftext|>