diff options
Diffstat (limited to 'js/codeq')
-rw-r--r-- | js/codeq/core.js | 298 |
1 files changed, 266 insertions, 32 deletions
diff --git a/js/codeq/core.js b/js/codeq/core.js index dd417d4..564aec3 100644 --- a/js/codeq/core.js +++ b/js/codeq/core.js @@ -1,33 +1,267 @@ -window.siteDefinition = { logLevel: 'debug' }; // for debug purposes - -window.codeq = { - /** - * XML namespaces. - */ - ns: { - svg: 'http://www.w3.org/2000/svg' - }, - - noOnlineStatus: !('onLine' in navigator), - - /** - * REST API URL prefix. - */ - urlPrefix: '/svc/', - - /** - * Returns the number of Unicode code points in the given string. - * - * @param s {string} - * @returns {number} - */ - codePointCount: function (s) { - var n = 0, i, code; - for (i = s.length - 1; i >= 0; i--) { - code = s.charCodeAt(i); - if ((code >= 0xd800) && (code < 0xe000)) i++; - n++; +(function () { + window.siteDefinition = {logLevel: 'debug'}; // for debug purposes + + // ================================================================================ + // JSON-ization (stringification) function + // ================================================================================ + + var jsonize = JSON && JSON.stringify || function () { + var t, buffer, i, isFirst; + if (null === obj) return 'null'; + t = typeof obj; + if (t === 'string') { + return '"' + obj.replace(regexpBackslash, '\\\\').replace(regexpQuote, '\\"') + '"'; + } + if (t === 'number') { + if (isFinite(obj)) return obj.toString(); + throw new Error('Cannot jsonize a non-finite number: ' + obj.toString()); + } + if (t === 'boolean') { + if (obj) return 'true'; + return 'false'; + } + if (t === 'object') { + if (obj instanceof String) return jsonize(obj.valueOf()); + if (obj instanceof Number) return jsonize(obj.valueOf()); + if (obj instanceof Boolean) return jsonize(obj.valueOf()); + if (obj instanceof Array) { + buffer = [ '[' ]; + isFirst = true; + for (i = 0; i < obj.length; i++) { + if (isFirst) isFirst = false; + else buffer.push(','); + buffer.push(jsonize(obj[i])); + } + buffer.push(']'); + return buffer.join(''); + } + buffer = [ '{' ]; + isFirst = true; + for (i in obj) { + if (isFirst) isFirst = false; + else buffer.push(','); + buffer.push(jsonize(i), ':', jsonize(obj[i])); + } + buffer.push('}'); + return buffer.join(''); + } + throw new Error('Cannot jsonize ' + t); + }; + + // ================================================================================ + // The log module: contains methods for logging, sending logs to the server + // ================================================================================ + + var log = (function () { + var logServiceUrl = window.siteDefinition && (typeof siteDefinition.logService === 'string') && siteDefinition.logService, + assembleOutput = function (stuff, e) { + var lines = [ stuff ]; + if (e && e.stack) lines.push(e.stack); + return lines.join('\n'); + }, + console = window.console || {}, + levelPrefix = {'debug': 'DEBUG: ', 'info': 'INFO: ', 'warn': 'WARN: ', 'error': 'ERROR: '}, + levelSeverity = {'debug': 1, 'info': 2, 'warn': 3, 'error': 4, 'off': 5}, + severityThreshold = window.siteDefinition && levelSeverity[siteDefinition.logLevel] || levelSeverity['off'], + logToConsole = function (level) { + var prefix = levelPrefix[level]; + if (levelSeverity[level] < severityThreshold) return function () {}; // level is below threshold, ignore + if (typeof console[level] === 'function') return function (output) {console[level](output);}; + if (typeof console.log === 'function') return function (output) {console.log(prefix + output);}; + if (typeof dump === 'function') return function (output) {dump(prefix + output);}; + return function () {}; // no way to log in browser + }, + consoleLogger = { + 'debug': logToConsole('debug'), + 'info': logToConsole('info'), + 'warn': logToConsole('warn'), + 'error': logToConsole('error') + }, + logger = {}, + level; + + if (logServiceUrl) { + // log to the service and to the console + var logs = []; // buffered logs + for (level in levelPrefix) { + if (!levelPrefix.hasOwnProperty(level)) continue; + logger[level] = (function (levelLogger, level) { + if (levelSeverity[level] < severityThreshold) return function () {}; // level is below threshold, ignore + return function (stuff, e) { + var output = assembleOutput(stuff, e); + levelLogger(output); + logs.push({ + 't': Date.now(), + 'l': level, + 'm': output + }); + }; + })(consoleLogger[level], level); + } + setInterval(function () { + // each second send any logs to the server + var copyOfLogs; + if (logs.length < 1) return; + copyOfLogs = jsonize({'logs': logs}); + logs = []; + $.ajax({ + contentType: 'application/json', + dataType: 'application/json', + type: 'POST', + url: logServiceUrl, + data: copyOfLogs, + error: function (jqXHR, textStatus, errorThrown) { + consoleLogger.error(assembleOutput('Posting of logs to ' + logServiceUrl + ' failed: ' + textStatus, errorThrown)); + }, + success: function (data, textStatus, jqXHR) {} + }); + }, 1000); + } + else { + // log only to the console + for (level in levelPrefix) { + if (!levelPrefix.hasOwnProperty(level)) continue; + logger[level] = (function (levelLogger) { + if (levelSeverity[level] < severityThreshold) return function () {}; // level is below threshold, ignore + return function (stuff, e) { levelLogger(assembleOutput(stuff, e)); }; + })(consoleLogger[level]); + } + } + + return logger; + })(); + + // ================================================================================ + // The core CodeQ module + // ================================================================================ + + var jqDisabled = $('#disabled'), // used in codeq.wait() + waitCssEnter = {'cursor': 'wait', 'display': ''}; + + // regular expressions for the templating function, etc. + var regexpQuote = new RegExp('"', 'g'), + regexpBackslash = new RegExp('\\\\', 'g'), + regexpWhiteSpaceStart = new RegExp('^[ \r\n\t]+'), + regexpWhiteSpaceEnd = new RegExp('[ \r\n\t]+$'), + regexpWhiteSpaceNonPrintable = new RegExp('[\r\n\t]', 'g'), + regexpWhiteSpaceBeforeTag = new RegExp('[ \r\n\t]+(?=<)', 'g'), + regexpWhiteSpaceAfterTag = new RegExp('>[ \r\n\t]+', 'g'), + regexpWhiteSpace = new RegExp('[ \\r\\n\\t]+'), + regexpWhiteSpaceTrim = new RegExp('^[ \\t\\r\\n]*(.*[^ \\t\\r\\n])[ \\t\\r\\n]*$', 'm'); + + // convert a string into its definition (javascript literal) + var stringToDef = function (str) { + return str.replace(regexpBackslash, '\\\\').replace(regexpQuote, '\\"').replace(regexpWhiteSpaceNonPrintable, ' '); + }; + + // given a HTML source, remove whitespace among tags + var cleanHtml = function (html) { + // JavaScript doesn't support lookbehind, so we use the split-join trick + return html.replace(regexpWhiteSpaceBeforeTag, '').split(regexpWhiteSpaceAfterTag).join('>'); + }; + + window.codeq = { + 'jsonize': jsonize, + + 'log': log, + + /** + * Returns the number of Unicode code points in the given string. + * + * @param s {string} + * @returns {number} + */ + 'codePointCount': function (s) { + var n = 0, i, code; + if (typeof s !== 'string') { + code = 'codePointCount(): argument not a string: type = ' + typeof s + ', is null = ' + (s === null); + if ((typeof s === 'object') && (s !== null) && s.constructor) code += ', constructor = ' + s.constructor.name; + codeq.log.error(code); + return 0; + } + for (i = s.length - 1; i >= 0; i--) { + try { + code = s.charCodeAt(i); + } + catch (e) { + codeq.log.error('Invocation of charCodeAt() failed at iteration #' + i + ': ' + e, e); + return 0; + } + if ((code >= 0xd800) && (code < 0xe000)) i++; + n++; + } + return n; + }, + + 'wait': function (promise) { + jqDisabled.css(waitCssEnter); + return promise.fin(function () { + jqDisabled.css('display', 'none'); + }); + }, + + 'templator': function (str, templateName) { + var f, parts, i, subparts, subpart, + src = [ 'var _result = [], echo = function (s) { _result.push(s); };\n' ], + componentName, atoms, j, atom, debugPrefix, s, r; + + if (!templateName) templateName = 'unknown template'; + debugPrefix = '[' + templateName + ']'; + + // remove comments + parts = str.split('[%--'); // break on start-of-comment + atoms = [ parts[0] ]; // first part is not a comment + for (i = 1; i < parts.length; i++) { // iterate over start-of-comments + atom = parts[i].split('--%]'); // break on end-of-comment + if (atom.length > 1) { // if end-of-comment was encountered + atoms.push(atom[1]); // add whatever is trailing it + for (j = 2; j < atom.length; j++) { // re-add even dangling end-of-comments + atoms.push('--%]'); + atoms.push(atom[j]); + } + } + } + + // start processing + parts = atoms.join('').split('[%'); + + if (parts[0].length > 0) src.push('_result.push("', stringToDef(cleanHtml(parts[0])), '");\n'); // the first part that doesn't begin with '[%' + for (i = 1; i < parts.length; i++) { // for every part that begins with '[%' + if (parts[i].slice(0, 2) === '--') { // a comment start + subparts = parts[i].split('--%]'); // split at comment end + } + else { // a start of a statement or of a value reference + subparts = parts[i].split('%]'); // there should be only one terminating '%]', find it + subpart = subparts[0].replace(regexpWhiteSpaceStart, '').replace(regexpWhiteSpaceEnd, ''); // trim the white space + if (subpart.length > 0) { + if (subpart[0] === '=') { // a value reference + s = subpart.slice(1); + r = regexpWhiteSpaceTrim.exec(s); + src.push('_result.push(this.', r && r[1] || s, ');\n'); + } + else { // a statement + src.push(subpart, '\n'); + } + } + } + if ((subparts.length > 1) && (subparts[1].length > 0)) { // there's a trailing text + src.push('_result.push("', stringToDef(cleanHtml(subparts[1])), '");\n'); + } + } + src.push('return _result.join("");'); +// if (_internalDoLog_) codeq.log.debug('createTemplate(): ' + debugPrefix + ' creating templating function:\nfunction(_components_) {\n' + src.join('') + '\n}\n=== Created from template: ===\n' + str); + try { + f = new Function(src.join('')); // create a function based on the given template + } + catch (e) { + codeq.log.error('createTemplate(): ' + debugPrefix + ' Failed to instantiate template function: ' + e + '\nfunction() {\n' + src.join('') + '\n}\n=== Created from template: ===\n' + str, e); + throw e; + } + + return function (args) { + if ((typeof args !== 'object') || (args === null)) args = {}; + return f.apply(args); + }; } - return n; - } -}; + }; +})(); |