Comparing version 0.8.3 to 0.8.4
424
conf.js
@@ -40,3 +40,3 @@ // | ||
const REQUIRED_RE = /^[A-Z]*$/ | ||
, RESERVED_NAMES_RE = /^(end|include)$/ | ||
, RESERVED_NAMES_RE = /^(end|include|define)$/ | ||
, PARAM_REQUIRED_RE = /^(struct|section|expression|custom)/ | ||
@@ -88,2 +88,3 @@ , BYTESIZE_RE = /^([\d\.]+)(b|kb|mb|gb)$|^([\d\.]+)$/ | ||
exports.createScript = function(code, filename) { | ||
@@ -96,2 +97,24 @@ var script; | ||
exports.validateValue = function(type, value, strict) { | ||
var fakefield; | ||
var param; | ||
if (typeof type == "undefined") { | ||
throw new Error("bad argument, `type`, expected type"); | ||
} | ||
if (typeof type == "function") { | ||
fakefield = { type: "custom", param: type, strict: strict || false }; | ||
} else { | ||
fakefield = { type: type, strict: strict || false }; | ||
} | ||
try { | ||
return validateValue.call(null, fakefield, value); | ||
} catch (validationError) { | ||
throw new Error(validationError.message); | ||
} | ||
}; | ||
function Script(code, filename) { | ||
@@ -104,5 +127,7 @@ this.code = code | ||
this.isolated = false; | ||
this.globals = null; | ||
} | ||
Script.prototype.runInContext = function(context, env) { | ||
var globals = this.globals || {}; | ||
var sandbox; | ||
@@ -116,8 +141,9 @@ var runtime; | ||
runtime = new Runtime( this | ||
, context | ||
, this.workdir | ||
, this.paths | ||
, this.strict | ||
, this.isolated); | ||
runtime = new Runtime(this, | ||
context, | ||
this.workdir, | ||
this.paths, | ||
this.strict, | ||
this.isolated, | ||
globals); | ||
@@ -129,3 +155,3 @@ | ||
runScript(sandbox, this.code, this.filename); | ||
runScript(runtime, sandbox, this.code, this.filename); | ||
@@ -137,4 +163,115 @@ while ((result = runtime.pop()) && runtime.currentScope); | ||
// define command implementation | ||
function defineImpl(name, markup) { | ||
var sectionmarkup = {}; | ||
var runtime; | ||
var sandbox; | ||
var context; | ||
if (this instanceof Runtime) { | ||
runtime = this; | ||
} else { | ||
runtime = this[0]; | ||
sandbox = this[1]; | ||
} | ||
context = runtime.context; | ||
if (typeof context[name] !== "undefined") { | ||
throw new RuntimeError(runtime, "already defined"); | ||
} | ||
sectionmarkup[name] = markup; | ||
try { | ||
updateSection(context, sectionmarkup); | ||
} catch (updateError) { | ||
throw new RuntimeError(runtime, updateError.message); | ||
} | ||
if (sandbox) { | ||
defineProperties(runtime, context.props, sandbox.__props); | ||
} | ||
} | ||
function defineProperties(runtime, properties, target) { | ||
var namespace; | ||
var property; | ||
for (var name in properties) { | ||
property = properties[name]; | ||
if (typeof property == "object") { | ||
// Special case, namespace. We need to rebuild this | ||
// each time, to keep it updated. | ||
defineProperties(runtime, property, (namespace = {})); | ||
target[name] = namespace; | ||
} else { | ||
if (name in target == false) { | ||
property = property.bind(runtime); | ||
Object.defineProperty(target, name, { | ||
enumerable: true, get: property, set: property | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
// Include command implementation | ||
function includeImpl(filename) { | ||
var self = this; | ||
var env = typeof arguments[1] === "object" && arguments[1] || {}; | ||
var isolated = env && arguments[2] || arguments[1]; | ||
var resolvedPath; | ||
var script; | ||
var sandbox; | ||
var runtime; | ||
resolvedPath = this.resolvePath(filename, true); | ||
if (resolvedPath == null) { | ||
throw new RuntimeError(this, "Include not found '" + filename + "'"); | ||
} | ||
if (!Array.isArray(resolvedPath)) { | ||
resolvedPath = [resolvedPath]; | ||
} | ||
resolvedPath.forEach(function(p) { | ||
var msg; | ||
var code; | ||
try { | ||
code = require("fs").readFileSync(p, "utf8"); | ||
} catch (ioException) { | ||
throw new RuntimeError(self, msg); | ||
} | ||
script = new Script(code, basename(p)); | ||
script.workdir = dirname(p); | ||
runtime = new Runtime(script, | ||
self.context, | ||
script.workdir, | ||
self.paths, | ||
self.strict, | ||
self.isolated || isolated, | ||
self.globals); | ||
runtime.copy(self); | ||
sandbox = createSandbox(runtime, env || {}); | ||
runScript(runtime, sandbox, script.code, script.filename); | ||
self.copy(runtime); | ||
}); | ||
} | ||
// Runtime | ||
function Runtime(script, context, workdir, paths, strict, isolated) { | ||
function Runtime(script, context, workdir, paths, strict, isolated, globals) { | ||
this.script = script; | ||
@@ -146,2 +283,3 @@ this.context = context; | ||
this.isolated = isolated; | ||
this.globals = globals; | ||
@@ -158,2 +296,4 @@ this.resultStack = []; | ||
Runtime.prototype.define = defineImpl; | ||
// Copy a runtime variables from specified runtime | ||
@@ -291,4 +431,7 @@ Runtime.prototype.copy = function(runtime) { | ||
function RuntimeError(runtime, message) { | ||
function RuntimeError(runtime, message, label) { | ||
var self = this; | ||
var script; | ||
var stack; | ||
var obj; | ||
var tmp; | ||
@@ -317,94 +460,79 @@ | ||
} | ||
if (typeof label == "string") { | ||
this.label = label; | ||
} else { | ||
exports.RuntimeError = RuntimeError; | ||
require("util").inherits(RuntimeError, Error); | ||
if (!runtime || !runtime.script) { | ||
this.label = "unknown"; | ||
return; | ||
} | ||
RuntimeError.prototype.getSimpleMessage = function() { | ||
var script; | ||
var stack; | ||
var obj; | ||
script = this.runtime.script; | ||
stack = this._stack; | ||
if (!this.runtime || !this.runtime.script) { | ||
throw new Error("Uncompatible error object"); | ||
for (var i = 0; i < stack.length; i++) { | ||
obj = stack[i]; | ||
if (obj.getTypeName() == "[object global]" && | ||
obj.getEvalOrigin() == script.filename) { | ||
// Matched current script. | ||
this.label = [script.filename, | ||
obj.getLineNumber(), | ||
obj.getColumnNumber()].join(":"); | ||
} | ||
} | ||
} | ||
script = this.runtime.script; | ||
} | ||
stack = this._stack; | ||
exports.RuntimeError = RuntimeError; | ||
require("util").inherits(RuntimeError, Error); | ||
for (var i = 0; i < stack.length; i++) { | ||
obj = stack[i]; | ||
RuntimeError.prototype.getSimpleMessage = function() { | ||
return this.message + " (" + this.label + ")"; | ||
}; | ||
if (obj.getTypeName() == "[object global]" && | ||
obj.getEvalOrigin() == script.filename) { | ||
// Matched current script. | ||
// Kind of "hacky", but it works. | ||
RuntimeError.fromNativeError = function(runtime, error) { | ||
var stack; | ||
var re; | ||
var m; | ||
return this.message + " (" + script.filename + " " + obj.getLineNumber() | ||
+ ":" + obj.getColumnNumber() + ")"; | ||
if (typeof error == "object" && error.stack && runtime.script) { | ||
re = new RegExp("at\\s(" + runtime.script.filename + "\\:\\d+\\:\\d+)"); | ||
stack = error.stack.split("\n"); | ||
for (var i = 0, l = stack.length; i < l; i++) { | ||
if ((m = re.exec(stack[i]))) { | ||
return new RuntimeError(runtime, error.message || error.toString(), | ||
m[1]); | ||
} | ||
} | ||
return new RuntimeError(runtime, error.message || error.toString()); | ||
} else { | ||
return new RuntimeError(runtime, error); | ||
} | ||
return this.message; | ||
}; | ||
// Include command implementation | ||
function includeImpl(filename) { | ||
var self = this; | ||
var env = typeof arguments[1] === "object" && arguments[1] || {}; | ||
var isolated = env && arguments[2] || arguments[1]; | ||
var resolvedPath; | ||
var script; | ||
var sandbox; | ||
var runtime; | ||
resolvedPath = this.resolvePath(filename, true); | ||
if (resolvedPath == null) { | ||
throw new RuntimeError(this, "Include not found '" + filename + "'"); | ||
// Run a script in sandbox | ||
function runScript(runtime, sandbox, code, filename) { | ||
var wrapper = WRAPPER_TMPL.replace(/%s/g, code); | ||
var script = createScript(wrapper, filename); | ||
try { | ||
script.runInNewContext(sandbox); | ||
} catch (scriptError) { | ||
if (scriptError instanceof RuntimeError) { | ||
throw scriptError; | ||
} else if (typeof scriptError == "object" && | ||
typeof scriptError.stack == "string") { | ||
throw RuntimeError.fromNativeError(runtime, scriptError); | ||
} else { | ||
throw new RuntimeError(runtime, scriptError && | ||
scriptError.message || | ||
"unknown runtime error"); | ||
} | ||
} | ||
if (!Array.isArray(resolvedPath)) { | ||
resolvedPath = [resolvedPath]; | ||
} | ||
resolvedPath.forEach(function(p) { | ||
var msg; | ||
var code; | ||
try { | ||
code = require("fs").readFileSync(p, "utf8"); | ||
} catch (ioException) { | ||
throw new RuntimeError(self, msg); | ||
} | ||
script = new Script(code, basename(p)); | ||
script.workdir = dirname(p); | ||
runtime = new Runtime( script | ||
, self.context | ||
, script.workdir | ||
, self.paths | ||
, self.strict | ||
, self.isolated || isolated); | ||
runtime.copy(self); | ||
sandbox = createSandbox(runtime, env || {}); | ||
runScript(sandbox, script.code, script.filename); | ||
self.copy(runtime); | ||
}); | ||
} | ||
// Run a script in sandbox | ||
function runScript(sandbox, code, filename) { | ||
var script = createScript(WRAPPER_TMPL.replace(/%s/g, code), filename); | ||
script.runInNewContext(sandbox); | ||
} | ||
// Create a new sandbox from runtime | ||
@@ -415,10 +543,6 @@ // and optional enviroment variables | ||
var context = runtime.context; | ||
var globals = runtime.globals; | ||
var propfn; | ||
for (var name in context.props) { | ||
propfn = context.props[name].bind(runtime); | ||
Object.defineProperty(sandbox.__props, name, { | ||
get: propfn, set: propfn | ||
}); | ||
} | ||
defineProperties(runtime, context.props, sandbox.__props); | ||
@@ -440,5 +564,6 @@ Object.defineProperty(sandbox.__props, "end", { | ||
sandbox.include = includeImpl.bind(runtime); | ||
sandbox.define = defineImpl.bind([runtime, sandbox]); | ||
for (var name in env) { | ||
if (RESERVED_NAMES_RE(name)) { | ||
if (RESERVED_NAMES_RE.test(name)) { | ||
throw new Error("Environment property '" + name + "' is reserved."); | ||
@@ -449,2 +574,9 @@ } | ||
for (var key in globals) { | ||
if (RESERVED_NAMES_RE.test(key)) { | ||
throw new Error("Global property '" + key + "' is reserved."); | ||
} | ||
sandbox[key] = globals[key]; | ||
} | ||
return sandbox; | ||
@@ -454,2 +586,17 @@ } | ||
function getNamespace(target, expr) { | ||
var splitted = expr.split("."); | ||
var name = splitted.shift(); | ||
var obj; | ||
if (name in target) { | ||
obj = target[name]; | ||
} else { | ||
obj = target[name] = {}; | ||
} | ||
return splitted.length ? getNamespace(obj, splitted.join(".")) : obj; | ||
} | ||
// Update section with specified markup | ||
@@ -463,2 +610,3 @@ function updateSection(scope, markup) { | ||
var subscope; | ||
var ns; | ||
@@ -471,3 +619,3 @@ keys = Object.keys(markup); | ||
if (RESERVED_NAMES_RE(name)) { | ||
if (RESERVED_NAMES_RE.test(name)) { | ||
throw new Error("Name '" + name + "' is reserved."); | ||
@@ -498,3 +646,3 @@ } | ||
if (PARAM_REQUIRED_RE(field.type) && !field.param) { | ||
if (PARAM_REQUIRED_RE.test(field.type) && !field.param) { | ||
throw new Error("Property '" + name + "', `param` must be set for field."); | ||
@@ -513,3 +661,3 @@ } | ||
if (field.required) { | ||
scope.requirements[name] = true; | ||
scope.requirements[name] = field; | ||
} | ||
@@ -545,4 +693,6 @@ | ||
if (!root.props[name]) { | ||
root.props[name] = createProp(name); | ||
ns = field.ns ? getNamespace(root.props, field.ns) : root.props; | ||
if (!ns[name]) { | ||
ns[name] = createProp(name, field.ns); | ||
} | ||
@@ -556,13 +706,16 @@ | ||
// Create a new property wrapper | ||
function createProp(name) { | ||
function createProp(name, ns) { | ||
return function(value) { | ||
var args = slice.call(arguments); | ||
var scope = this.currentScope; | ||
var fullname; | ||
var field; | ||
var prop; | ||
fullname = ns ? [ns, name].join(".") : name; | ||
if (!scope || !scope.fields || | ||
!(field = scope.fields[name])) { | ||
throw new Error( "Property '" + name + "' cannot be defined " | ||
+ "in section '" + scope.name + "'"); | ||
throw new Error( "Property '" + fullname + "' cannot be defined " | ||
+ "in section '" + (scope && scope.name || "<null>") + "'"); | ||
} | ||
@@ -576,3 +729,3 @@ | ||
if (!(prop = field.fields[field.property])) { | ||
throw new Error( "Property '" + name + "', field not found: " | ||
throw new Error( "Property '" + fullname + "', field not found: " | ||
+ field.property); | ||
@@ -607,2 +760,6 @@ } | ||
if (typeof field.ns == "string") { | ||
result = getNamespace(result, field.ns); | ||
} | ||
if (field.list) { | ||
@@ -626,3 +783,3 @@ | ||
} else if (name in result) { | ||
} else if (field.overridable == false && (name in result)) { | ||
throw new RuntimeError(this, "Expected one value only"); | ||
@@ -642,2 +799,3 @@ } else { | ||
var self = this; | ||
var target; | ||
var defvalue; | ||
@@ -649,2 +807,6 @@ var keys; | ||
if (!scope) { | ||
throw new RuntimeError(this, "bad syntax, unexpected `end`"); | ||
} | ||
keys = Object.keys(scope.fields); | ||
@@ -656,11 +818,12 @@ length = keys.length; | ||
field = scope.fields[key]; | ||
target = field.ns ? getNamespace(result, field.ns) : result; | ||
if (!(key in result)) { | ||
if (!(key in target)) { | ||
if (key in scope.defaults) { | ||
if (field.list) { | ||
result[key] = []; | ||
target[key] = []; | ||
if (Array.isArray(scope.defaults[key])) { | ||
scope.defaults[key].forEach(function(val) { | ||
var validated = validateValue.call(self, field, val); | ||
result[key].push(val); | ||
target[key].push(val); | ||
index && (index[index.length] = val); | ||
@@ -670,10 +833,10 @@ }); | ||
defvalue = scope.defaults[key]; | ||
result[key].push(validateValue.call(self, field, defvalue)); | ||
target[key].push(validateValue.call(self, field, defvalue)); | ||
} | ||
} else { | ||
defvalue = scope.defaults[key]; | ||
result[key] = validateValue.call(self, field, defvalue); | ||
target[key] = validateValue.call(self, field, defvalue); | ||
} | ||
} else if (field.list && !field.required) { | ||
result[key] = []; | ||
target[key] = []; | ||
} | ||
@@ -688,3 +851,5 @@ } | ||
key = keys[length]; | ||
if (!(key in result)) { | ||
field = scope.requirements[key]; | ||
target = field.ns ? getNamespace(result, field.ns) : result; | ||
if (!(key in target)) { | ||
throw new RuntimeError(self, "Required property '" + key + "'" | ||
@@ -717,2 +882,3 @@ + "was not set."); | ||
var required = false; | ||
var overridable = false; | ||
var list = false; | ||
@@ -727,2 +893,3 @@ var value = NIL; | ||
var onexit = null; | ||
var ns = null; | ||
var ctor; | ||
@@ -738,3 +905,3 @@ var i; | ||
type = expr[0].toLowerCase(); | ||
required = REQUIRED_RE(expr[0]) && true || false; | ||
required = REQUIRED_RE.test(expr[0]) && true || false; | ||
} else if (expr[0].constructor === RegExp) { | ||
@@ -758,3 +925,3 @@ type = "expression"; | ||
type = expr.toLowerCase(); | ||
required = REQUIRED_RE(expr) && true || false; | ||
required = REQUIRED_RE.test(expr) && true || false; | ||
} else if ((i = NATIVE_TYPE_MAPPING.indexOf(expr)) != -1) { | ||
@@ -768,3 +935,3 @@ type = NATIVE_TYPE_MAPPING[i + 1]; | ||
type = expr.type.toLowerCase(); | ||
required = REQUIRED_RE(expr.type) && true || false; | ||
required = REQUIRED_RE.test(expr.type) && true || false; | ||
} else if (expr.type && expr.type.constructor === RegExp) { | ||
@@ -788,3 +955,3 @@ type = "expression"; | ||
} | ||
required = expr.required || (REQUIRED_RE(expr) && true || false); | ||
required = expr.required || (REQUIRED_RE.test(expr) && true || false); | ||
// value = "value" in expr && expr.value || NIL; | ||
@@ -802,4 +969,6 @@ | ||
idxignore = expr.idxignore || false; | ||
overridable = expr.overridable || false; | ||
onenter = expr.onenter || null; | ||
onexit = expr.onexit || null; | ||
ns = expr.ns || null; | ||
} | ||
@@ -811,14 +980,16 @@ | ||
return { name: name | ||
, type: type | ||
, property: property | ||
, list: list | ||
, required: required | ||
, param: param | ||
, strict: strict | ||
, value: value | ||
, idxignore: idxignore | ||
, index: index | ||
, onenter: onenter | ||
, onexit: onexit }; | ||
return {name: name, | ||
type: type, | ||
property: property, | ||
list: list, | ||
required: required, | ||
param: param, | ||
strict: strict, | ||
value: value, | ||
idxignore: idxignore, | ||
overridable: overridable, | ||
index: index, | ||
ns: ns, | ||
onenter: onenter, | ||
onexit: onexit }; | ||
} | ||
@@ -832,3 +1003,2 @@ | ||
switch (field.type) { | ||
@@ -973,3 +1143,3 @@ | ||
function getBytes(expr) { | ||
var m = BYTESIZE_RE(expr); | ||
var m = BYTESIZE_RE.exec(expr); | ||
@@ -993,3 +1163,3 @@ if (!m) { | ||
function getMilliseconds(expr) { | ||
var m = TIMEUNIT_RE(expr); | ||
var m = TIMEUNIT_RE.exec(expr); | ||
@@ -996,0 +1166,0 @@ if (!m) { |
{ | ||
"name": "conf", | ||
"version": "0.8.3", | ||
"version": "0.8.4", | ||
"description": "Config library for Nodejs", | ||
@@ -5,0 +5,0 @@ "keywords": ["dsl", "conf", "config", "script", "general"], |
43450
994