crazy_ivan 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,544 +0,0 @@
1
- // Copyright (C) 2009 Andy Chu
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- // $Id: json-template.js 271 2009-05-31 19:35:43Z andy@chubot.org $
16
-
17
- //
18
- // JavaScript implementation of json-template.
19
- //
20
-
21
- // This is predefined in tests, shouldn't be defined anywhere else. TODO: Do
22
- // something nicer.
23
- var log = log || function() {};
24
- var repr = repr || function() {};
25
-
26
-
27
- // The "module" exported by this script is called "jsontemplate":
28
-
29
- var jsontemplate = function() {
30
-
31
-
32
- // Regex escaping for common metacharacters (note that JavaScript needs 2 \\ --
33
- // no raw strings!
34
- var META_ESCAPE = {
35
- '{': '\\{',
36
- '}': '\\}',
37
- '{{': '\\{\\{',
38
- '}}': '\\}\\}',
39
- '[': '\\[',
40
- ']': '\\]'
41
- };
42
-
43
- function _MakeTokenRegex(meta_left, meta_right) {
44
- // TODO: check errors
45
- return new RegExp(
46
- '(' +
47
- META_ESCAPE[meta_left] +
48
- '.+?' +
49
- META_ESCAPE[meta_right] +
50
- '\n?)', 'g'); // global for use with .exec()
51
- }
52
-
53
- //
54
- // Formatters
55
- //
56
-
57
- function HtmlEscape(s) {
58
- return s.replace(/&/g,'&').
59
- replace(/>/g,'>').
60
- replace(/</g,'&lt;');
61
- }
62
-
63
- function HtmlTagEscape(s) {
64
- return s.replace(/&/g,'&amp;').
65
- replace(/>/g,'&gt;').
66
- replace(/</g,'&lt;').
67
- replace(/"/g,'&quot;');
68
- }
69
-
70
- // Default ToString can be changed
71
- function ToString(s) {
72
- return s.toString();
73
- }
74
-
75
- var DEFAULT_FORMATTERS = {
76
- 'html': HtmlEscape,
77
- 'htmltag': HtmlTagEscape,
78
- 'html-attr-value': HtmlTagEscape,
79
- 'str': ToString,
80
- 'raw': function(x) {return x;}
81
- };
82
-
83
-
84
- //
85
- // Template implementation
86
- //
87
-
88
- function _ScopedContext(context, undefined_str) {
89
- // The stack contains:
90
- // The current context (an object).
91
- // An iteration index. -1 means we're NOT iterating.
92
- var stack = [{context: context, index: -1}];
93
-
94
- return {
95
- PushSection: function(name) {
96
- log('PushSection '+name);
97
- if (name === undefined || name === null) {
98
- return null;
99
- }
100
- var new_context = stack[stack.length-1].context[name] || null;
101
- stack.push({context: new_context, index: -1});
102
- return new_context;
103
- },
104
-
105
- Pop: function() {
106
- stack.pop();
107
- },
108
-
109
- next: function() {
110
- var stacktop = stack[stack.length-1];
111
-
112
- // Now we're iterating -- push a new mutable object onto the stack
113
- if (stacktop.index == -1) {
114
- stacktop = {context: null, index: 0};
115
- stack.push(stacktop);
116
- }
117
-
118
- // The thing we're iterating over
119
- var context_array = stack[stack.length - 2].context;
120
-
121
- // We're already done
122
- if (stacktop.index == context_array.length) {
123
- stack.pop();
124
- log('next: null');
125
- return null; // sentinel to say that we're done
126
- }
127
-
128
- log('next: ' + stacktop.index);
129
-
130
- stacktop.context = context_array[stacktop.index++];
131
-
132
- log('next: true');
133
- return true; // OK, we mutated the stack
134
- },
135
-
136
- CursorValue: function() {
137
- return stack[stack.length - 1].context;
138
- },
139
-
140
- _Undefined: function(name) {
141
- if (undefined_str === undefined) {
142
- throw {
143
- name: 'UndefinedVariable', message: name + ' is not defined'
144
- };
145
- } else {
146
- return undefined_str;
147
- }
148
- },
149
-
150
- _LookUpStack: function(name) {
151
- var i = stack.length - 1;
152
- while (true) {
153
- var context = stack[i].context;
154
- log('context '+repr(context));
155
-
156
- if (typeof context !== 'object') {
157
- i--;
158
- } else {
159
- var value = context[name];
160
- if (value === undefined || value === null) {
161
- i--;
162
- } else {
163
- return value;
164
- }
165
- }
166
- if (i <= -1) {
167
- return this._Undefined(name);
168
- }
169
- }
170
- },
171
-
172
- Lookup: function(name) {
173
- var parts = name.split('.');
174
- var value = this._LookUpStack(parts[0]);
175
- if (parts.length > 1) {
176
- for (var i=1; i<parts.length; i++) {
177
- value = value[parts[i]];
178
- if (value === undefined) {
179
- return this._Undefined(parts[i]);
180
- }
181
- }
182
- }
183
- return value;
184
- }
185
-
186
- };
187
- }
188
-
189
-
190
- function _Section(section_name) {
191
- var current_clause = [];
192
- var statements = {'default': current_clause};
193
-
194
- return {
195
- section_name: section_name, // public attribute
196
-
197
- Statements: function(clause) {
198
- clause = clause || 'default';
199
- return statements[clause] || [];
200
- },
201
-
202
- NewClause: function(clause_name) {
203
- var new_clause = [];
204
- statements[clause_name] = new_clause;
205
- current_clause = new_clause;
206
- },
207
-
208
- Append: function(statement) {
209
- current_clause.push(statement);
210
- }
211
- };
212
- }
213
-
214
-
215
- function _Execute(statements, context, callback) {
216
- var i;
217
- for (i=0; i<statements.length; i++) {
218
- statement = statements[i];
219
-
220
- //log('Executing ' + statement);
221
-
222
- if (typeof(statement) == 'string') {
223
- callback(statement);
224
- } else {
225
- var func = statement[0];
226
- var args = statement[1];
227
- func(args, context, callback);
228
- }
229
- }
230
- }
231
-
232
-
233
- function _DoSubstitute(statement, context, callback) {
234
- log('Substituting: '+ statement.name);
235
- var value;
236
- if (statement.name == '@') {
237
- value = context.CursorValue();
238
- } else {
239
- value = context.Lookup(statement.name);
240
- }
241
-
242
- // Format values
243
- for (i=0; i<statement.formatters.length; i++) {
244
- value = statement.formatters[i](value);
245
- }
246
-
247
- callback(value);
248
- }
249
-
250
-
251
- // for [section foo]
252
- function _DoSection(args, context, callback) {
253
-
254
- var block = args;
255
- var value = context.PushSection(block.section_name);
256
- var do_section = false;
257
-
258
- // "truthy" values should have their sections executed.
259
- if (value) {
260
- do_section = true;
261
- }
262
- // Except: if the value is a zero-length array (which is "truthy")
263
- if (value && value.length === 0) {
264
- do_section = false;
265
- }
266
-
267
- if (do_section) {
268
- _Execute(block.Statements(), context, callback);
269
- context.Pop();
270
- } else { // Empty list, None, False, etc.
271
- context.Pop();
272
- _Execute(block.Statements('or'), context, callback);
273
- }
274
- }
275
-
276
-
277
- function _DoRepeatedSection(args, context, callback) {
278
- var block = args;
279
- var pushed;
280
-
281
- if (block.section_name == '@') {
282
- // If the name is @, we stay in the enclosing context, but assume it's a
283
- // list, and repeat this block many times.
284
- items = context.CursorValue();
285
- // TODO: check that items is an array; apparently this is hard in JavaScript
286
- //if type(items) is not list:
287
- // raise EvaluationError('Expected a list; got %s' % type(items))
288
- pushed = false;
289
- } else {
290
- items = context.PushSection(block.section_name);
291
- pushed = true;
292
- }
293
-
294
- //log('ITEMS: '+showArray(items));
295
- if (items && items.length > 0) {
296
- // Execute the statements in the block for every item in the list.
297
- // Execute the alternate block on every iteration except the last. Each
298
- // item could be an atom (string, integer, etc.) or a dictionary.
299
-
300
- var last_index = items.length - 1;
301
- var statements = block.Statements();
302
- var alt_statements = block.Statements('alternate');
303
-
304
- for (var i=0; context.next() !== null; i++) {
305
- log('_DoRepeatedSection i: ' +i);
306
- _Execute(statements, context, callback);
307
- if (i != last_index) {
308
- log('ALTERNATE');
309
- _Execute(alt_statements, context, callback);
310
- }
311
- }
312
- } else {
313
- log('OR: '+block.Statements('or'));
314
- _Execute(block.Statements('or'), context, callback);
315
- }
316
-
317
- if (pushed) {
318
- context.Pop();
319
- }
320
- }
321
-
322
-
323
- var _SECTION_RE = /(repeated)?\s*(section)\s+(\S+)?/;
324
-
325
-
326
- // TODO: The compile function could be in a different module, in case we want to
327
- // compile on the server side.
328
- function _Compile(template_str, options) {
329
- var more_formatters = options.more_formatters ||
330
- function (x) { return null; };
331
-
332
- // We want to allow an explicit null value for default_formatter, which means
333
- // that an error is raised if no formatter is specified.
334
- var default_formatter;
335
- if (options.default_formatter === undefined) {
336
- default_formatter = 'str';
337
- } else {
338
- default_formatter = options.default_formatter;
339
- }
340
-
341
- function GetFormatter(format_str) {
342
- var formatter = more_formatters(format_str) ||
343
- DEFAULT_FORMATTERS[format_str];
344
- if (formatter === undefined) {
345
- throw {
346
- name: 'BadFormatter',
347
- message: format_str + ' is not a valid formatter'
348
- };
349
- }
350
- return formatter;
351
- }
352
-
353
- var format_char = options.format_char || '|';
354
- if (format_char != ':' && format_char != '|') {
355
- throw {
356
- name: 'ConfigurationError',
357
- message: 'Only format characters : and | are accepted'
358
- };
359
- }
360
-
361
- var meta = options.meta || '{}';
362
- var n = meta.length;
363
- if (n % 2 == 1) {
364
- throw {
365
- name: 'ConfigurationError',
366
- message: meta + ' has an odd number of metacharacters'
367
- };
368
- }
369
- var meta_left = meta.substring(0, n/2);
370
- var meta_right = meta.substring(n/2, n);
371
-
372
- var token_re = _MakeTokenRegex(meta_left, meta_right);
373
- var current_block = _Section();
374
- var stack = [current_block];
375
-
376
- var strip_num = meta_left.length; // assume they're the same length
377
-
378
- var token_match;
379
- var last_index = 0;
380
-
381
- while (true) {
382
- token_match = token_re.exec(template_str);
383
- log('match:', token_match);
384
- if (token_match === null) {
385
- break;
386
- } else {
387
- var token = token_match[0];
388
- }
389
- log('last_index: '+ last_index);
390
- log('token_match.index: '+ token_match.index);
391
-
392
- // Add the previous literal to the program
393
- if (token_match.index > last_index) {
394
- var tok = template_str.slice(last_index, token_match.index);
395
- current_block.Append(tok);
396
- log('tok: "'+ tok+'"');
397
- }
398
- last_index = token_re.lastIndex;
399
-
400
- log('token0: "'+ token+'"');
401
-
402
- var had_newline = false;
403
- if (token.slice(-1) == '\n') {
404
- token = token.slice(null, -1);
405
- had_newline = true;
406
- }
407
-
408
- token = token.slice(strip_num, -strip_num);
409
-
410
- if (token.charAt(0) == '#') {
411
- continue; // comment
412
- }
413
-
414
- if (token.charAt(0) == '.') { // Keyword
415
- token = token.substring(1, token.length);
416
-
417
- var literal = {
418
- 'meta-left': meta_left,
419
- 'meta-right': meta_right,
420
- 'space': ' ',
421
- 'tab': '\t',
422
- 'newline': '\n'
423
- }[token];
424
-
425
- if (literal !== undefined) {
426
- current_block.Append(literal);
427
- continue;
428
- }
429
-
430
- var section_match = token.match(_SECTION_RE);
431
-
432
- if (section_match) {
433
- var repeated = section_match[1];
434
- var section_name = section_match[3];
435
- var func = repeated ? _DoRepeatedSection : _DoSection;
436
- log('repeated ' + repeated + ' section_name ' + section_name);
437
-
438
- var new_block = _Section(section_name);
439
- current_block.Append([func, new_block]);
440
- stack.push(new_block);
441
- current_block = new_block;
442
- continue;
443
- }
444
-
445
- if (token == 'alternates with') {
446
- current_block.NewClause('alternate');
447
- continue;
448
- }
449
-
450
- if (token == 'or') {
451
- current_block.NewClause('or');
452
- continue;
453
- }
454
-
455
- if (token == 'end') {
456
- // End the block
457
- stack.pop();
458
- if (stack.length > 0) {
459
- current_block = stack[stack.length-1];
460
- } else {
461
- throw {
462
- name: 'TemplateSyntaxError',
463
- message: 'Got too many {end} statements'
464
- };
465
- }
466
- continue;
467
- }
468
- }
469
-
470
- // A variable substitution
471
- var parts = token.split(format_char);
472
- var formatters;
473
- var name;
474
- if (parts.length == 1) {
475
- if (default_formatter === null) {
476
- throw {
477
- name: 'MissingFormatter',
478
- message: 'This template requires explicit formatters.'
479
- };
480
- }
481
- // If no formatter is specified, the default is the 'str' formatter,
482
- // which the user can define however they desire.
483
- formatters = [GetFormatter(default_formatter)];
484
- name = token;
485
- } else {
486
- formatters = [];
487
- for (var j=1; j<parts.length; j++) {
488
- formatters.push(GetFormatter(parts[j]));
489
- }
490
- name = parts[0];
491
- }
492
- current_block.Append(
493
- [_DoSubstitute, { name: name, formatters: formatters}]);
494
- if (had_newline) {
495
- current_block.Append('\n');
496
- }
497
- }
498
-
499
- // Add the trailing literal
500
- current_block.Append(template_str.slice(last_index));
501
-
502
- if (stack.length !== 1) {
503
- throw {
504
- name: 'TemplateSyntaxError',
505
- message: 'Got too few {end} statements'
506
- };
507
- }
508
- return current_block;
509
- }
510
-
511
- // The Template class is defined in the traditional style so that users can add
512
- // methods by mutating the prototype attribute. TODO: Need a good idiom for
513
- // inheritance without mutating globals.
514
-
515
- function Template(template_str, options) {
516
-
517
- // Add 'new' if we were not called with 'new', so prototyping works.
518
- if(!(this instanceof Template)) {
519
- return new Template(template_str, options);
520
- }
521
-
522
- this._options = options || {};
523
- this._program = _Compile(template_str, this._options);
524
- }
525
-
526
- Template.prototype.render = function(data_dict, callback) {
527
- // options.undefined_str can either be a string or undefined
528
- var context = _ScopedContext(data_dict, this._options.undefined_str);
529
- _Execute(this._program.Statements(), context, callback);
530
- };
531
-
532
- Template.prototype.expand = function(data_dict) {
533
- var tokens = [];
534
- this.render(data_dict, function(x) { tokens.push(x); });
535
- return tokens.join('');
536
- };
537
-
538
-
539
- // We just export one name for now, the Template "class".
540
- // We need HtmlEscape in the browser tests, so might as well export it.
541
-
542
- return {Template: Template, HtmlEscape: HtmlEscape};
543
-
544
- }();