summaryrefslogtreecommitdiff
path: root/js/codeq/core.js
diff options
context:
space:
mode:
authorAleš Smodiš <aless@guru.si>2015-10-01 17:25:40 +0200
committerAleš Smodiš <aless@guru.si>2015-10-01 17:25:40 +0200
commit439406a5100b24b90bbec0c70c6338101abe735d (patch)
tree85fa9010599f0216d0d777776b70c833b569504d /js/codeq/core.js
parent026a0d5fb64d8c0ee5eb45e545b1ac294ac0e85c (diff)
Use codeq.templator() for hints. Support for images.
Diffstat (limited to 'js/codeq/core.js')
-rw-r--r--js/codeq/core.js172
1 files changed, 163 insertions, 9 deletions
diff --git a/js/codeq/core.js b/js/codeq/core.js
index 29b497c..4b03bfb 100644
--- a/js/codeq/core.js
+++ b/js/codeq/core.js
@@ -163,8 +163,10 @@
return html.replace(regexpWhiteSpaceBeforeTag, '').split(regexpWhiteSpaceAfterTag).join('>');
};
- var lang = 'en'; // this is overridden in the boot sequence below, if the browser uses a supported language
+ var lang = 'en', // this is overridden in the boot sequence below, if the browser uses a supported language
+ resources = {}; // resource tree, loaded from data/resources.json in the boot sequence
+ // event dispatch
var eventListeners = {}, // keyed by event name, value is an array of listeners
asyncTimer = null,
queuedEvents = [],
@@ -194,6 +196,120 @@
}, 0);
};
+ var tokenize = function (line) {
+ var result = [],
+ N = line.length,
+ i = 0, // character index into line
+ storedAtom = null, // the previous atom, to decide whether to store a string or an object {key:'', value:''}
+ atom = [], // currently parsed content
+ isKeyValue = false,
+ storeAtom = function () {
+ if (atom.length == 0) return;
+ if (storedAtom === null) storedAtom = atom.join('');
+ else if (isKeyValue) {
+ result.push({'key': storedAtom, 'value': atom.join('')});
+ storedAtom = null;
+ isKeyValue = false;
+ }
+ else {
+ result.push(storedAtom);
+ storedAtom = atom.join('');
+ }
+ atom.length = 0;
+ },
+ c, q;
+
+ while (i < N) {
+ c = line[i++];
+ switch (c) {
+ // end-of-atom characters
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ storeAtom();
+ break;
+
+ // escape character
+ case '\\':
+ if (i < N) atom.push(line[i++]);
+ break;
+
+ // key-value delimiter character
+ case '=':
+ if (storedAtom) {
+ if (isKeyValue) atom.push(c);
+ else if (atom.length == 0) isKeyValue = true; // must have been a whitespace before "="
+ else {
+ storeAtom();
+ isKeyValue = true;
+ }
+ }
+ else atom.push(c); // "=" is delimiter only before the value, not before the key
+ break;
+
+ // quoted string
+ case '"':
+ case "'":
+ stringConsume:
+ while (i < N) {
+ q = line[i++];
+ switch (q) {
+ case '\\':
+ if (i < N) atom.push(line[i++]);
+ break;
+ case c:
+ break stringConsume;
+ default:
+ atom.push(q);
+ break;
+ }
+ }
+ break;
+
+ default:
+ atom.push(c);
+ break;
+ }
+ }
+ // purge anything in cache
+ storeAtom();
+ if (storedAtom !== null) result.push(storedAtom);
+ return result;
+ };
+
+ var directiveHandlers = {
+ 'resource': function (code, tokens, templatePath) {
+ var resourceName = tokens[1],
+ traversedPath = ['data'], // top-level directory
+ branch = resources,
+ candidate = null,
+ i, fragment;
+ if (!resourceName) {
+ codeq.log.error('No resource name provided; path: "' + templatePath.join('/') + '"');
+ code.push('_result.push("data/broken.png");\n');
+ return;
+ }
+ if (branch[resourceName]) candidate = 'data/' + resourceName; // top-level match
+ for (i = 0; i < templatePath.length; i++) {
+ fragment = templatePath[i];
+ branch = branch[fragment];
+ if (!branch) {
+ codeq.log.error('Resource sub-branch ' + fragment + ' does not exist; resource: "' + resourceName + '", path: "' + templatePath.join('/') + '"');
+ code.push('_result.push("data/broken.png");\n');
+ break;
+ }
+ traversedPath.push(fragment);
+ if (branch[resourceName]) candidate = traversedPath.join('/') + '/' + resourceName;
+ }
+ if (candidate) code.push('_result.push("', candidate, '");\n');
+ else {
+ codeq.log.error('Resource ' + resourceName + ' was not found; path: "' + templatePath.join('/') + '"');
+ code.push('_result.push("data/broken.png");\n');
+ }
+ }
+ };
+
window.codeq = {
'jsonize': jsonize,
@@ -271,13 +387,14 @@
});
},
- 'templator': function (str, templateName) {
+ 'templator': function (str, templatePath, templateName) {
var f, parts, i, subparts, subpart,
src = [ 'var _result = [], echo = function (s) { _result.push(s); };\n' ],
- componentName, atoms, j, atom, debugPrefix, s, r;
+ atoms, j, atom, debugPrefix, s, r, tokens, handler;
if (!templateName) templateName = 'unknown template';
debugPrefix = '[' + templateName + ']';
+ if (!templatePath) templatePath = [];
// remove comments
parts = str.split('[%--'); // break on start-of-comment
@@ -308,9 +425,25 @@
if (subpart[0] === '=') { // a value reference
s = subpart.slice(1);
r = regexpWhiteSpaceTrim.exec(s);
- src.push('_result.push(this.', r && r[1] || s, ');\n');
+ r = r && r[1] || s;
+ src.push('_result.push(typeof this.', r, ' === \'undefined\' ? \'', r, ' missing\' : this.', r, ');\n');
}
- else { // a statement
+ else if (subpart[0] === '@') { // a directive
+ tokens = tokenize(subpart.slice(1));
+ if (tokens.length === 0) {
+ codeq.log.error('An empty directive in ' + templateName);
+ }
+ else {
+ handler = directiveHandlers[tokens[0]];
+ if (!handler) {
+ codeq.log.error('An unknown directive in ' + templateName + ': ' + tokens[0]);
+ }
+ else {
+ handler(src, tokens, templatePath);
+ }
+ }
+ }
+ else { // javascript statement(s)
src.push(subpart, '\n');
}
}
@@ -330,8 +463,22 @@
}
return function (args) {
- if ((typeof args !== 'object') || (args === null)) args = {};
- return f.apply(args);
+ var esc = codeq.escapeHtml,
+ escArgs = {},
+ key;
+ if ((typeof args === 'object') && (args !== null)) {
+ for (key in args) {
+ if (!args.hasOwnProperty(key)) continue;
+ escArgs[key] = esc(args[key]);
+ }
+ }
+ try {
+ return f.apply(escArgs);
+ }
+ catch (e) {
+ codeq.log.error('Error evaluating template ' + templateName + ' function: ' + e + '\nfunction() {\n' + src.join('') + '\nCreated from template:\n' + str, e);
+ throw e;
+ }
};
},
@@ -430,9 +577,12 @@
codeq.availableLangs.push(key);
}
- // the boot chain: must be a sequence of .then() terminated with .done()
+ // the boot chain: must be a sequence of .then() terminated with a .fail().done()
loadGuiTranslations()
- .then(function () {
+ .then(codeq.comms.getResourceTree)
+ .then(function (resourceTree) {
+ resources = resourceTree; // save the loaded resource tree to the internal variable
+
codeq.fire('init'); // tell any interested modules that we are now initialized, perhaps they want to initialize too
codeq.setLang(lang || 'en'); // initial language setting, this also translates the GUI
// go to login
@@ -441,6 +591,10 @@
//For performance reasons, the Tooltip and Popover data-apis are opt-in, meaning you must initialize them yourself.
$('[data-toggle="popover"]').popover()
})
+ .fail(function (e) {
+ codeq.log.error('CodeQ failed to start: ' + e, e);
+ alert('CodeQ failed to start: ' + e);
+ })
.done();
});
})();