This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
7.7 KiB
267 lines
7.7 KiB
'use strict'; |
|
|
|
|
|
module.exports = { |
|
copy: copy, |
|
checkDataType: checkDataType, |
|
checkDataTypes: checkDataTypes, |
|
coerceToTypes: coerceToTypes, |
|
toHash: toHash, |
|
getProperty: getProperty, |
|
escapeQuotes: escapeQuotes, |
|
equal: require('fast-deep-equal'), |
|
ucs2length: require('./ucs2length'), |
|
varOccurences: varOccurences, |
|
varReplace: varReplace, |
|
cleanUpCode: cleanUpCode, |
|
finalCleanUpCode: finalCleanUpCode, |
|
schemaHasRules: schemaHasRules, |
|
schemaHasRulesExcept: schemaHasRulesExcept, |
|
toQuotedString: toQuotedString, |
|
getPathExpr: getPathExpr, |
|
getPath: getPath, |
|
getData: getData, |
|
unescapeFragment: unescapeFragment, |
|
unescapeJsonPointer: unescapeJsonPointer, |
|
escapeFragment: escapeFragment, |
|
escapeJsonPointer: escapeJsonPointer |
|
}; |
|
|
|
|
|
function copy(o, to) { |
|
to = to || {}; |
|
for (var key in o) to[key] = o[key]; |
|
return to; |
|
} |
|
|
|
|
|
function checkDataType(dataType, data, negate) { |
|
var EQUAL = negate ? ' !== ' : ' === ' |
|
, AND = negate ? ' || ' : ' && ' |
|
, OK = negate ? '!' : '' |
|
, NOT = negate ? '' : '!'; |
|
switch (dataType) { |
|
case 'null': return data + EQUAL + 'null'; |
|
case 'array': return OK + 'Array.isArray(' + data + ')'; |
|
case 'object': return '(' + OK + data + AND + |
|
'typeof ' + data + EQUAL + '"object"' + AND + |
|
NOT + 'Array.isArray(' + data + '))'; |
|
case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + |
|
NOT + '(' + data + ' % 1)' + |
|
AND + data + EQUAL + data + ')'; |
|
default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; |
|
} |
|
} |
|
|
|
|
|
function checkDataTypes(dataTypes, data) { |
|
switch (dataTypes.length) { |
|
case 1: return checkDataType(dataTypes[0], data, true); |
|
default: |
|
var code = ''; |
|
var types = toHash(dataTypes); |
|
if (types.array && types.object) { |
|
code = types.null ? '(': '(!' + data + ' || '; |
|
code += 'typeof ' + data + ' !== "object")'; |
|
delete types.null; |
|
delete types.array; |
|
delete types.object; |
|
} |
|
if (types.number) delete types.integer; |
|
for (var t in types) |
|
code += (code ? ' && ' : '' ) + checkDataType(t, data, true); |
|
|
|
return code; |
|
} |
|
} |
|
|
|
|
|
var COERCE_TO_TYPES = toHash([ 'string', 'number', 'integer', 'boolean', 'null' ]); |
|
function coerceToTypes(optionCoerceTypes, dataTypes) { |
|
if (Array.isArray(dataTypes)) { |
|
var types = []; |
|
for (var i=0; i<dataTypes.length; i++) { |
|
var t = dataTypes[i]; |
|
if (COERCE_TO_TYPES[t]) types[types.length] = t; |
|
else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t; |
|
} |
|
if (types.length) return types; |
|
} else if (COERCE_TO_TYPES[dataTypes]) { |
|
return [dataTypes]; |
|
} else if (optionCoerceTypes === 'array' && dataTypes === 'array') { |
|
return ['array']; |
|
} |
|
} |
|
|
|
|
|
function toHash(arr) { |
|
var hash = {}; |
|
for (var i=0; i<arr.length; i++) hash[arr[i]] = true; |
|
return hash; |
|
} |
|
|
|
|
|
var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; |
|
var SINGLE_QUOTE = /'|\\/g; |
|
function getProperty(key) { |
|
return typeof key == 'number' |
|
? '[' + key + ']' |
|
: IDENTIFIER.test(key) |
|
? '.' + key |
|
: "['" + escapeQuotes(key) + "']"; |
|
} |
|
|
|
|
|
function escapeQuotes(str) { |
|
return str.replace(SINGLE_QUOTE, '\\$&') |
|
.replace(/\n/g, '\\n') |
|
.replace(/\r/g, '\\r') |
|
.replace(/\f/g, '\\f') |
|
.replace(/\t/g, '\\t'); |
|
} |
|
|
|
|
|
function varOccurences(str, dataVar) { |
|
dataVar += '[^0-9]'; |
|
var matches = str.match(new RegExp(dataVar, 'g')); |
|
return matches ? matches.length : 0; |
|
} |
|
|
|
|
|
function varReplace(str, dataVar, expr) { |
|
dataVar += '([^0-9])'; |
|
expr = expr.replace(/\$/g, '$$$$'); |
|
return str.replace(new RegExp(dataVar, 'g'), expr + '$1'); |
|
} |
|
|
|
|
|
var EMPTY_ELSE = /else\s*{\s*}/g |
|
, EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g |
|
, EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g; |
|
function cleanUpCode(out) { |
|
return out.replace(EMPTY_ELSE, '') |
|
.replace(EMPTY_IF_NO_ELSE, '') |
|
.replace(EMPTY_IF_WITH_ELSE, 'if (!($1))'); |
|
} |
|
|
|
|
|
var ERRORS_REGEXP = /[^v.]errors/g |
|
, REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g |
|
, REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g |
|
, RETURN_VALID = 'return errors === 0;' |
|
, RETURN_TRUE = 'validate.errors = null; return true;' |
|
, RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/ |
|
, RETURN_DATA_ASYNC = 'return data;' |
|
, ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g |
|
, REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/; |
|
|
|
function finalCleanUpCode(out, async) { |
|
var matches = out.match(ERRORS_REGEXP); |
|
if (matches && matches.length == 2) { |
|
out = async |
|
? out.replace(REMOVE_ERRORS_ASYNC, '') |
|
.replace(RETURN_ASYNC, RETURN_DATA_ASYNC) |
|
: out.replace(REMOVE_ERRORS, '') |
|
.replace(RETURN_VALID, RETURN_TRUE); |
|
} |
|
|
|
matches = out.match(ROOTDATA_REGEXP); |
|
if (!matches || matches.length !== 3) return out; |
|
return out.replace(REMOVE_ROOTDATA, ''); |
|
} |
|
|
|
|
|
function schemaHasRules(schema, rules) { |
|
if (typeof schema == 'boolean') return !schema; |
|
for (var key in schema) if (rules[key]) return true; |
|
} |
|
|
|
|
|
function schemaHasRulesExcept(schema, rules, exceptKeyword) { |
|
if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not'; |
|
for (var key in schema) if (key != exceptKeyword && rules[key]) return true; |
|
} |
|
|
|
|
|
function toQuotedString(str) { |
|
return '\'' + escapeQuotes(str) + '\''; |
|
} |
|
|
|
|
|
function getPathExpr(currentPath, expr, jsonPointers, isNumber) { |
|
var path = jsonPointers // false by default |
|
? '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')') |
|
: (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\''); |
|
return joinPaths(currentPath, path); |
|
} |
|
|
|
|
|
function getPath(currentPath, prop, jsonPointers) { |
|
var path = jsonPointers // false by default |
|
? toQuotedString('/' + escapeJsonPointer(prop)) |
|
: toQuotedString(getProperty(prop)); |
|
return joinPaths(currentPath, path); |
|
} |
|
|
|
|
|
var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; |
|
var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; |
|
function getData($data, lvl, paths) { |
|
var up, jsonPointer, data, matches; |
|
if ($data === '') return 'rootData'; |
|
if ($data[0] == '/') { |
|
if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data); |
|
jsonPointer = $data; |
|
data = 'rootData'; |
|
} else { |
|
matches = $data.match(RELATIVE_JSON_POINTER); |
|
if (!matches) throw new Error('Invalid JSON-pointer: ' + $data); |
|
up = +matches[1]; |
|
jsonPointer = matches[2]; |
|
if (jsonPointer == '#') { |
|
if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); |
|
return paths[lvl - up]; |
|
} |
|
|
|
if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); |
|
data = 'data' + ((lvl - up) || ''); |
|
if (!jsonPointer) return data; |
|
} |
|
|
|
var expr = data; |
|
var segments = jsonPointer.split('/'); |
|
for (var i=0; i<segments.length; i++) { |
|
var segment = segments[i]; |
|
if (segment) { |
|
data += getProperty(unescapeJsonPointer(segment)); |
|
expr += ' && ' + data; |
|
} |
|
} |
|
return expr; |
|
} |
|
|
|
|
|
function joinPaths (a, b) { |
|
if (a == '""') return b; |
|
return (a + ' + ' + b).replace(/' \+ '/g, ''); |
|
} |
|
|
|
|
|
function unescapeFragment(str) { |
|
return unescapeJsonPointer(decodeURIComponent(str)); |
|
} |
|
|
|
|
|
function escapeFragment(str) { |
|
return encodeURIComponent(escapeJsonPointer(str)); |
|
} |
|
|
|
|
|
function escapeJsonPointer(str) { |
|
return str.replace(/~/g, '~0').replace(/\//g, '~1'); |
|
} |
|
|
|
|
|
function unescapeJsonPointer(str) { |
|
return str.replace(/~1/g, '/').replace(/~0/g, '~'); |
|
}
|
|
|