Comparing version 2.1.3 to 2.2.0
{ | ||
"name": "ducky", | ||
"version": "2.1.3", | ||
"version": "2.2.0", | ||
"description": "Duck-Typed Value Handling for JavaScript", | ||
"main": "./lib/ducky.js", | ||
"main": "./lib/ducky.browser.js", | ||
"homepage": "http://duckyjs.com", | ||
@@ -7,0 +7,0 @@ "license": "MIT", |
{ | ||
"ecmaFeatures": {}, | ||
"parser": "babel-eslint", | ||
"env": { | ||
"browser": false, | ||
"node": false, | ||
"node": true, | ||
"amd": false, | ||
"mocha": false, | ||
"mocha": true, | ||
"jasmine": false | ||
}, | ||
"globals": { | ||
"expect": true | ||
}, | ||
"rules": { | ||
@@ -17,6 +20,7 @@ "no-alert": 2, | ||
"no-catch-shadow": 2, | ||
"no-comma-dangle": 2, | ||
"no-comma-dangle": 0, | ||
"no-cond-assign": 2, | ||
"no-console": 2, | ||
"no-constant-condition": 2, | ||
"no-continue": 0, | ||
"no-control-regex": 2, | ||
@@ -27,2 +31,4 @@ "no-debugger": 2, | ||
"no-dupe-keys": 2, | ||
"no-dupe-args": 2, | ||
"no-duplicate-case": 2, | ||
"no-else-return": 0, | ||
@@ -54,3 +60,3 @@ "no-empty": 2, | ||
"no-lonely-if": 0, | ||
"no-loop-func": 2, | ||
"no-loop-func": 0, | ||
"no-mixed-requires": [0, false], | ||
@@ -72,2 +78,3 @@ "no-mixed-spaces-and-tabs": [2, false], | ||
"no-octal-escape": 2, | ||
"no-param-reassign": 0, | ||
"no-path-concat": 0, | ||
@@ -88,3 +95,3 @@ "no-plusplus": 0, | ||
"no-shadow-restricted-names": 2, | ||
"no-space-before-semi": 2, | ||
"no-space-before-semi": 0, | ||
"no-spaced-func": 2, | ||
@@ -95,6 +102,7 @@ "no-sparse-arrays": 2, | ||
"no-trailing-spaces": 2, | ||
"no-throw-literal": 0, | ||
"no-undef": 2, | ||
"no-undef-init": 2, | ||
"no-undefined": 0, | ||
"no-underscore-dangle": 2, | ||
"no-underscore-dangle": 0, | ||
"no-unreachable": 2, | ||
@@ -109,6 +117,6 @@ "no-unused-expressions": 2, | ||
"no-wrap-func": 2, | ||
"block-scoped-var": 0, | ||
"brace-style": [0, "1tbs"], | ||
"camelcase": 0, | ||
"comma-dangle": [2, "never"], | ||
"comma-spacing": 2, | ||
@@ -122,3 +130,3 @@ "comma-style": 0, | ||
"dot-notation": [2, { "allowKeywords": true }], | ||
"eol-last": 0, | ||
"eol-last": 2, | ||
"eqeqeq": 2, | ||
@@ -128,6 +136,8 @@ "func-names": 0, | ||
"generator-star": 0, | ||
"generator-star-spacing": 0, | ||
"global-strict": [2, "never"], | ||
"guard-for-in": 0, | ||
"handle-callback-err": 0, | ||
"key-spacing": [0, { "beforeColon": false, "afterColon": true }], | ||
"indent": 0, | ||
"key-spacing": [0, { "beforeColon": false, "afterColon": false }], | ||
"max-depth": [0, 4], | ||
@@ -138,6 +148,8 @@ "max-len": [0, 80, 4], | ||
"max-statements": [0, 10], | ||
"new-cap": 2, | ||
"new-cap": 0, | ||
"new-parens": 2, | ||
"newline-after-var": 0, | ||
"one-var": 0, | ||
"operator-assignment": [0, "always"], | ||
"operator-linebreak": 0, | ||
"padded-blocks": 0, | ||
@@ -147,3 +159,4 @@ "quote-props": 0, | ||
"radix": 0, | ||
"semi": 2, | ||
"semi": 0, | ||
"semi-spacing": [2, {"before": false, "after": true}], | ||
"sort-vars": 0, | ||
@@ -153,2 +166,4 @@ "space-after-function-name": [0, "never"], | ||
"space-before-blocks": [0, "always"], | ||
"space-before-function-paren": [0, "always"], | ||
"space-before-function-parentheses": [0, "always"], | ||
"space-in-brackets": [0, "never"], | ||
@@ -155,0 +170,0 @@ "space-in-parens": [0, "never"], |
@@ -27,8 +27,7 @@ /* | ||
module.exports = function (grunt) { | ||
grunt.loadNpmTasks("grunt-expand-include"); | ||
grunt.loadNpmTasks("grunt-contrib-jshint"); | ||
grunt.loadNpmTasks("grunt-contrib-uglify"); | ||
grunt.loadNpmTasks("grunt-contrib-clean"); | ||
grunt.loadNpmTasks("grunt-mocha-test"); | ||
grunt.loadNpmTasks("grunt-browserify"); | ||
grunt.loadNpmTasks("grunt-eslint"); | ||
grunt.loadNpmTasks("grunt-mocha-test"); | ||
@@ -38,15 +37,51 @@ grunt.initConfig({ | ||
version: grunt.file.readYAML("VERSION.yml"), | ||
"expand-include": { | ||
"ducky": { | ||
src: [ "src/ducky.js" ], | ||
dest: "lib/ducky.js", | ||
browserify: { | ||
"ducky-browser": { | ||
files: { | ||
"lib/ducky.browser.js": [ "src/ducky.js" ] | ||
}, | ||
options: { | ||
directiveSyntax: "js", | ||
globalDefines: { | ||
major: "<%= version.major %>", | ||
minor: "<%= version.minor %>", | ||
micro: "<%= version.micro %>", | ||
date: "<%= version.date %>" | ||
transform: [ | ||
[ "browserify-replace", { replace: [ | ||
{ from: /\$major/g, to: "<%= version.major %>" }, | ||
{ from: /\$minor/g, to: "<%= version.minor %>" }, | ||
{ from: /\$micro/g, to: "<%= version.micro %>" }, | ||
{ from: /\$date/g, to: "<%= version.date %>" } | ||
]}], | ||
"babelify" | ||
], | ||
plugin: [ | ||
[ "minifyify", { map: "ducky.browser.map", output: "lib/ducky.browser.map" } ], | ||
[ "browserify-derequire" ], | ||
[ "browserify-header" ] | ||
], | ||
browserifyOptions: { | ||
standalone: "Ducky", | ||
debug: true | ||
} | ||
} | ||
}, | ||
"ducky-node": { | ||
files: { | ||
"lib/ducky.node.js": [ "src/ducky.js" ] | ||
}, | ||
options: { | ||
transform: [ | ||
[ "browserify-replace", { replace: [ | ||
{ from: /\$major/g, to: "<%= version.major %>" }, | ||
{ from: /\$minor/g, to: "<%= version.minor %>" }, | ||
{ from: /\$micro/g, to: "<%= version.micro %>" }, | ||
{ from: /\$date/g, to: "<%= version.date %>" } | ||
]}], | ||
"babelify" | ||
], | ||
plugin: [ | ||
[ "browserify-derequire" ], | ||
[ "browserify-header" ] | ||
], | ||
browserifyOptions: { | ||
standalone: "Ducky", | ||
debug: false | ||
} | ||
} | ||
} | ||
@@ -58,4 +93,4 @@ }, | ||
}, | ||
gruntfile: [ "Gruntfile.js" ], | ||
"ducky": [ "lib/ducky.js" ] | ||
gruntfile: [ "Gruntfile.js" ], | ||
"ducky": [ "src/**/*.js" ] | ||
}, | ||
@@ -66,3 +101,4 @@ eslint: { | ||
}, | ||
target: [ "lib/ducky.js" ], | ||
gruntfile: [ "Gruntfile.js" ], | ||
"ducky": [ "src/**/*.js" ] | ||
}, | ||
@@ -77,12 +113,2 @@ mochaTest: { | ||
}, | ||
uglify: { | ||
options: { | ||
preserveComments: "some", | ||
report: "min" | ||
}, | ||
dist: { | ||
src: "lib/ducky.js", | ||
dest: "lib/ducky.min.js" | ||
} | ||
}, | ||
clean: { | ||
@@ -94,4 +120,4 @@ clean: [ "lib/*", "lib" ], | ||
grunt.registerTask("default", [ "expand-include", "jshint", "eslint", "mochaTest", "uglify" ]); | ||
grunt.registerTask("default", [ "jshint", "eslint", "browserify", "mochaTest" ]); | ||
}; | ||
{ | ||
"esnext": true, | ||
"asi": true, | ||
"maxerr": 200, | ||
@@ -3,0 +5,0 @@ "bitwise": true, |
@@ -5,3 +5,3 @@ { | ||
"description": "Duck-Typed Value Handling for JavaScript", | ||
"version": "2.1.3", | ||
"version": "2.2.0", | ||
"license": "MIT", | ||
@@ -23,14 +23,22 @@ "author": { | ||
}, | ||
"main": "./lib/ducky.js", | ||
"main": "./lib/ducky.node.js", | ||
"devDependencies": { | ||
"grunt": "~0.4.5", | ||
"grunt-cli": "~0.1.13", | ||
"grunt-contrib-jshint": "~0.11.0", | ||
"grunt-contrib-uglify": "~0.7.0", | ||
"grunt-contrib-jshint": "~0.11.2", | ||
"grunt-contrib-uglify": "~0.9.1", | ||
"grunt-contrib-clean": "~0.6.0", | ||
"grunt-eslint": "~5.1.0", | ||
"grunt-eslint": "~16.0.0", | ||
"babel-eslint": "~3.1.23", | ||
"grunt-browserify": "~3.8.0", | ||
"grunt-mocha-test": "~0.12.7", | ||
"grunt-expand-include": "~0.9.6", | ||
"chai": "~1.10.0", | ||
"chai-fuzzy": "~1.4.0" | ||
"mocha": "~2.2.5", | ||
"chai": "~3.0.0", | ||
"chai-fuzzy": "~1.5.0", | ||
"underscore": "~1.8.3", | ||
"babelify": "~6.1.2", | ||
"minifyify": "~7.0.2", | ||
"browserify-replace": "~0.9.0", | ||
"browserify-header": "~0.9.2", | ||
"browserify-derequire": "~0.9.4" | ||
}, | ||
@@ -37,0 +45,0 @@ "dependencies" : { |
@@ -25,4 +25,9 @@ /* | ||
/* global $major: false */ | ||
/* global $minor: false */ | ||
/* global $micro: false */ | ||
/* global $date: false */ | ||
/* API version */ | ||
ducky.version = { | ||
var version = { | ||
major: $major, | ||
@@ -32,3 +37,5 @@ minor: $minor, | ||
date: $date | ||
}; | ||
} | ||
export { version } | ||
@@ -26,98 +26,99 @@ /* | ||
/* custom Token class */ | ||
var Token = function () { | ||
this.name = ""; | ||
this.text = ""; | ||
this.tokens = []; | ||
this.pos = 0; | ||
this.len = 0; | ||
}; | ||
Token.prototype = { | ||
class Token { | ||
constructor () { | ||
this.name = "" | ||
this.text = "" | ||
this.tokens = [] | ||
this.pos = 0 | ||
this.len = 0 | ||
} | ||
/* setter for caller context name */ | ||
setName: function (name) { | ||
this.name = name; | ||
}, | ||
setName (name) { | ||
this.name = name | ||
} | ||
/* setter for plain-text input */ | ||
setText: function (text) { | ||
this.text = text; | ||
}, | ||
setText (text) { | ||
this.text = text | ||
} | ||
/* setter for additional token symbols */ | ||
addToken: function (b1, b2, e2, e1, symbol) { | ||
this.tokens.push({ b1: b1, b2: b2, e2: e2, e1: e1, symbol: symbol }); | ||
this.len++; | ||
}, | ||
addToken (b1, b2, e2, e1, symbol) { | ||
this.tokens.push({ b1: b1, b2: b2, e2: e2, e1: e1, symbol: symbol }) | ||
this.len++ | ||
} | ||
/* peek at the next token or token at particular offset */ | ||
peek: function (offset) { | ||
peek (offset) { | ||
if (typeof offset === "undefined") | ||
offset = 0; | ||
offset = 0 | ||
if (offset >= this.len) | ||
throw new Error(this.name + ": parse error: not enough tokens"); | ||
return this.tokens[this.pos + offset].symbol; | ||
}, | ||
throw new Error(`${this.name}: parse error: not enough tokens`) | ||
return this.tokens[this.pos + offset].symbol | ||
} | ||
/* skip one or more tokens */ | ||
skip: function (len) { | ||
skip (len) { | ||
if (typeof len === "undefined") | ||
len = 1; | ||
len = 1 | ||
if (len > this.len) | ||
throw new Error(this.name + ": parse error: not enough tokens available to skip: " + this.ctx()); | ||
this.pos += len; | ||
this.len -= len; | ||
}, | ||
throw new Error(`${this.name}: parse error: not enough tokens available to skip: ${this.ctx()}`) | ||
this.pos += len | ||
this.len -= len | ||
} | ||
/* consume the current token (by expecting it to be a particular symbol) */ | ||
consume: function (symbol) { | ||
consume (symbol) { | ||
if (this.len <= 0) | ||
throw new Error(this.name + ": parse error: no more tokens available to consume: " + this.ctx()); | ||
throw new Error(`${this.name}: parse error: no more tokens available to consume: ${this.ctx()}`) | ||
if (this.tokens[this.pos].symbol !== symbol) | ||
throw new Error(this.name + ": parse error: expected token symbol \"" + symbol + "\": " + this.ctx()); | ||
this.pos++; | ||
this.len--; | ||
}, | ||
throw new Error(`${this.name}: parse error: expected token symbol "${symbol}": ${this.ctx()}`) | ||
this.pos++ | ||
this.len-- | ||
} | ||
/* return a textual description of the token parsing context */ | ||
ctx: function (width) { | ||
ctx (width) { | ||
if (typeof width === "undefined") | ||
width = 78; | ||
var tok = this.tokens[this.pos]; | ||
width = 78 | ||
let tok = this.tokens[this.pos] | ||
/* the current token itself */ | ||
var ctx = "<" + this.text.substr(tok.b2, tok.e2 - tok.b2 + 1) + ">"; | ||
ctx = this.text.substr(tok.b1, tok.b2 - tok.b1) + ctx; | ||
ctx = ctx + this.text.substr(tok.e2 + 1, tok.e1 - tok.e2); | ||
let ctx = "<" + this.text.substr(tok.b2, tok.e2 - tok.b2 + 1) + ">" | ||
ctx = this.text.substr(tok.b1, tok.b2 - tok.b1) + ctx | ||
ctx = ctx + this.text.substr(tok.e2 + 1, tok.e1 - tok.e2) | ||
/* the previous and following token(s) */ | ||
var k = (width - ctx.length); | ||
let k = (width - ctx.length) | ||
if (k > 0) { | ||
k = Math.floor(k / 2); | ||
var i, str; | ||
k = Math.floor(k / 2) | ||
let i, str | ||
if (this.pos > 0) { | ||
/* previous token(s) */ | ||
var k1 = 0; | ||
let k1 = 0 | ||
for (i = this.pos - 1; i >= 0; i--) { | ||
tok = this.tokens[i]; | ||
str = this.text.substr(tok.b1, tok.e1 - tok.b1 + 1); | ||
k1 += str.length; | ||
tok = this.tokens[i] | ||
str = this.text.substr(tok.b1, tok.e1 - tok.b1 + 1) | ||
k1 += str.length | ||
if (k1 > k) | ||
break; | ||
ctx = str + ctx; | ||
break | ||
ctx = str + ctx | ||
} | ||
if (i > 0) | ||
ctx = "[...]" + ctx; | ||
ctx = "[...]" + ctx | ||
} | ||
if (this.len > 1) { | ||
/* following token(s) */ | ||
var k2 = 0; | ||
let k2 = 0 | ||
for (i = this.pos + 1; i < this.pos + this.len; i++) { | ||
tok = this.tokens[i]; | ||
str = this.text.substr(tok.b1, tok.e1 - tok.b1 + 1); | ||
k2 += str.length; | ||
tok = this.tokens[i] | ||
str = this.text.substr(tok.b1, tok.e1 - tok.b1 + 1) | ||
k2 += str.length | ||
if (k2 > k) | ||
break; | ||
ctx = ctx + str; | ||
break | ||
ctx = ctx + str | ||
} | ||
if (i < this.pos + this.len) | ||
ctx = ctx + "[...]"; | ||
ctx = ctx + "[...]" | ||
} | ||
@@ -129,6 +130,8 @@ } | ||
.replace(/\n/, "\\n") | ||
.replace(/\t/, "\\t"); | ||
return ctx; | ||
.replace(/\t/, "\\t") | ||
return ctx | ||
} | ||
}; | ||
} | ||
export { Token } | ||
@@ -27,35 +27,37 @@ /* | ||
/* result and state variables */ | ||
var path = []; | ||
var pos = 0; | ||
let path = [] | ||
let pos = 0 | ||
/* iterate over selection specification */ | ||
var m; | ||
var txt = spec; | ||
let m | ||
let txt = spec | ||
while (txt !== "") { | ||
/* case 1: standard path segment */ | ||
if ((m = txt.match(/^\s*(?:\.)?\s*([a-zA-Z$0-9_][a-zA-Z$0-9_:-]*)/)) !== null) | ||
path.push(m[1]); | ||
path.push(m[1]) | ||
/* case 2: numerical array-style dereference */ | ||
else if ((m = txt.match(/^\s*\[\s*(\d+|\*{1,2})\s*\]/)) !== null) | ||
path.push(m[1]); | ||
path.push(m[1]) | ||
/* case 3: double-quoted string array-style dereference */ | ||
else if ((m = txt.match(/^\s*\[\s*"((?:\\"|.)*?)"\s*\]/)) !== null) | ||
path.push(m[1].replace(/\\"/g, "\"")); | ||
path.push(m[1].replace(/\\"/g, "\"")) | ||
/* case 4: single-quoted string array-style dereference */ | ||
else if ((m = txt.match(/^\s*\[\s*'((?:\\'|.)*?)'\s*\]/)) !== null) | ||
path.push(m[1].replace(/\\'/g, "'")); | ||
path.push(m[1].replace(/\\'/g, "'")) | ||
/* skip all whitespaces between segments */ | ||
else if ((m = txt.match(/^\s+$/)) !== null) | ||
break; | ||
break | ||
else | ||
throw new Error("select: parse error: invalid character at: " + | ||
spec.substr(0, pos) + "<" + txt.substr(0, 1) + ">" + txt.substr(1)); | ||
spec.substr(0, pos) + "<" + txt.substr(0, 1) + ">" + txt.substr(1)) | ||
/* advance parsing position */ | ||
pos += m[0].length; | ||
txt = txt.substr(m[0].length); | ||
pos += m[0].length | ||
txt = txt.substr(m[0].length) | ||
} | ||
return path; | ||
}; | ||
return path | ||
} | ||
export { select_compile } | ||
@@ -30,13 +30,13 @@ /* | ||
if (arguments.length === 3) | ||
throw new Error("select: cannot set value on empty path"); | ||
throw new Error("select: cannot set value on empty path") | ||
else | ||
return obj; | ||
return obj | ||
} | ||
/* step into object graph according to path prefix */ | ||
var i = 0; | ||
let i = 0 | ||
while (i < path.length - 1) { | ||
if (typeof obj !== "object") | ||
throw new Error("select: cannot further dereference: no more intermediate objects in path"); | ||
obj = obj[path[i++]]; | ||
throw new Error("select: cannot further dereference: no more intermediate objects in path") | ||
obj = obj[path[i++]] | ||
} | ||
@@ -46,22 +46,24 @@ | ||
if (typeof obj !== "object") | ||
throw new Error("select: cannot further dereference: no object at end of path"); | ||
var value_old = obj[path[i]]; | ||
throw new Error("select: cannot further dereference: no object at end of path") | ||
let value_old = obj[path[i]] | ||
/* optionally set new value */ | ||
if (arguments.length === 3) { | ||
var value_new = arguments[2]; | ||
let value_new = arguments[2] | ||
if (value_new === undefined) { | ||
/* delete value from collection */ | ||
if (obj instanceof Array) | ||
obj.splice(parseInt(path[i], 10), 1); | ||
obj.splice(parseInt(path[i], 10), 1) | ||
else | ||
delete obj[path[i]]; | ||
delete obj[path[i]] | ||
} | ||
else | ||
/* set value into collection */ | ||
obj[path[i]] = value_new; | ||
obj[path[i]] = value_new | ||
} | ||
return value_old; | ||
}; | ||
return value_old | ||
} | ||
export { select_execute } | ||
@@ -25,25 +25,25 @@ /* | ||
import { select_compile } from "./ducky-3-select-1-compile.js" | ||
import { select_execute } from "./ducky-3-select-2-execute.js" | ||
/* the internal compile cache */ | ||
var select_cache = {}; | ||
let select_cache = {} | ||
/* API function: select an arbitrary value via a path specification | ||
and either get the current value or set the new value */ | ||
ducky.select = function (obj, spec, value) { | ||
var select = function (obj, spec, value) { | ||
/* sanity check arguments */ | ||
if (arguments.length < 2) | ||
throw new Error("select: invalid number of arguments: \"" + | ||
arguments.length + "\" (minimum of 2 expected)"); | ||
throw new Error(`select: invalid number of arguments: ${arguments.length} (minimum of 2 expected)`) | ||
else if (arguments.length > 3) | ||
throw new Error("select: invalid number of arguments: \"" + | ||
arguments.length + "\" (maximum of 3 expected)"); | ||
throw new Error(`select: invalid number of arguments: ${arguments.length} (maximum of 3 expected)`) | ||
if (typeof spec !== "string") | ||
throw new Error("select: invalid specification argument: \"" + | ||
spec + "\" (string expected)"); | ||
throw new Error(`select: invalid specification argument: "${spec}" (string expected)`) | ||
/* compile select path from specification | ||
or reuse cached pre-compiled selection path */ | ||
var path = select_cache[spec]; | ||
let path = select_cache[spec] | ||
if (typeof path === "undefined") { | ||
path = select_compile(spec); | ||
select_cache[spec] = path; | ||
path = select_compile(spec) | ||
select_cache[spec] = path | ||
} | ||
@@ -56,31 +56,28 @@ | ||
: select_execute(obj, path, value) | ||
); | ||
}; | ||
) | ||
} | ||
/* compile a path specification into array of dereferencing steps */ | ||
ducky.select.compile = function (spec) { | ||
select.compile = function (spec) { | ||
/* sanity check argument */ | ||
if (arguments.length !== 1) | ||
throw new Error("select: invalid number of arguments: \"" + | ||
arguments.length + "\" (exactly 1 expected)"); | ||
throw new Error(`select: invalid number of arguments: ${arguments.length} (exactly 1 expected)`) | ||
if (typeof spec !== "string") | ||
throw new Error("select: invalid specification argument: \"" + | ||
spec + "\" (string expected)"); | ||
return select_compile.apply(undefined, arguments); | ||
}; | ||
throw new Error(`select: invalid specification argument: "${spec}" (string expected)`) | ||
return select_compile.apply(undefined, arguments) | ||
} | ||
/* execute object selection */ | ||
ducky.select.execute = function (obj, path) { | ||
select.execute = function (obj, path) { | ||
/* sanity check arguments */ | ||
if (arguments.length < 2) | ||
throw new Error("select: invalid number of arguments: \"" + | ||
arguments.length + "\" (minimum of 2 expected)"); | ||
throw new Error(`select: invalid number of arguments: ${arguments.length} (minimum of 2 expected)`) | ||
else if (arguments.length > 3) | ||
throw new Error("select: invalid number of arguments: \"" + | ||
arguments.length + "\" (maximum of 3 expected)"); | ||
throw new Error(`select: invalid number of arguments: ${arguments.length} (maximum of 3 expected)`) | ||
if (!(typeof path === "object" && path instanceof Array)) | ||
throw new Error("select: invalid path argument: \"" + | ||
path + "\" (array expected)"); | ||
return select_execute.apply(undefined, arguments); | ||
}; | ||
throw new Error(`select: invalid path argument: "${path}" (array expected)`) | ||
return select_execute.apply(undefined, arguments) | ||
} | ||
export { select } | ||
@@ -25,16 +25,18 @@ /* | ||
import { Token } from "./ducky-1-util.js" | ||
/* tokenize the validation specification */ | ||
var validate_tokenize = function (spec) { | ||
/* create new Token abstraction */ | ||
var token = new Token(); | ||
token.setName("validate"); | ||
token.setText(spec); | ||
var token = new Token() | ||
token.setName("validate") | ||
token.setText(spec) | ||
/* determine individual token symbols */ | ||
var m; | ||
var b = 0; | ||
let m | ||
let b = 0 | ||
while (spec !== "") { | ||
m = spec.match(/^(\s*)([^{}\[\]:,?*+()!|\s]+|[{}\[\]:,?*+()!|])(\s*)/); | ||
m = spec.match(/^(\s*)([^{}\[\]:,?*+()!|\s]+|[{}\[\]:,?*+()!|])(\s*)/) | ||
if (m === null) | ||
throw new Error("validate: parse error: cannot further canonicalize: \"" + spec + "\""); | ||
throw new Error(`validate: parse error: cannot further canonicalize: "${spec}"`) | ||
token.addToken( | ||
@@ -46,8 +48,10 @@ b, | ||
m[2] | ||
); | ||
spec = spec.substr(m[0].length); | ||
b += m[0].length; | ||
) | ||
spec = spec.substr(m[0].length) | ||
b += m[0].length | ||
} | ||
return token; | ||
}; | ||
return token | ||
} | ||
export { validate_tokenize } | ||
@@ -27,115 +27,115 @@ /* | ||
var validate_parse = { | ||
parse_spec: function (token) { | ||
parse_spec (token) { | ||
if (token.len <= 0) | ||
return null; | ||
var ast; | ||
var symbol = token.peek(); | ||
return null | ||
let ast | ||
let symbol = token.peek() | ||
if (symbol === "!") | ||
ast = this.parse_not(token); | ||
ast = this.parse_not(token) | ||
else if (symbol === "(") | ||
ast = this.parse_group(token); | ||
ast = this.parse_group(token) | ||
else if (symbol === "{") | ||
ast = this.parse_hash(token); | ||
ast = this.parse_hash(token) | ||
else if (symbol === "[") | ||
ast = this.parse_array(token); | ||
ast = this.parse_array(token) | ||
else if (symbol.match(/^(?:null|undefined|boolean|number|string|function|object)$/)) | ||
ast = this.parse_primary(token); | ||
ast = this.parse_primary(token) | ||
else if (symbol === "any") | ||
ast = this.parse_any(token); | ||
ast = this.parse_any(token) | ||
else if (symbol.match(/^[_a-zA-Z$][_a-zA-Z$0-9]*(?:\.[_a-zA-Z$][_a-zA-Z$0-9]*)*$/)) | ||
ast = this.parse_class(token); | ||
ast = this.parse_class(token) | ||
else | ||
throw new Error("validate: parse error: invalid token symbol: \"" + token.ctx() + "\""); | ||
return ast; | ||
throw new Error(`validate: parse error: invalid token symbol: "${token.ctx()}"`) | ||
return ast | ||
}, | ||
/* parse boolean "not" operation */ | ||
parse_not: function (token) { | ||
token.consume("!"); | ||
var ast = this.parse_spec(token); /* RECURSION */ | ||
ast = { type: "not", op: ast }; | ||
return ast; | ||
parse_not (token) { | ||
token.consume("!") | ||
let ast = this.parse_spec(token) /* RECURSION */ | ||
ast = { type: "not", op: ast } | ||
return ast | ||
}, | ||
/* parse group (for boolean "or" operation) */ | ||
parse_group: function (token) { | ||
token.consume("("); | ||
var ast = this.parse_spec(token); | ||
parse_group (token) { | ||
token.consume("(") | ||
let ast = this.parse_spec(token) | ||
while (token.peek() === "|") { | ||
token.consume("|"); | ||
var child = this.parse_spec(token); /* RECURSION */ | ||
ast = { type: "or", op1: ast, op2: child }; | ||
token.consume("|") | ||
let child = this.parse_spec(token) /* RECURSION */ | ||
ast = { type: "or", op1: ast, op2: child } | ||
} | ||
token.consume(")"); | ||
return ast; | ||
token.consume(")") | ||
return ast | ||
}, | ||
/* parse hash type specification */ | ||
parse_hash: function (token) { | ||
token.consume("{"); | ||
var elements = []; | ||
parse_hash (token) { | ||
token.consume("{") | ||
let elements = [] | ||
while (token.peek() !== "}") { | ||
var key = this.parse_key(token); | ||
var arity = this.parse_arity(token, "?"); | ||
token.consume(":"); | ||
var spec = this.parse_spec(token); /* RECURSION */ | ||
elements.push({ type: "element", key: key, arity: arity, element: spec }); | ||
let key = this.parse_key(token) | ||
let arity = this.parse_arity(token, "?") | ||
token.consume(":") | ||
let spec = this.parse_spec(token) /* RECURSION */ | ||
elements.push({ type: "element", key: key, arity: arity, element: spec }) | ||
if (token.peek() === ",") | ||
token.skip(); | ||
token.skip() | ||
else | ||
break; | ||
break | ||
} | ||
var ast = { type: "hash", elements: elements }; | ||
token.consume("}"); | ||
return ast; | ||
let ast = { type: "hash", elements: elements } | ||
token.consume("}") | ||
return ast | ||
}, | ||
/* parse array type specification */ | ||
parse_array: function (token) { | ||
token.consume("["); | ||
var elements = []; | ||
parse_array (token) { | ||
token.consume("[") | ||
let elements = [] | ||
while (token.peek() !== "]") { | ||
var spec = this.parse_spec(token); /* RECURSION */ | ||
var arity = this.parse_arity(token, "?*+"); | ||
elements.push({ type: "element", element: spec, arity: arity }); | ||
var spec = this.parse_spec(token) /* RECURSION */ | ||
var arity = this.parse_arity(token, "?*+") | ||
elements.push({ type: "element", element: spec, arity: arity }) | ||
if (token.peek() === ",") | ||
token.skip(); | ||
token.skip() | ||
else | ||
break; | ||
break | ||
} | ||
var ast = { type: "array", elements: elements }; | ||
token.consume("]"); | ||
return ast; | ||
let ast = { type: "array", elements: elements } | ||
token.consume("]") | ||
return ast | ||
}, | ||
/* parse primary type specification */ | ||
parse_primary: function (token) { | ||
var primary = token.peek(); | ||
parse_primary (token) { | ||
let primary = token.peek() | ||
if (!primary.match(/^(?:null|undefined|boolean|number|string|function|object)$/)) | ||
throw new Error("validate: parse error: invalid primary type \"" + primary + "\""); | ||
token.skip(); | ||
return { type: "primary", name: primary }; | ||
throw new Error(`validate: parse error: invalid primary type "${primary}"`) | ||
token.skip() | ||
return { type: "primary", name: primary } | ||
}, | ||
/* parse special "any" type specification */ | ||
parse_any: function (token) { | ||
var any = token.peek(); | ||
parse_any (token) { | ||
let any = token.peek() | ||
if (any !== "any") | ||
throw new Error("validate: parse error: invalid any type \"" + any + "\""); | ||
token.skip(); | ||
return { type: "any" }; | ||
throw new Error(`validate: parse error: invalid any type "${any}"`) | ||
token.skip() | ||
return { type: "any" } | ||
}, | ||
/* parse JavaScript class specification */ | ||
parse_class: function (token) { | ||
var clazz = token.peek(); | ||
parse_class (token) { | ||
let clazz = token.peek() | ||
if (!clazz.match(/^[_a-zA-Z$][_a-zA-Z$0-9]*(?:\.[_a-zA-Z$][_a-zA-Z$0-9]*)*$/)) | ||
throw new Error("validate: parse error: invalid class type \"" + clazz + "\""); | ||
token.skip(); | ||
return { type: "class", name: clazz }; | ||
throw new Error(`validate: parse error: invalid class type "${clazz}"`) | ||
token.skip() | ||
return { type: "class", name: clazz } | ||
}, | ||
/* parse arity specification */ | ||
parse_arity: function (token, charset) { | ||
var arity = [ 1, 1 ]; | ||
parse_arity (token, charset) { | ||
let arity = [ 1, 1 ] | ||
if ( token.len >= 5 | ||
@@ -152,4 +152,4 @@ && token.peek(0) === "{" | ||
: parseInt(token.peek(3), 10)) | ||
]; | ||
token.skip(5); | ||
] | ||
token.skip(5) | ||
} | ||
@@ -160,22 +160,24 @@ else if ( | ||
&& charset.indexOf(token.peek()) >= 0) { | ||
var c = token.peek(); | ||
let c = token.peek() | ||
switch (c) { | ||
case "?": arity = [ 0, 1 ]; break; | ||
case "*": arity = [ 0, Number.MAX_VALUE ]; break; | ||
case "+": arity = [ 1, Number.MAX_VALUE ]; break; | ||
case "?": arity = [ 0, 1 ]; break | ||
case "*": arity = [ 0, Number.MAX_VALUE ]; break | ||
case "+": arity = [ 1, Number.MAX_VALUE ]; break | ||
} | ||
token.skip(); | ||
token.skip() | ||
} | ||
return arity; | ||
return arity | ||
}, | ||
/* parse hash key specification */ | ||
parse_key: function (token) { | ||
var key = token.peek(); | ||
parse_key (token) { | ||
var key = token.peek() | ||
if (!key.match(/^(?:[_a-zA-Z$][_a-zA-Z$0-9]*|@)$/)) | ||
throw new Error("validate: parse error: invalid key \"" + key + "\""); | ||
token.skip(); | ||
return key; | ||
throw new Error(`validate: parse error: invalid key "${key}"`) | ||
token.skip() | ||
return key | ||
} | ||
}; | ||
} | ||
export { validate_parse } | ||
@@ -25,46 +25,59 @@ /* | ||
import { registered } from "./ducky-2-registry-2-api.js" | ||
var validate_execute = { | ||
/* validate specification (top-level) */ | ||
exec_spec: function (value, node) { | ||
var valid = false; | ||
exec_spec (value, node, path, errors) { | ||
let valid = false | ||
if (node !== null) { | ||
switch (node.type) { | ||
case "not": valid = this.exec_not( value, node); break; | ||
case "or": valid = this.exec_or( value, node); break; | ||
case "hash": valid = this.exec_hash( value, node); break; | ||
case "array": valid = this.exec_array( value, node); break; | ||
case "primary": valid = this.exec_primary(value, node); break; | ||
case "class": valid = this.exec_class( value, node); break; | ||
case "any": valid = true; break; | ||
case "not": valid = this.exec_not( value, node, path, errors); break | ||
case "or": valid = this.exec_or( value, node, path, errors); break | ||
case "hash": valid = this.exec_hash( value, node, path, errors); break | ||
case "array": valid = this.exec_array( value, node, path, errors); break | ||
case "primary": valid = this.exec_primary(value, node, path, errors); break | ||
case "class": valid = this.exec_class( value, node, path, errors); break | ||
case "any": valid = true; break | ||
default: | ||
throw new Error("validate: invalid validation AST: " + | ||
"node has unknown type \"" + node.type + "\""); | ||
throw new Error(`validate: invalid validation AST: node has unknown type "${node.type}"`) | ||
} | ||
} | ||
return valid; | ||
return valid | ||
}, | ||
/* validate through boolean "not" operation */ | ||
exec_not: function (value, node) { | ||
return !this.exec_spec(value, node.op); /* RECURSION */ | ||
exec_not (value, node, path, errors) { | ||
let err = errors !== null ? [] : null | ||
let valid = this.exec_spec(value, node.op, path, err) /* RECURSION */ | ||
valid = !valid | ||
if (!valid && errors !== null) | ||
err.forEach((e) => errors.push(e)) | ||
return valid | ||
}, | ||
/* validate through boolean "or" operation */ | ||
exec_or: function (value, node) { | ||
return ( | ||
this.exec_spec(value, node.op1) /* RECURSION */ | ||
|| this.exec_spec(value, node.op2) /* RECURSION */ | ||
); | ||
exec_or (value, node, path, errors) { | ||
let [ err1, err2 ] = errors !== null ? [ [], [] ] : [ null, null ] | ||
let valid1 = this.exec_spec(value, node.op1, path, err1) /* RECURSION */ | ||
let valid2 = this.exec_spec(value, node.op2, path, err2) /* RECURSION */ | ||
let valid = valid1 || valid2 | ||
if (!valid && errors !== null) { | ||
err1.forEach((e) => errors.push(e)) | ||
err2.forEach((e) => errors.push(e)) | ||
} | ||
return valid | ||
}, | ||
/* validate hash type */ | ||
exec_hash: function (value, node) { | ||
var i, el; | ||
var valid = (typeof value === "object"); | ||
var fields = {}; | ||
var field; | ||
if (valid) { | ||
exec_hash (value, node, path, errors) { | ||
let i, el | ||
let valid = (typeof value === "object") | ||
let fields = {} | ||
let field | ||
if (!valid && errors !== null) | ||
errors.push(`mismatch at path "${path}": found type "${typeof value}", expected hash`) | ||
else if (valid) { | ||
/* pass 1: ensure that all mandatory fields exist | ||
and determine map of valid fields for pass 2 */ | ||
var hasAnyKeys = false; | ||
let hasAnyKeys = false | ||
for (field in value) { | ||
@@ -75,20 +88,28 @@ if ( !Object.hasOwnProperty.call(value, field) | ||
|| field === "prototype" ) | ||
continue; | ||
hasAnyKeys = true; | ||
break; | ||
continue | ||
hasAnyKeys = true | ||
break | ||
} | ||
for (i = 0; i < node.elements.length; i++) { | ||
el = node.elements[i]; | ||
fields[el.key] = el.element; | ||
el = node.elements[i] | ||
fields[el.key] = el.element | ||
if ( el.arity[0] > 0 | ||
&& ( (el.key === "@" && !hasAnyKeys) | ||
|| (el.key !== "@" && typeof value[el.key] === "undefined"))) { | ||
valid = false; | ||
break; | ||
valid = false | ||
if (errors !== null) { | ||
if (el.key === "@") | ||
errors.push(`mismatch at path "${path}": mandatory element under arbitrary key not found`) | ||
else | ||
errors.push(`mismatch at path "${path}": mandatory element under key "${el.key}" not found`) | ||
} | ||
else | ||
break | ||
} | ||
} | ||
} | ||
if (valid) { | ||
if (valid || errors !== null) { | ||
/* pass 2: ensure that no unknown fields exist | ||
and that all existing fields are valid */ | ||
let sep = (path !== "" ? "." : "") | ||
for (field in value) { | ||
@@ -99,59 +120,100 @@ if ( !Object.hasOwnProperty.call(value, field) | ||
|| field === "prototype" ) | ||
continue; | ||
continue | ||
if ( typeof fields[field] === "undefined" | ||
&& typeof fields["@"] === "undefined" | ||
&& errors !== null ) | ||
errors.push(`mismatch at path "${path}": element under key "${field}" unexpected`) | ||
if ( typeof fields[field] !== "undefined" | ||
&& this.exec_spec(value[field], fields[field])) /* RECURSION */ | ||
continue; | ||
&& this.exec_spec(value[field], fields[field], `${path}${sep}${field}`, errors)) /* RECURSION */ | ||
continue | ||
if ( typeof fields["@"] !== "undefined" | ||
&& this.exec_spec(value[field], fields["@"])) /* RECURSION */ | ||
continue; | ||
valid = false; | ||
break; | ||
&& this.exec_spec(value[field], fields["@"], `${path}${sep}${field}`, errors)) /* RECURSION */ | ||
continue | ||
valid = false | ||
if (errors === null) | ||
break | ||
} | ||
} | ||
return valid; | ||
return valid | ||
}, | ||
/* validate array type */ | ||
exec_array: function (value, node) { | ||
var i, el; | ||
var valid = (typeof value === "object" && value instanceof Array); | ||
if (valid) { | ||
var pos = 0; | ||
exec_array (value, node, path, errors) { | ||
let i, el | ||
let valid = (typeof value === "object" && value instanceof Array) | ||
if (!valid && errors !== null) | ||
errors.push(`mismatch at path "${path}": found type "${typeof value}", expected array`) | ||
else if (valid) { | ||
let pos = 0 | ||
let err = null | ||
/* iterate over all AST nodes */ | ||
for (i = 0; i < node.elements.length; i++) { | ||
el = node.elements[i]; | ||
var found = 0; | ||
el = node.elements[i] | ||
let found = 0 | ||
err = errors !== null ? [] : null | ||
/* iterate over remaining value elements | ||
- as long as the maximum value is not still reached and | ||
- as long as there are still elements available | ||
- as long as the elements are still valid */ | ||
while (found < el.arity[1] && pos < value.length) { | ||
if (!this.exec_spec(value[pos], el.element)) /* RECURSION */ | ||
break; | ||
found++; | ||
pos++; | ||
if (!this.exec_spec(value[pos], el.element, `${path}[${pos}]`, err)) /* RECURSION */ | ||
break | ||
found++ | ||
pos++ | ||
} | ||
if (found < el.arity[0]) { | ||
valid = false; | ||
break; | ||
if (errors !== null) | ||
errors.push(`mismatch at path "${path}[${pos}]": ` + | ||
`found only ${found} elements of array element type #${i}, ` + | ||
`expected at least ${el.arity[0]} elements`) | ||
valid = false | ||
break | ||
} | ||
} | ||
if (pos < value.length) | ||
valid = false; | ||
/* if last AST node matched not successfully, report its errors */ | ||
if (!valid && err !== null && err.length > 0) { | ||
if (errors !== null) | ||
err.forEach((e) => errors.push(e)) | ||
} | ||
/* in case more elements are available without matching nodes */ | ||
else if (pos < value.length) { | ||
if (errors !== null) | ||
errors.push(`mismatch at path "${path}": matched only ${pos} elements, ` + | ||
`but ${value.length} elements found`) | ||
valid = false | ||
} | ||
} | ||
return valid; | ||
return valid | ||
}, | ||
/* validate standard JavaScript type */ | ||
exec_primary: function (value, node) { | ||
return (node.name === "null" && value === null) || (typeof value === node.name); | ||
exec_primary (value, node, path, errors) { | ||
let valid = (node.name === "null" && value === null) || (typeof value === node.name) | ||
if (!valid && errors !== null) | ||
errors.push(`mismatch at path "${path}": found type "${typeof value}", expected primary type "${node.name}"`) | ||
return valid | ||
}, | ||
/* validate custom JavaScript type */ | ||
exec_class: function (value, node) { | ||
return ( | ||
exec_class (value, node, path, errors) { | ||
let type = registered(node.name) | ||
let valid = ( | ||
typeof value === "object" | ||
&& ( | ||
Object.prototype.toString.call(value) === "[object " + node.name + "]" | ||
|| ( typeof registry[node.name] === "function" | ||
&& value instanceof registry[node.name]) | ||
|| ( typeof type === "function" | ||
&& value instanceof type ) | ||
) | ||
); | ||
) | ||
if (!valid && errors !== null) | ||
errors.push(`mismatch at path "${path}": found type "${typeof value}", expected class type "${node.name}"`) | ||
return valid | ||
} | ||
}; | ||
} | ||
export { validate_execute } | ||
@@ -25,54 +25,61 @@ /* | ||
import { validate_tokenize } from "./ducky-4-validate-1-tokenize.js" | ||
import { validate_parse } from "./ducky-4-validate-2-parse.js" | ||
import { validate_execute } from "./ducky-4-validate-3-execute.js" | ||
/* internal compile cache */ | ||
var validate_cache = {}; | ||
let validate_cache = {} | ||
/* API function: validate an arbitrary value against a validation DSL */ | ||
ducky.validate = function (value, spec) { | ||
var validate = function (value, spec, errors) { | ||
/* sanity check arguments */ | ||
if (arguments.length !== 2) | ||
throw new Error("validate: invalid number of arguments: \"" + | ||
arguments.length + "\" (exactly 2 expected)"); | ||
if (arguments.length < 2) | ||
throw new Error(`validate: invalid number of arguments: ${arguments.length} (minimum of 2 expected)`) | ||
else if (arguments.length > 3) | ||
throw new Error(`validate: invalid number of arguments: ${arguments.length} (maximum of 3 expected)`) | ||
if (typeof spec !== "string") | ||
throw new Error("validate: invalid specification argument: \"" + | ||
spec + "\" (string expected)"); | ||
throw new Error(`validate: invalid specification argument: "${spec}" (string expected)`) | ||
/* compile validation AST from specification | ||
or reuse cached pre-compiled validation AST */ | ||
var ast = validate_cache[spec]; | ||
var ast = validate_cache[spec] | ||
if (typeof ast === "undefined") { | ||
ast = ducky.validate.compile(spec); | ||
validate_cache[spec] = ast; | ||
ast = validate.compile(spec) | ||
validate_cache[spec] = ast | ||
} | ||
/* execute validation AST against the value */ | ||
return ducky.validate.execute(value, ast); | ||
}; | ||
return validate.execute(value, ast, errors) | ||
} | ||
ducky.validate.compile = function (spec) { | ||
validate.compile = function (spec) { | ||
/* sanity check arguments */ | ||
if (arguments.length !== 1) | ||
throw new Error("validate: invalid number of arguments: \"" + | ||
arguments.length + "\" (exactly 1 expected)"); | ||
throw new Error(`validate: invalid number of arguments: ${arguments.length} (exactly 1 expected)`) | ||
if (typeof spec !== "string") | ||
throw new Error("validate: invalid specification argument: \"" + | ||
spec + "\" (string expected)"); | ||
throw new Error(`validate: invalid specification argument: "${spec}" (string expected)`) | ||
/* tokenize the specification string into a token stream */ | ||
var token = validate_tokenize(spec); | ||
var token = validate_tokenize(spec) | ||
/* parse the token stream into an AST */ | ||
var ast = validate_parse.parse_spec(token); | ||
var ast = validate_parse.parse_spec(token) | ||
return ast; | ||
}; | ||
return ast | ||
} | ||
ducky.validate.execute = function (value, ast) { | ||
validate.execute = function (value, ast, errors) { | ||
/* sanity check arguments */ | ||
if (arguments.length !== 2) | ||
throw new Error("validate: invalid number of arguments: \"" + | ||
arguments.length + "\" (exactly 2 expected)"); | ||
if (arguments.length < 2) | ||
throw new Error(`validate: invalid number of arguments: ${arguments.length} (minimum of 2 expected)`) | ||
else if (arguments.length > 3) | ||
throw new Error(`validate: invalid number of arguments: ${arguments.length} (maximum of 3 expected)`) | ||
if (arguments.length < 3 || typeof errors === "undefined") | ||
errors = null | ||
/* execute validation AST against the value */ | ||
return validate_execute.exec_spec(value, ast); | ||
}; | ||
return validate_execute.exec_spec(value, ast, "", errors) | ||
} | ||
export { validate } | ||
@@ -25,6 +25,8 @@ /* | ||
import { validate } from "./ducky-4-validate-4-api.js" | ||
/* determine or at least guess whether we were called with | ||
positional or name-based parameters */ | ||
var params_is_name_based = function (args, spec) { | ||
var name_based = false; | ||
let params_is_name_based = function (args, spec) { | ||
let name_based = false | ||
if ( args.length === 1 | ||
@@ -34,40 +36,39 @@ && typeof args[0] === "object") { | ||
"foo({ foo: ..., bar: ...})" */ | ||
name_based = true; | ||
name_based = true | ||
/* ...but do not be mislead by a positional use like | ||
"foo(bar)" where "bar" is an arbitrary object! */ | ||
for (var name in args[0]) { | ||
for (let name in args[0]) { | ||
if (!Object.hasOwnProperty.call(args[0], name)) { | ||
if (typeof spec[name] === "undefined") | ||
name_based = false; | ||
name_based = false | ||
} | ||
} | ||
} | ||
return name_based; | ||
}; | ||
return name_based | ||
} | ||
/* common value validity checking */ | ||
var params_check_validity = function (func, param, value, valid, what) { | ||
let params_check_validity = function (func, param, value, valid, what) { | ||
if (typeof valid === "undefined") | ||
return; | ||
if (!ducky.validate(value, valid)) | ||
throw new Error(func + ": parameter \"" + param + "\" has " + | ||
what + " " + JSON.stringify(value) + ", which does not validate " + | ||
"against \"" + valid + "\""); | ||
}; | ||
return | ||
if (!validate(value, valid)) | ||
throw new Error(`${func}: parameter "${param}" has ` + | ||
`${what} ${JSON.stringify(value)}, which does not validate against "${valid}"`) | ||
} | ||
/* API function: flexible parameter handling */ | ||
ducky.params = function (func, args, spec) { | ||
var params = function (func, args, spec) { | ||
/* start with a fresh parameter object */ | ||
var params = {}; | ||
var params = {} | ||
/* handle parameter defaults */ | ||
var name; | ||
let name | ||
for (name in spec) { | ||
if (!Object.hasOwnProperty.call(spec, name)) | ||
continue; | ||
continue | ||
if (typeof spec[name].def !== "undefined") { | ||
if (typeof spec[name].valid !== "undefined") | ||
params_check_validity(func, name, spec[name].def, spec[name].valid, "default value"); | ||
params[name] = spec[name].def; | ||
params_check_validity(func, name, spec[name].def, spec[name].valid, "default value") | ||
params[name] = spec[name].def | ||
} | ||
@@ -78,3 +79,3 @@ } | ||
if (params_is_name_based(args, spec)) { | ||
args = args[0]; | ||
args = args[0] | ||
@@ -88,7 +89,7 @@ /* | ||
if (!Object.hasOwnProperty.call(args, name)) | ||
continue; | ||
continue | ||
if (typeof spec[name] === "undefined") | ||
throw new Error(func + ": unknown parameter \"" + name + "\""); | ||
params_check_validity(func, name, args[name], spec[name].valid, "value"); | ||
params[name] = args[name]; | ||
throw new Error(`${func}: unknown parameter "${name}"`) | ||
params_check_validity(func, name, args[name], spec[name].valid, "value") | ||
params[name] = args[name] | ||
} | ||
@@ -99,7 +100,7 @@ | ||
if (!Object.hasOwnProperty.call(spec, name)) | ||
continue; | ||
continue | ||
if ( typeof spec[name].req !== "undefined" | ||
&& spec[name].req | ||
&& typeof args[name] === "undefined") | ||
throw new Error(func + ": required parameter \"" + name + "\" missing"); | ||
throw new Error(`${func}: required parameter "${name}" missing`) | ||
} | ||
@@ -114,14 +115,14 @@ } | ||
and the mapping from parameter position to parameter name */ | ||
var positional = 0; | ||
var required = 0; | ||
var pos2name = {}; | ||
let positional = 0 | ||
let required = 0 | ||
let pos2name = {} | ||
for (name in spec) { | ||
if (!Object.hasOwnProperty.call(spec, name)) | ||
continue; | ||
continue | ||
if (typeof spec[name].pos !== "undefined") { | ||
pos2name[spec[name].pos] = name; | ||
pos2name[spec[name].pos] = name | ||
if (typeof spec[name].pos === "number") | ||
positional++; | ||
positional++ | ||
if (typeof spec[name].req !== "undefined" && spec[name].req) | ||
required++; | ||
required++ | ||
} | ||
@@ -132,20 +133,19 @@ } | ||
if (args.length < required) | ||
throw new Error(func + ": invalid number of arguments " + | ||
"(at least " + required + " required)"); | ||
throw new Error(`${func}: invalid number of arguments (at least ${required} required)`) | ||
/* pass 2: process parameters in sequence */ | ||
var i = 0; | ||
let i = 0 | ||
while (i < positional && i < args.length) { | ||
params_check_validity(func, pos2name[i], args[i], spec[pos2name[i]].valid, "value"); | ||
params[pos2name[i]] = args[i]; | ||
i++; | ||
params_check_validity(func, pos2name[i], args[i], spec[pos2name[i]].valid, "value") | ||
params[pos2name[i]] = args[i] | ||
i++ | ||
} | ||
if (i < args.length) { | ||
if (typeof pos2name["..."] === "undefined") | ||
throw new Error(func + ": too many arguments provided"); | ||
var rest = []; | ||
throw new Error(`${func}: too many arguments provided`) | ||
let rest = [] | ||
while (i < args.length) | ||
rest.push(args[i++]); | ||
params_check_validity(func, pos2name["..."], rest, spec[pos2name["..."]].valid, "value"); | ||
params[pos2name["..."]] = rest; | ||
rest.push(args[i++]) | ||
params_check_validity(func, pos2name["..."], rest, spec[pos2name["..."]].valid, "value") | ||
params[pos2name["..."]] = rest | ||
} | ||
@@ -155,4 +155,6 @@ } | ||
/* return prepared parameter object */ | ||
return params; | ||
}; | ||
return params | ||
} | ||
export { params } | ||
@@ -25,30 +25,13 @@ /*! | ||
/* Universal Module Definition (UMD) */ | ||
(function (root, name, factory) { | ||
/* global define: false */ | ||
/* global module: false */ | ||
if (typeof define === "function" && typeof define.amd !== "undefined") | ||
/* AMD environment */ | ||
define(name, function () { return factory(root); }); | ||
else if (typeof module === "object" && typeof module.exports === "object") | ||
/* CommonJS environment */ | ||
module.exports = factory(root); | ||
else | ||
/* Browser environment */ | ||
root[name] = factory(root); | ||
}(this, "ducky", function (/* root */) { | ||
var ducky = {}; | ||
include("ducky-0-version.js"); | ||
include("ducky-1-util.js"); | ||
include("ducky-2-registry.js"); | ||
include("ducky-3-select-1-compile.js"); | ||
include("ducky-3-select-2-execute.js"); | ||
include("ducky-3-select-3-api.js"); | ||
include("ducky-4-validate-1-tokenize.js"); | ||
include("ducky-4-validate-2-parse.js"); | ||
include("ducky-4-validate-3-execute.js"); | ||
include("ducky-4-validate-4-api.js"); | ||
include("ducky-5-params.js"); | ||
return ducky; | ||
})); | ||
import { version } from "./ducky-0-version.js" | ||
import { register, unregister } from "./ducky-2-registry-2-api.js" | ||
import { select } from "./ducky-3-select-3-api.js" | ||
import { validate } from "./ducky-4-validate-4-api.js" | ||
import { params } from "./ducky-5-params.js" | ||
export { version } | ||
export { register, unregister } | ||
export { select } | ||
export { validate } | ||
export { params } | ||
@@ -32,7 +32,7 @@ /* | ||
global.chai = require("chai") | ||
chai.use(require("chai-fuzzy")); | ||
chai.use(require("chai-fuzzy")) | ||
global.expect = global.chai.expect | ||
global.chai.config.includeStack = true | ||
var ducky = require("../lib/ducky.js") | ||
var ducky = require("../lib/ducky.node.js") | ||
var version = ducky.version | ||
@@ -48,5 +48,5 @@ | ||
expect(version.date ).to.be.a("number").least(20130101) | ||
}); | ||
}); | ||
}) | ||
}) | ||
}) | ||
@@ -32,7 +32,7 @@ /* | ||
global.chai = require("chai") | ||
chai.use(require("chai-fuzzy")); | ||
chai.use(require("chai-fuzzy")) | ||
global.expect = global.chai.expect | ||
global.chai.config.includeStack = true | ||
var ducky = require("../lib/ducky.js") | ||
var ducky = require("../lib/ducky.node.js") | ||
var select = ducky.select | ||
@@ -49,21 +49,21 @@ | ||
} | ||
}; | ||
} | ||
it("should correctly get value", function () { | ||
expect(select(obj, "")).to.be.equal(obj); | ||
expect(select(obj, " ")).to.be.equal(obj); | ||
expect(select(obj, "foo")).to.be.equal(obj.foo); | ||
expect(select(obj, "foo.bar")).to.be.equal(obj.foo.bar); | ||
expect(select(obj, "foo.bar.baz[0]")).to.be.equal(obj.foo.bar.baz[0]); | ||
expect(select(obj, "foo.bar.baz[4]")).to.be.equal(obj.foo.bar.baz[4]); | ||
expect(select(obj, "foo['bar'].baz[4]")).to.be.equal(obj.foo.bar.baz[4]); | ||
expect(select(obj, "['foo']['bar'][\"baz\"]['4']")).to.be.equal(obj.foo.bar.baz[4]); | ||
expect(select(obj, " [ 'foo' ] [ 'bar'] [ \"baz\" ][ '4' ] ")).to.be.equal(obj.foo.bar.baz[4]); | ||
}); | ||
expect(select(obj, "")).to.be.equal(obj) | ||
expect(select(obj, " ")).to.be.equal(obj) | ||
expect(select(obj, "foo")).to.be.equal(obj.foo) | ||
expect(select(obj, "foo.bar")).to.be.equal(obj.foo.bar) | ||
expect(select(obj, "foo.bar.baz[0]")).to.be.equal(obj.foo.bar.baz[0]) | ||
expect(select(obj, "foo.bar.baz[4]")).to.be.equal(obj.foo.bar.baz[4]) | ||
expect(select(obj, "foo['bar'].baz[4]")).to.be.equal(obj.foo.bar.baz[4]) | ||
expect(select(obj, "['foo']['bar'][\"baz\"]['4']")).to.be.equal(obj.foo.bar.baz[4]) | ||
expect(select(obj, " [ 'foo' ] [ 'bar'] [ \"baz\" ][ '4' ] ")).to.be.equal(obj.foo.bar.baz[4]) | ||
}) | ||
it("should correctly set value", function () { | ||
var old = obj.foo.bar.baz; | ||
expect(select(obj, "foo.bar.baz", { marker: 42 })).to.be.equal(old); | ||
expect(obj).to.be.like({ foo: { bar: { baz: { marker: 42 }, quux: 42 }}}); | ||
}); | ||
}); | ||
var old = obj.foo.bar.baz | ||
expect(select(obj, "foo.bar.baz", { marker: 42 })).to.be.equal(old) | ||
expect(obj).to.be.like({ foo: { bar: { baz: { marker: 42 }, quux: 42 }}}) | ||
}) | ||
}) | ||
}) | ||
@@ -32,7 +32,7 @@ /* | ||
global.chai = require("chai") | ||
chai.use(require("chai-fuzzy")); | ||
chai.use(require("chai-fuzzy")) | ||
global.expect = global.chai.expect | ||
global.chai.config.includeStack = true | ||
var ducky = require("../lib/ducky.js") | ||
var ducky = require("../lib/ducky.node.js") | ||
var validate = ducky.validate | ||
@@ -71,5 +71,5 @@ | ||
expect(validate(new Foo(), "Foo")).to.be.false | ||
ducky.register("Foo", Foo); | ||
ducky.register("Foo", Foo) | ||
var Quux = function () {} | ||
ducky.register("foo.bar.Quux", Quux); | ||
ducky.register("foo.bar.Quux", Quux) | ||
expect(validate(new Quux(), "foo.bar.Quux")).to.be.true | ||
@@ -125,4 +125,15 @@ }) | ||
}) | ||
it("should correctly report errors", function () { | ||
var errors = [] | ||
expect(validate(true, "string", errors)).to.be.false | ||
expect(errors).to.have.length(1) | ||
expect(errors[0]).to.match(/^mismatch at path "":.*/) | ||
errors = [] | ||
expect(validate([ 42, "foo" ], "[ number, boolean ]", errors)).to.be.false | ||
expect(errors).to.have.length(2) | ||
expect(errors[0]).to.match(/^mismatch at path "\[1\]":.*/) | ||
expect(errors[1]).to.match(/^mismatch at path "\[1\]":.*/) | ||
}) | ||
}) | ||
}) | ||
@@ -32,7 +32,7 @@ /* | ||
global.chai = require("chai") | ||
chai.use(require("chai-fuzzy")); | ||
chai.use(require("chai-fuzzy")) | ||
global.expect = global.chai.expect | ||
global.chai.config.includeStack = true | ||
var ducky = require("../lib/ducky.js") | ||
var ducky = require("../lib/ducky.node.js") | ||
@@ -48,10 +48,10 @@ describe("Ducky", function () { | ||
other: { pos: "...", def: [], valid: "[ number* ]" } | ||
}); | ||
expect(params.name).to.be.equal("bar"); | ||
expect(params.enabled).be.equal(true); | ||
expect(params.spec).be.like([ 42, 7 ]); | ||
expect(params.other).be.like([ 1, 2, 3 ]); | ||
}) | ||
expect(params.name).to.be.equal("bar") | ||
expect(params.enabled).be.equal(true) | ||
expect(params.spec).be.like([ 42, 7 ]) | ||
expect(params.other).be.like([ 1, 2, 3 ]) | ||
}; | ||
foo("bar", true, [ 42, 7 ], 1, 2, 3); | ||
foo({ name: "bar", enabled: true, spec: [ 42, 7 ], other: [ 1, 2, 3 ] }); | ||
foo("bar", true, [ 42, 7 ], 1, 2, 3) | ||
foo({ name: "bar", enabled: true, spec: [ 42, 7 ], other: [ 1, 2, 3 ] }) | ||
expect(function () { foo() }).to.throw(Error) | ||
@@ -58,0 +58,0 @@ expect(function () { foo({ quux: "quux" }) }).to.throw(Error) |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
261380
30
2976
18
1
4
1