From 603bdd1e8c7c0078b5376148515673b943d5b652 Mon Sep 17 00:00:00 2001 From: Markus Felten Date: Wed, 4 Jan 2017 03:33:38 +0100 Subject: [PATCH] fix: pass errors as rejected promises --- src/expander.js | 153 +++++-------------------------------------- src/grammar.js | 126 +++++++++++++++++++++++++++++++++++ tests/simple_test.js | 72 +++++++++----------- 3 files changed, 174 insertions(+), 177 deletions(-) create mode 100644 src/grammar.js diff --git a/src/expander.js b/src/expander.js index 303c11c8..b3256f62 100644 --- a/src/expander.js +++ b/src/expander.js @@ -5,56 +5,20 @@ * @module config-expander */ -const path = require('path'); - import { createContext } from 'expression-expander'; -import { - create -} -from 'pratt-parser'; - import { createValue } from './util'; import { - functions -} -from './functions'; - -class AST { - get value() { - return undefined; - } -} - -function Error(error) { - console.error(error); - return Promise.reject(error); -} - -class BinOP extends AST { - constructor(a, b, exec) { - super(); - Object.defineProperty(this, 'value', { - get: () => exec(a, b) - }); - } -} - -class FCall extends AST { - constructor(f, context, args) { - super(); - Object.defineProperty(this, 'value', { - get: () => f.apply(context, args).value - }); - } + grammar } +from './grammar'; /** * Expands expressions in a configuration object @@ -63,105 +27,24 @@ class FCall extends AST { * @returns {Promise} expanded configuration */ export function expand(config, options = {}) { - const constants = options.constants || { - basedir: '/' - }; - - const context = { - constants: constants - }; - - const grammar = create({ - identifier(value, properties, context) { - if (context.length >= 2) { - const ctx = context[context.length - 2]; - - if (ctx.value[value] !== undefined) { - properties.value.value = ctx.value[value]; - return; - } - } - if (context[0].value.constants) { - const v = context[0].value.constants[value]; - if (v !== undefined) { - properties.value.value = v; - return; - } - } - - const c = constants[value]; - if (c) { - properties.value.value = c; - } else { - properties.type.value = 'identifier'; - properties.value.value = value; - } - }, - prefix: { - '(': { - precedence: 80, - led(grammar, left) { - if (left.type === 'identifier') { - const args = []; - - if (grammar.token.value !== ')') { - while (true) { - args.push(grammar.expression(0)); - - if (grammar.token.value !== ',') { - break; - } - grammar.advance(','); - } - } - - grammar.advance(')'); - - const f = functions[left.value]; - if (f) { - return new FCall(f, context, args); - } else { - return Error(`unknown function ${left.value}`); - } - } else { - const e = grammar.expression(0); - grammar.advance(')'); - return e; - } - } - } - }, - infix: { - ',': {}, - ')': {}, - '+': { - precedence: 50, - combine: (left, right) => new BinOP(left, right, (l, r) => l.value + r.value) - }, - '-': { - precedence: 50, - combine: (left, right) => new BinOP(left, right, (l, r) => l.value - r.value) - }, - '*': { - precedence: 60, - combine: (left, right) => new BinOP(left, right, (l, r) => l.value * r.value) - }, - '/': { - precedence: 60, - combine: (left, right) => new BinOP(left, right, (l, r) => l.value / r.value) - } + try { + const constants = options.constants || { + basedir: '/' + }; + + const context = { + constants + }; + + const ee = createContext({ + evaluate: (expression, _context, path) => { + context.path = path; + const ast = grammar.parse(expression, context); + return ast.value; } - }); - - const ctx = createContext({ - evaluate: (expression, context, path) => { - const ast = grammar.parse(expression, path); - return ast.value; - } - }); + }); - try { - const r = ctx.expand(config); + const r = ee.expand(config); return r instanceof Promise ? r : Promise.resolve(r); } catch (e) { return Promise.reject(e); diff --git a/src/grammar.js b/src/grammar.js new file mode 100644 index 00000000..b0496fee --- /dev/null +++ b/src/grammar.js @@ -0,0 +1,126 @@ +/* jslint node: true, esnext: true */ +'use strict'; + +import { + create +} +from 'pratt-parser'; + +import { + functions +} +from './functions'; + +class AST { + get value() { + return undefined; + } +} + +function Error(error) { + return { + value: Promise.reject(error) + }; +} + +class BinOP extends AST { + constructor(a, b, exec) { + super(); + Object.defineProperty(this, 'value', { + get: () => exec(a, b) + }); + } +} + +class FCall extends AST { + constructor(f, context, args) { + super(); + Object.defineProperty(this, 'value', { + get: () => f.apply(context, args).value + }); + } +} + +export const grammar = create({ + identifier(value, properties, context) { + const path = context.path; + + if (path.length >= 2) { + const ctx = path[path.length - 2]; + + if (ctx.value[value] !== undefined) { + properties.value.value = ctx.value[value]; + return; + } + } + if (path[0].value.constants) { + const v = path[0].value.constants[value]; + if (v !== undefined) { + properties.value.value = v; + return; + } + } + + const c = context.constants[value]; + if (c) { + properties.value.value = c; + } else { + properties.type.value = 'identifier'; + properties.value.value = value; + } + }, + prefix: { + '(': { + precedence: 80, + led(grammar, left) { + if (left.type === 'identifier') { + const args = []; + + if (grammar.token.value !== ')') { + while (true) { + args.push(grammar.expression(0)); + + if (grammar.token.value !== ',') { + break; + } + grammar.advance(','); + } + } + + grammar.advance(')'); + + const f = functions[left.value]; + if (f) { + return new FCall(f, grammar.context, args); + } else { + return Error(`unknown function ${left.value}`); + } + } else { + const e = grammar.expression(0); + grammar.advance(')'); + return e; + } + } + } + }, + infix: { + ',': {}, + ')': {}, + '+': { + precedence: 50, + combine: (left, right) => new BinOP(left, right, (l, r) => l.value + r.value) + }, + '-': { + precedence: 50, + combine: (left, right) => new BinOP(left, right, (l, r) => l.value - r.value) + }, + '*': { + precedence: 60, + combine: (left, right) => new BinOP(left, right, (l, r) => l.value * r.value) + }, + '/': { + precedence: 60, + combine: (left, right) => new BinOP(left, right, (l, r) => l.value / r.value) + } + } +}); diff --git a/tests/simple_test.js b/tests/simple_test.js index 2a616f6e..c5b8513f 100644 --- a/tests/simple_test.js +++ b/tests/simple_test.js @@ -1,4 +1,4 @@ -/* global describe, it */ +/* global describe, it, xit */ /* jslint node: true, esnext: true */ 'use strict'; @@ -44,6 +44,22 @@ describe('expander', () => { }, name: 1 }))); + + it('double def', () => expand({ + constants: { + A: 2 + }, + name: '${A}' + }, { + constants: { + A: 7 + } + }).then(r => assert.deepEqual(r, { + constants: { + A: 2 + }, + name: 2 + }))); }); describe('expression', () => { @@ -85,47 +101,19 @@ describe('expander', () => { }); describe('functions', () => { - xit('unknown function', () => expand({ - name: "${thisFunctionIsUnknown('lower')}" - }).then(r => assert.deepEqual(r, { - name: 'LOWER' - }))); - - it('toUpperCase', () => expand({ - name: "${toUpperCase('lower')}" - }).then(r => assert.deepEqual(r, { - name: 'LOWER' - }))); - - it('toLowerCase', () => expand({ - name: "${toLowerCase('UPPER')}" - }).then(r => assert.deepEqual(r, { - name: 'upper' - }))); - - it('substring', () => expand({ - name: "${substring('lower',1,3)}" - }).then(r => assert.deepEqual(r, { - name: 'ow' - }))); - - it('replace', () => expand({ - name: "${replace('lower','ow','12')}" - }).then(r => assert.deepEqual(r, { - name: 'l12er' - }))); - - it('substring with expressions', () => expand({ - name: "${substring('lower',1,1+2*1)}" - }).then(r => assert.deepEqual(r, { - name: 'ow' - }))); - - it('substring with expressions', () => expand({ - name: "${substring('lower',1,number('2')+1)}" - }).then(r => assert.deepEqual(r, { - name: 'ow' - }))); + it('unknown function', () => expand( + "${thisFunctionIsUnknown()}" + ).then(e => assert.deepEqual(e, {})) + .catch(e => assert.deepEqual(e, "unknown function thisFunctionIsUnknown"))); + it('toUpperCase', () => expand("${toUpperCase('lower')}").then(r => assert.equal(r, 'LOWER'))); + it('toLowerCase', () => expand("${toLowerCase('UPPER')}").then(r => assert.equal(r, 'upper'))); + it('substring', () => expand("${substring('lower',1,3)}").then(r => assert.equal(r, 'ow'))); + it('replace', () => expand("${replace('lower','ow','12')}").then(r => assert.equal(r, 'l12er'))); + it('substring with expressions', () => expand("${substring('lower',1,1+2*1)}").then(r => assert.equal(r, + 'ow'))); + + it('substring with expressions', () => expand("${substring('lower',1,number('2')+1)}").then(r => assert.equal( + r, 'ow'))); it('encrypt/decrypt', () => expand("${decrypt('key',encrypt('key','secret'))}").then(r => assert.equal( r, 'secret')));