ethstats-server/web-app/.meteor/local/build/programs/server/packages/spacebars-compiler.js

1191 lines
108 KiB
JavaScript
Raw Normal View History

2015-08-14 19:22:53 +02:00
(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var HTML = Package.htmljs.HTML;
var HTMLTools = Package['html-tools'].HTMLTools;
var BlazeTools = Package['blaze-tools'].BlazeTools;
var _ = Package.underscore._;
/* Package-scope variables */
var SpacebarsCompiler, TemplateTag;
(function () {
////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/spacebars-compiler/templatetag.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////
//
SpacebarsCompiler = {}; // 1
// 2
// A TemplateTag is the result of parsing a single `{{...}}` tag. // 3
// // 4
// The `.type` of a TemplateTag is one of: // 5
// // 6
// - `"DOUBLE"` - `{{foo}}` // 7
// - `"TRIPLE"` - `{{{foo}}}` // 8
// - `"COMMENT"` - `{{! foo}}` // 9
// - `"BLOCKCOMMENT" - `{{!-- foo--}}` // 10
// - `"INCLUSION"` - `{{> foo}}` // 11
// - `"BLOCKOPEN"` - `{{#foo}}` // 12
// - `"BLOCKCLOSE"` - `{{/foo}}` // 13
// - `"ELSE"` - `{{else}}` // 14
// - `"ESCAPE"` - `{{|`, `{{{|`, `{{{{|` and so on // 15
// // 16
// Besides `type`, the mandatory properties of a TemplateTag are: // 17
// // 18
// - `path` - An array of one or more strings. The path of `{{foo.bar}}` // 19
// is `["foo", "bar"]`. Applies to DOUBLE, TRIPLE, INCLUSION, BLOCKOPEN, // 20
// and BLOCKCLOSE. // 21
// // 22
// - `args` - An array of zero or more argument specs. An argument spec // 23
// is a two or three element array, consisting of a type, value, and // 24
// optional keyword name. For example, the `args` of `{{foo "bar" x=3}}` // 25
// are `[["STRING", "bar"], ["NUMBER", 3, "x"]]`. Applies to DOUBLE, // 26
// TRIPLE, INCLUSION, and BLOCKOPEN. // 27
// // 28
// - `value` - A string of the comment's text. Applies to COMMENT and // 29
// BLOCKCOMMENT. // 30
// // 31
// These additional are typically set during parsing: // 32
// // 33
// - `position` - The HTMLTools.TEMPLATE_TAG_POSITION specifying at what sort // 34
// of site the TemplateTag was encountered (e.g. at element level or as // 35
// part of an attribute value). Its absence implies // 36
// TEMPLATE_TAG_POSITION.ELEMENT. // 37
// // 38
// - `content` and `elseContent` - When a BLOCKOPEN tag's contents are // 39
// parsed, they are put here. `elseContent` will only be present if // 40
// an `{{else}}` was found. // 41
// 42
var TEMPLATE_TAG_POSITION = HTMLTools.TEMPLATE_TAG_POSITION; // 43
// 44
TemplateTag = SpacebarsCompiler.TemplateTag = function () { // 45
HTMLTools.TemplateTag.apply(this, arguments); // 46
}; // 47
TemplateTag.prototype = new HTMLTools.TemplateTag; // 48
TemplateTag.prototype.constructorName = 'SpacebarsCompiler.TemplateTag'; // 49
// 50
var makeStacheTagStartRegex = function (r) { // 51
return new RegExp(r.source + /(?![{>!#/])/.source, // 52
r.ignoreCase ? 'i' : ''); // 53
}; // 54
// 55
// "starts" regexes are used to see what type of template // 56
// tag the parser is looking at. They must match a non-empty // 57
// result, but not the interesting part of the tag. // 58
var starts = { // 59
ESCAPE: /^\{\{(?=\{*\|)/, // 60
ELSE: makeStacheTagStartRegex(/^\{\{\s*else(?=[\s}])/i), // 61
DOUBLE: makeStacheTagStartRegex(/^\{\{\s*(?!\s)/), // 62
TRIPLE: makeStacheTagStartRegex(/^\{\{\{\s*(?!\s)/), // 63
BLOCKCOMMENT: makeStacheTagStartRegex(/^\{\{\s*!--/), // 64
COMMENT: makeStacheTagStartRegex(/^\{\{\s*!/), // 65
INCLUSION: makeStacheTagStartRegex(/^\{\{\s*>\s*(?!\s)/), // 66
BLOCKOPEN: makeStacheTagStartRegex(/^\{\{\s*#\s*(?!\s)/), // 67
BLOCKCLOSE: makeStacheTagStartRegex(/^\{\{\s*\/\s*(?!\s)/) // 68
}; // 69
// 70
var ends = { // 71
DOUBLE: /^\s*\}\}/, // 72
TRIPLE: /^\s*\}\}\}/ // 73
}; // 74
// 75
// Parse a tag from the provided scanner or string. If the input // 76
// doesn't start with `{{`, returns null. Otherwise, either succeeds // 77
// and returns a SpacebarsCompiler.TemplateTag, or throws an error (using // 78
// `scanner.fatal` if a scanner is provided). // 79
TemplateTag.parse = function (scannerOrString) { // 80
var scanner = scannerOrString; // 81
if (typeof scanner === 'string') // 82
scanner = new HTMLTools.Scanner(scannerOrString); // 83
// 84
if (! (scanner.peek() === '{' && // 85
(scanner.rest()).slice(0, 2) === '{{')) // 86
return null; // 87
// 88
var run = function (regex) { // 89
// regex is assumed to start with `^` // 90
var result = regex.exec(scanner.rest()); // 91
if (! result) // 92
return null; // 93
var ret = result[0]; // 94
scanner.pos += ret.length; // 95
return ret; // 96
}; // 97
// 98
var advance = function (amount) { // 99
scanner.pos += amount; // 100
}; // 101
// 102
var scanIdentifier = function (isFirstInPath) { // 103
var id = BlazeTools.parseIdentifierName(scanner); // 104
if (! id) // 105
expected('IDENTIFIER'); // 106
if (isFirstInPath && // 107
(id === 'null' || id === 'true' || id === 'false')) // 108
scanner.fatal("Can't use null, true, or false, as an identifier at start of path"); // 109
// 110
return id; // 111
}; // 112
// 113
var scanPath = function () { // 114
var segments = []; // 115
// 116
// handle initial `.`, `..`, `./`, `../`, `../..`, `../../`, etc // 117
var dots; // 118
if ((dots = run(/^[\.\/]+/))) { // 119
var ancestorStr = '.'; // eg `../../..` maps to `....` // 120
var endsWithSlash = /\/$/.test(dots); // 121
// 122
if (endsWithSlash) // 123
dots = dots.slice(0, -1); // 124
// 125
_.each(dots.split('/'), function(dotClause, index) { // 126
if (index === 0) { // 127
if (dotClause !== '.' && dotClause !== '..') // 128
expected("`.`, `..`, `./` or `../`"); // 129
} else { // 130
if (dotClause !== '..') // 131
expected("`..` or `../`"); // 132
} // 133
// 134
if (dotClause === '..') // 135
ancestorStr += '.'; // 136
}); // 137
// 138
segments.push(ancestorStr); // 139
// 140
if (!endsWithSlash) // 141
return segments; // 142
} // 143
// 144
while (true) { // 145
// scan a path segment // 146
// 147
if (run(/^\[/)) { // 148
var seg = run(/^[\s\S]*?\]/); // 149
if (! seg) // 150
error("Unterminated path segment"); // 151
seg = seg.slice(0, -1); // 152
if (! seg && ! segments.length) // 153
error("Path can't start with empty string"); // 154
segments.push(seg); // 155
} else { // 156
var id = scanIdentifier(! segments.length); // 157
if (id === 'this') { // 158
if (! segments.length) { // 159
// initial `this` // 160
segments.push('.'); // 161
} else { // 162
error("Can only use `this` at the beginning of a path.\nInstead of `foo.this` or `../this`, just write `foo` or `..`.");
} // 164
} else { // 165
segments.push(id); // 166
} // 167
} // 168
// 169
var sep = run(/^[\.\/]/); // 170
if (! sep) // 171
break; // 172
} // 173
// 174
return segments; // 175
}; // 176
// 177
// scan the keyword portion of a keyword argument // 178
// (the "foo" portion in "foo=bar"). // 179
// Result is either the keyword matched, or null // 180
// if we're not at a keyword argument position. // 181
var scanArgKeyword = function () { // 182
var match = /^([^\{\}\(\)\>#=\s"'\[\]]+)\s*=\s*/.exec(scanner.rest()); // 183
if (match) { // 184
scanner.pos += match[0].length; // 185
return match[1]; // 186
} else { // 187
return null; // 188
} // 189
}; // 190
// 191
// scan an argument; succeeds or errors. // 192
// Result is an array of two or three items: // 193
// type , value, and (indicating a keyword argument) // 194
// keyword name. // 195
var scanArg = function () { // 196
var keyword = scanArgKeyword(); // null if not parsing a kwarg // 197
var value = scanArgValue(); // 198
return keyword ? value.concat(keyword) : value; // 199
}; // 200
// 201
// scan an argument value (for keyword or positional arguments); // 202
// succeeds or errors. Result is an array of type, value. // 203
var scanArgValue = function () { // 204
var startPos = scanner.pos; // 205
var result; // 206
if ((result = BlazeTools.parseNumber(scanner))) { // 207
return ['NUMBER', result.value]; // 208
} else if ((result = BlazeTools.parseStringLiteral(scanner))) { // 209
return ['STRING', result.value]; // 210
} else if (/^[\.\[]/.test(scanner.peek())) { // 211
return ['PATH', scanPath()]; // 212
} else if ((result = BlazeTools.parseIdentifierName(scanner))) { // 213
var id = result; // 214
if (id === 'null') { // 215
return ['NULL', null]; // 216
} else if (id === 'true' || id === 'false') { // 217
return ['BOOLEAN', id === 'true']; // 218
} else { // 219
scanner.pos = startPos; // unconsume `id` // 220
return ['PATH', scanPath()]; // 221
} // 222
} else { // 223
expected('identifier, number, string, boolean, or null'); // 224
} // 225
}; // 226
// 227
var type; // 228
// 229
var error = function (msg) { // 230
scanner.fatal(msg); // 231
}; // 232
// 233
var expected = function (what) { // 234
error('Expected ' + what); // 235
}; // 236
// 237
// must do ESCAPE first, immediately followed by ELSE // 238
// order of others doesn't matter // 239
if (run(starts.ESCAPE)) type = 'ESCAPE'; // 240
else if (run(starts.ELSE)) type = 'ELSE'; // 241
else if (run(starts.DOUBLE)) type = 'DOUBLE'; // 242
else if (run(starts.TRIPLE)) type = 'TRIPLE'; // 243
else if (run(starts.BLOCKCOMMENT)) type = 'BLOCKCOMMENT'; // 244
else if (run(starts.COMMENT)) type = 'COMMENT'; // 245
else if (run(starts.INCLUSION)) type = 'INCLUSION'; // 246
else if (run(starts.BLOCKOPEN)) type = 'BLOCKOPEN'; // 247
else if (run(starts.BLOCKCLOSE)) type = 'BLOCKCLOSE'; // 248
else // 249
error('Unknown stache tag'); // 250
// 251
var tag = new TemplateTag; // 252
tag.type = type; // 253
// 254
if (type === 'BLOCKCOMMENT') { // 255
var result = run(/^[\s\S]*?--\s*?\}\}/); // 256
if (! result) // 257
error("Unclosed block comment"); // 258
tag.value = result.slice(0, result.lastIndexOf('--')); // 259
} else if (type === 'COMMENT') { // 260
var result = run(/^[\s\S]*?\}\}/); // 261
if (! result) // 262
error("Unclosed comment"); // 263
tag.value = result.slice(0, -2); // 264
} else if (type === 'BLOCKCLOSE') { // 265
tag.path = scanPath(); // 266
if (! run(ends.DOUBLE)) // 267
expected('`}}`'); // 268
} else if (type === 'ELSE') { // 269
if (! run(ends.DOUBLE)) // 270
expected('`}}`'); // 271
} else if (type === 'ESCAPE') { // 272
var result = run(/^\{*\|/); // 273
tag.value = '{{' + result.slice(0, -1); // 274
} else { // 275
// DOUBLE, TRIPLE, BLOCKOPEN, INCLUSION // 276
tag.path = scanPath(); // 277
tag.args = []; // 278
var foundKwArg = false; // 279
while (true) { // 280
run(/^\s*/); // 281
if (type === 'TRIPLE') { // 282
if (run(ends.TRIPLE)) // 283
break; // 284
else if (scanner.peek() === '}') // 285
expected('`}}}`'); // 286
} else { // 287
if (run(ends.DOUBLE)) // 288
break; // 289
else if (scanner.peek() === '}') // 290
expected('`}}`'); // 291
} // 292
var newArg = scanArg(); // 293
if (newArg.length === 3) { // 294
foundKwArg = true; // 295
} else { // 296
if (foundKwArg) // 297
error("Can't have a non-keyword argument after a keyword argument"); // 298
} // 299
tag.args.push(newArg); // 300
// 301
if (run(/^(?=[\s}])/) !== '') // 302
expected('space'); // 303
} // 304
} // 305
// 306
return tag; // 307
}; // 308
// 309
// Returns a SpacebarsCompiler.TemplateTag parsed from `scanner`, leaving scanner // 310
// at its original position. // 311
// // 312
// An error will still be thrown if there is not a valid template tag at // 313
// the current position. // 314
TemplateTag.peek = function (scanner) { // 315
var startPos = scanner.pos; // 316
var result = TemplateTag.parse(scanner); // 317
scanner.pos = startPos; // 318
return result; // 319
}; // 320
// 321
// Like `TemplateTag.parse`, but in the case of blocks, parse the complete // 322
// `{{#foo}}...{{/foo}}` with `content` and possible `elseContent`, rather // 323
// than just the BLOCKOPEN tag. // 324
// // 325
// In addition: // 326
// // 327
// - Throws an error if `{{else}}` or `{{/foo}}` tag is encountered. // 328
// // 329
// - Returns `null` for a COMMENT. (This case is distinguishable from // 330
// parsing no tag by the fact that the scanner is advanced.) // 331
// // 332
// - Takes an HTMLTools.TEMPLATE_TAG_POSITION `position` and sets it as the // 333
// TemplateTag's `.position` property. // 334
// // 335
// - Validates the tag's well-formedness and legality at in its position. // 336
TemplateTag.parseCompleteTag = function (scannerOrString, position) { // 337
var scanner = scannerOrString; // 338
if (typeof scanner === 'string') // 339
scanner = new HTMLTools.Scanner(scannerOrString); // 340
// 341
var startPos = scanner.pos; // for error messages // 342
var result = TemplateTag.parse(scannerOrString); // 343
if (! result) // 344
return result; // 345
// 346
if (result.type === 'BLOCKCOMMENT') // 347
return null; // 348
// 349
if (result.type === 'COMMENT') // 350
return null; // 351
// 352
if (result.type === 'ELSE') // 353
scanner.fatal("Unexpected {{else}}"); // 354
// 355
if (result.type === 'BLOCKCLOSE') // 356
scanner.fatal("Unexpected closing template tag"); // 357
// 358
position = (position || TEMPLATE_TAG_POSITION.ELEMENT); // 359
if (position !== TEMPLATE_TAG_POSITION.ELEMENT) // 360
result.position = position; // 361
// 362
if (result.type === 'BLOCKOPEN') { // 363
// parse block contents // 364
// 365
// Construct a string version of `.path` for comparing start and // 366
// end tags. For example, `foo/[0]` was parsed into `["foo", "0"]` // 367
// and now becomes `foo,0`. This form may also show up in error // 368
// messages. // 369
var blockName = result.path.join(','); // 370
// 371
var textMode = null; // 372
if (blockName === 'markdown' || // 373
position === TEMPLATE_TAG_POSITION.IN_RAWTEXT) { // 374
textMode = HTML.TEXTMODE.STRING; // 375
} else if (position === TEMPLATE_TAG_POSITION.IN_RCDATA || // 376
position === TEMPLATE_TAG_POSITION.IN_ATTRIBUTE) { // 377
textMode = HTML.TEXTMODE.RCDATA; // 378
} // 379
var parserOptions = { // 380
getTemplateTag: TemplateTag.parseCompleteTag, // 381
shouldStop: isAtBlockCloseOrElse, // 382
textMode: textMode // 383
}; // 384
result.content = HTMLTools.parseFragment(scanner, parserOptions); // 385
// 386
if (scanner.rest().slice(0, 2) !== '{{') // 387
scanner.fatal("Expected {{else}} or block close for " + blockName); // 388
// 389
var lastPos = scanner.pos; // save for error messages // 390
var tmplTag = TemplateTag.parse(scanner); // {{else}} or {{/foo}} // 391
// 392
if (tmplTag.type === 'ELSE') { // 393
// parse {{else}} and content up to close tag // 394
result.elseContent = HTMLTools.parseFragment(scanner, parserOptions); // 395
// 396
if (scanner.rest().slice(0, 2) !== '{{') // 397
scanner.fatal("Expected block close for " + blockName); // 398
// 399
lastPos = scanner.pos; // 400
tmplTag = TemplateTag.parse(scanner); // 401
} // 402
// 403
if (tmplTag.type === 'BLOCKCLOSE') { // 404
var blockName2 = tmplTag.path.join(','); // 405
if (blockName !== blockName2) { // 406
scanner.pos = lastPos; // 407
scanner.fatal('Expected tag to close ' + blockName + ', found ' + // 408
blockName2); // 409
} // 410
} else { // 411
scanner.pos = lastPos; // 412
scanner.fatal('Expected tag to close ' + blockName + ', found ' + // 413
tmplTag.type); // 414
} // 415
} // 416
// 417
var finalPos = scanner.pos; // 418
scanner.pos = startPos; // 419
validateTag(result, scanner); // 420
scanner.pos = finalPos; // 421
// 422
return result; // 423
}; // 424
// 425
var isAtBlockCloseOrElse = function (scanner) { // 426
// Detect `{{else}}` or `{{/foo}}`. // 427
// // 428
// We do as much work ourselves before deferring to `TemplateTag.peek`, // 429
// for efficiency (we're called for every input token) and to be // 430
// less obtrusive, because `TemplateTag.peek` will throw an error if it // 431
// sees `{{` followed by a malformed tag. // 432
var rest, type; // 433
return (scanner.peek() === '{' && // 434
(rest = scanner.rest()).slice(0, 2) === '{{' && // 435
/^\{\{\s*(\/|else\b)/.test(rest) && // 436
(type = TemplateTag.peek(scanner).type) && // 437
(type === 'BLOCKCLOSE' || type === 'ELSE')); // 438
}; // 439
// 440
// Validate that `templateTag` is correctly formed and legal for its // 441
// HTML position. Use `scanner` to report errors. On success, does // 442
// nothing. // 443
var validateTag = function (ttag, scanner) { // 444
// 445
if (ttag.type === 'INCLUSION' || ttag.type === 'BLOCKOPEN') { // 446
var args = ttag.args; // 447
if (args.length > 1 && args[0].length === 2 && args[0][0] !== 'PATH') { // 448
// we have a positional argument that is not a PATH followed by // 449
// other arguments // 450
scanner.fatal("First argument must be a function, to be called on the rest of the arguments; found " + args[0][0]);
} // 452
} // 453
// 454
var position = ttag.position || TEMPLATE_TAG_POSITION.ELEMENT; // 455
if (position === TEMPLATE_TAG_POSITION.IN_ATTRIBUTE) { // 456
if (ttag.type === 'DOUBLE' || ttag.type === 'ESCAPE') { // 457
return; // 458
} else if (ttag.type === 'BLOCKOPEN') { // 459
var path = ttag.path; // 460
var path0 = path[0]; // 461
if (! (path.length === 1 && (path0 === 'if' || // 462
path0 === 'unless' || // 463
path0 === 'with' || // 464
path0 === 'each'))) { // 465
scanner.fatal("Custom block helpers are not allowed in an HTML attribute, only built-in ones like #each and #if");
} // 467
} else { // 468
scanner.fatal(ttag.type + " template tag is not allowed in an HTML attribute"); // 469
} // 470
} else if (position === TEMPLATE_TAG_POSITION.IN_START_TAG) { // 471
if (! (ttag.type === 'DOUBLE')) { // 472
scanner.fatal("Reactive HTML attributes must either have a constant name or consist of a single {{helper}} providing a dictionary of names and values. A template tag of type " + ttag.type + " is not allowed here.");
} // 474
if (scanner.peek() === '=') { // 475
scanner.fatal("Template tags are not allowed in attribute names, only in attribute values or in the form of a single {{helper}} that evaluates to a dictionary of name=value pairs.");
} // 477
} // 478
// 479
}; // 480
// 481
////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function () {
////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/spacebars-compiler/optimizer.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////
//
// Optimize parts of an HTMLjs tree into raw HTML strings when they don't // 1
// contain template tags. // 2
// 3
var constant = function (value) { // 4
return function () { return value; }; // 5
}; // 6
// 7
var OPTIMIZABLE = { // 8
NONE: 0, // 9
PARTS: 1, // 10
FULL: 2 // 11
}; // 12
// 13
// We can only turn content into an HTML string if it contains no template // 14
// tags and no "tricky" HTML tags. If we can optimize the entire content // 15
// into a string, we return OPTIMIZABLE.FULL. If the we are given an // 16
// unoptimizable node, we return OPTIMIZABLE.NONE. If we are given a tree // 17
// that contains an unoptimizable node somewhere, we return OPTIMIZABLE.PARTS. // 18
// // 19
// For example, we always create SVG elements programmatically, since SVG // 20
// doesn't have innerHTML. If we are given an SVG element, we return NONE. // 21
// However, if we are given a big tree that contains SVG somewhere, we // 22
// return PARTS so that the optimizer can descend into the tree and optimize // 23
// other parts of it. // 24
var CanOptimizeVisitor = HTML.Visitor.extend(); // 25
CanOptimizeVisitor.def({ // 26
visitNull: constant(OPTIMIZABLE.FULL), // 27
visitPrimitive: constant(OPTIMIZABLE.FULL), // 28
visitComment: constant(OPTIMIZABLE.FULL), // 29
visitCharRef: constant(OPTIMIZABLE.FULL), // 30
visitRaw: constant(OPTIMIZABLE.FULL), // 31
visitObject: constant(OPTIMIZABLE.NONE), // 32
visitFunction: constant(OPTIMIZABLE.NONE), // 33
visitArray: function (x) { // 34
for (var i = 0; i < x.length; i++) // 35
if (this.visit(x[i]) !== OPTIMIZABLE.FULL) // 36
return OPTIMIZABLE.PARTS; // 37
return OPTIMIZABLE.FULL; // 38
}, // 39
visitTag: function (tag) { // 40
var tagName = tag.tagName; // 41
if (tagName === 'textarea') { // 42
// optimizing into a TEXTAREA's RCDATA would require being a little // 43
// more clever. // 44
return OPTIMIZABLE.NONE; // 45
} else if (! (HTML.isKnownElement(tagName) && // 46
! HTML.isKnownSVGElement(tagName))) { // 47
// foreign elements like SVG can't be stringified for innerHTML. // 48
return OPTIMIZABLE.NONE; // 49
} else if (tagName === 'table') { // 50
// Avoid ever producing HTML containing `<table><tr>...`, because the // 51
// browser will insert a TBODY. If we just `createElement("table")` and // 52
// `createElement("tr")`, on the other hand, no TBODY is necessary // 53
// (assuming IE 8+). // 54
return OPTIMIZABLE.NONE; // 55
} // 56
// 57
var children = tag.children; // 58
for (var i = 0; i < children.length; i++) // 59
if (this.visit(children[i]) !== OPTIMIZABLE.FULL) // 60
return OPTIMIZABLE.PARTS; // 61
// 62
if (this.visitAttributes(tag.attrs) !== OPTIMIZABLE.FULL) // 63
return OPTIMIZABLE.PARTS; // 64
// 65
return OPTIMIZABLE.FULL; // 66
}, // 67
visitAttributes: function (attrs) { // 68
if (attrs) { // 69
var isArray = HTML.isArray(attrs); // 70
for (var i = 0; i < (isArray ? attrs.length : 1); i++) { // 71
var a = (isArray ? attrs[i] : attrs); // 72
if ((typeof a !== 'object') || (a instanceof HTMLTools.TemplateTag)) // 73
return OPTIMIZABLE.PARTS; // 74
for (var k in a) // 75
if (this.visit(a[k]) !== OPTIMIZABLE.FULL) // 76
return OPTIMIZABLE.PARTS; // 77
} // 78
} // 79
return OPTIMIZABLE.FULL; // 80
} // 81
}); // 82
// 83
var getOptimizability = function (content) { // 84
return (new CanOptimizeVisitor).visit(content); // 85
}; // 86
// 87
var toRaw = function (x) { // 88
return HTML.Raw(HTML.toHTML(x)); // 89
}; // 90
// 91
var TreeTransformer = HTML.TransformingVisitor.extend(); // 92
TreeTransformer.def({ // 93
visitAttributes: function (attrs/*, ...*/) { // 94
// pass template tags through by default // 95
if (attrs instanceof HTMLTools.TemplateTag) // 96
return attrs; // 97
// 98
return HTML.TransformingVisitor.prototype.visitAttributes.apply( // 99
this, arguments); // 100
} // 101
}); // 102
// 103
// Replace parts of the HTMLjs tree that have no template tags (or // 104
// tricky HTML tags) with HTML.Raw objects containing raw HTML. // 105
var OptimizingVisitor = TreeTransformer.extend(); // 106
OptimizingVisitor.def({ // 107
visitNull: toRaw, // 108
visitPrimitive: toRaw, // 109
visitComment: toRaw, // 110
visitCharRef: toRaw, // 111
visitArray: function (array) { // 112
var optimizability = getOptimizability(array); // 113
if (optimizability === OPTIMIZABLE.FULL) { // 114
return toRaw(array); // 115
} else if (optimizability === OPTIMIZABLE.PARTS) { // 116
return TreeTransformer.prototype.visitArray.call(this, array); // 117
} else { // 118
return array; // 119
} // 120
}, // 121
visitTag: function (tag) { // 122
var optimizability = getOptimizability(tag); // 123
if (optimizability === OPTIMIZABLE.FULL) { // 124
return toRaw(tag); // 125
} else if (optimizability === OPTIMIZABLE.PARTS) { // 126
return TreeTransformer.prototype.visitTag.call(this, tag); // 127
} else { // 128
return tag; // 129
} // 130
}, // 131
visitChildren: function (children) { // 132
// don't optimize the children array into a Raw object! // 133
return TreeTransformer.prototype.visitArray.call(this, children); // 134
}, // 135
visitAttributes: function (attrs) { // 136
return attrs; // 137
} // 138
}); // 139
// 140
// Combine consecutive HTML.Raws. Remove empty ones. // 141
var RawCompactingVisitor = TreeTransformer.extend(); // 142
RawCompactingVisitor.def({ // 143
visitArray: function (array) { // 144
var result = []; // 145
for (var i = 0; i < array.length; i++) { // 146
var item = array[i]; // 147
if ((item instanceof HTML.Raw) && // 148
((! item.value) || // 149
(result.length && // 150
(result[result.length - 1] instanceof HTML.Raw)))) { // 151
// two cases: item is an empty Raw, or previous item is // 152
// a Raw as well. In the latter case, replace the previous // 153
// Raw with a longer one that includes the new Raw. // 154
if (item.value) { // 155
result[result.length - 1] = HTML.Raw( // 156
result[result.length - 1].value + item.value); // 157
} // 158
} else { // 159
result.push(item); // 160
} // 161
} // 162
return result; // 163
} // 164
}); // 165
// 166
// Replace pointless Raws like `HTMl.Raw('foo')` that contain no special // 167
// characters with simple strings. // 168
var RawReplacingVisitor = TreeTransformer.extend(); // 169
RawReplacingVisitor.def({ // 170
visitRaw: function (raw) { // 171
var html = raw.value; // 172
if (html.indexOf('&') < 0 && html.indexOf('<') < 0) { // 173
return html; // 174
} else { // 175
return raw; // 176
} // 177
} // 178
}); // 179
// 180
SpacebarsCompiler.optimize = function (tree) { // 181
tree = (new OptimizingVisitor).visit(tree); // 182
tree = (new RawCompactingVisitor).visit(tree); // 183
tree = (new RawReplacingVisitor).visit(tree); // 184
return tree; // 185
}; // 186
// 187
////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function () {
////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/spacebars-compiler/codegen.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////
//
// ============================================================ // 1
// Code-generation of template tags // 2
// 3
// The `CodeGen` class currently has no instance state, but in theory // 4
// it could be useful to track per-function state, like whether we // 5
// need to emit `var self = this` or not. // 6
var CodeGen = SpacebarsCompiler.CodeGen = function () {}; // 7
// 8
var builtInBlockHelpers = SpacebarsCompiler._builtInBlockHelpers = { // 9
'if': 'Blaze.If', // 10
'unless': 'Blaze.Unless', // 11
'with': 'Spacebars.With', // 12
'each': 'Blaze.Each' // 13
}; // 14
// 15
// 16
// Mapping of "macros" which, when preceded by `Template.`, expand // 17
// to special code rather than following the lookup rules for dotted // 18
// symbols. // 19
var builtInTemplateMacros = { // 20
// `view` is a local variable defined in the generated render // 21
// function for the template in which `Template.contentBlock` or // 22
// `Template.elseBlock` is invoked. // 23
'contentBlock': 'view.templateContentBlock', // 24
'elseBlock': 'view.templateElseBlock', // 25
// 26
// Confusingly, this makes `{{> Template.dynamic}}` an alias // 27
// for `{{> __dynamic}}`, where "__dynamic" is the template that // 28
// implements the dynamic template feature. // 29
'dynamic': 'Template.__dynamic', // 30
// 31
'subscriptionsReady': 'view.templateInstance().subscriptionsReady()' // 32
}; // 33
// 34
// A "reserved name" can't be used as a <template> name. This // 35
// function is used by the template file scanner. // 36
// // 37
// Note that the runtime imposes additional restrictions, for example // 38
// banning the name "body" and names of built-in object properties // 39
// like "toString". // 40
SpacebarsCompiler.isReservedName = function (name) { // 41
return builtInBlockHelpers.hasOwnProperty(name) || // 42
builtInTemplateMacros.hasOwnProperty(name); // 43
}; // 44
// 45
var makeObjectLiteral = function (obj) { // 46
var parts = []; // 47
for (var k in obj) // 48
parts.push(BlazeTools.toObjectLiteralKey(k) + ': ' + obj[k]); // 49
return '{' + parts.join(', ') + '}'; // 50
}; // 51
// 52
_.extend(CodeGen.prototype, { // 53
codeGenTemplateTag: function (tag) { // 54
var self = this; // 55
if (tag.position === HTMLTools.TEMPLATE_TAG_POSITION.IN_START_TAG) { // 56
// Special dynamic attributes: `<div {{attrs}}>...` // 57
// only `tag.type === 'DOUBLE'` allowed (by earlier validation) // 58
return BlazeTools.EmitCode('function () { return ' + // 59
self.codeGenMustache(tag.path, tag.args, 'attrMustache') // 60
+ '; }'); // 61
} else { // 62
if (tag.type === 'DOUBLE' || tag.type === 'TRIPLE') { // 63
var code = self.codeGenMustache(tag.path, tag.args); // 64
if (tag.type === 'TRIPLE') { // 65
code = 'Spacebars.makeRaw(' + code + ')'; // 66
} // 67
if (tag.position !== HTMLTools.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE) { // 68
// Reactive attributes are already wrapped in a function, // 69
// and there's no fine-grained reactivity. // 70
// Anywhere else, we need to create a View. // 71
code = 'Blaze.View("lookup:' + tag.path.join('.') + '", ' + // 72
'function () { return ' + code + '; })'; // 73
} // 74
return BlazeTools.EmitCode(code); // 75
} else if (tag.type === 'INCLUSION' || tag.type === 'BLOCKOPEN') { // 76
var path = tag.path; // 77
// 78
if (tag.type === 'BLOCKOPEN' && // 79
builtInBlockHelpers.hasOwnProperty(path[0])) { // 80
// if, unless, with, each. // 81
// // 82
// If someone tries to do `{{> if}}`, we don't // 83
// get here, but an error is thrown when we try to codegen the path. // 84
// 85
// Note: If we caught these errors earlier, while scanning, we'd be able to // 86
// provide nice line numbers. // 87
if (path.length > 1) // 88
throw new Error("Unexpected dotted path beginning with " + path[0]); // 89
if (! tag.args.length) // 90
throw new Error("#" + path[0] + " requires an argument"); // 91
// 92
// `args` must exist (tag.args.length > 0) // 93
var dataCode = self.codeGenInclusionDataFunc(tag.args) || 'null'; // 94
// `content` must exist // 95
var contentBlock = (('content' in tag) ? // 96
self.codeGenBlock(tag.content) : null); // 97
// `elseContent` may not exist // 98
var elseContentBlock = (('elseContent' in tag) ? // 99
self.codeGenBlock(tag.elseContent) : null); // 100
// 101
var callArgs = [dataCode, contentBlock]; // 102
if (elseContentBlock) // 103
callArgs.push(elseContentBlock); // 104
// 105
return BlazeTools.EmitCode( // 106
builtInBlockHelpers[path[0]] + '(' + callArgs.join(', ') + ')'); // 107
// 108
} else { // 109
var compCode = self.codeGenPath(path, {lookupTemplate: true}); // 110
if (path.length > 1) { // 111
// capture reactivity // 112
compCode = 'function () { return Spacebars.call(' + compCode + // 113
'); }'; // 114
} // 115
// 116
var dataCode = self.codeGenInclusionDataFunc(tag.args); // 117
var content = (('content' in tag) ? // 118
self.codeGenBlock(tag.content) : null); // 119
var elseContent = (('elseContent' in tag) ? // 120
self.codeGenBlock(tag.elseContent) : null); // 121
// 122
var includeArgs = [compCode]; // 123
if (content) { // 124
includeArgs.push(content); // 125
if (elseContent) // 126
includeArgs.push(elseContent); // 127
} // 128
// 129
var includeCode = // 130
'Spacebars.include(' + includeArgs.join(', ') + ')'; // 131
// 132
// calling convention compat -- set the data context around the // 133
// entire inclusion, so that if the name of the inclusion is // 134
// a helper function, it gets the data context in `this`. // 135
// This makes for a pretty confusing calling convention -- // 136
// In `{{#foo bar}}`, `foo` is evaluated in the context of `bar` // 137
// -- but it's what we shipped for 0.8.0. The rationale is that // 138
// `{{#foo bar}}` is sugar for `{{#with bar}}{{#foo}}...`. // 139
if (dataCode) { // 140
includeCode = // 141
'Blaze._TemplateWith(' + dataCode + ', function () { return ' + // 142
includeCode + '; })'; // 143
} // 144
// 145
// XXX BACK COMPAT - UI is the old name, Template is the new // 146
if ((path[0] === 'UI' || path[0] === 'Template') && // 147
(path[1] === 'contentBlock' || path[1] === 'elseBlock')) { // 148
// Call contentBlock and elseBlock in the appropriate scope // 149
includeCode = 'Blaze._InOuterTemplateScope(view, function () { return ' // 150
+ includeCode + '; })'; // 151
} // 152
// 153
return BlazeTools.EmitCode(includeCode); // 154
} // 155
} else if (tag.type === 'ESCAPE') { // 156
return tag.value; // 157
} else { // 158
// Can't get here; TemplateTag validation should catch any // 159
// inappropriate tag types that might come out of the parser. // 160
throw new Error("Unexpected template tag type: " + tag.type); // 161
} // 162
} // 163
}, // 164
// 165
// `path` is an array of at least one string. // 166
// // 167
// If `path.length > 1`, the generated code may be reactive // 168
// (i.e. it may invalidate the current computation). // 169
// // 170
// No code is generated to call the result if it's a function. // 171
// // 172
// Options: // 173
// // 174
// - lookupTemplate {Boolean} If true, generated code also looks in // 175
// the list of templates. (After helpers, before data context). // 176
// Used when generating code for `{{> foo}}` or `{{#foo}}`. Only // 177
// used for non-dotted paths. // 178
codeGenPath: function (path, opts) { // 179
if (builtInBlockHelpers.hasOwnProperty(path[0])) // 180
throw new Error("Can't use the built-in '" + path[0] + "' here"); // 181
// Let `{{#if Template.contentBlock}}` check whether this template was // 182
// invoked via inclusion or as a block helper, in addition to supporting // 183
// `{{> Template.contentBlock}}`. // 184
// XXX BACK COMPAT - UI is the old name, Template is the new // 185
if (path.length >= 2 && // 186
(path[0] === 'UI' || path[0] === 'Template') // 187
&& builtInTemplateMacros.hasOwnProperty(path[1])) { // 188
if (path.length > 2) // 189
throw new Error("Unexpected dotted path beginning with " + // 190
path[0] + '.' + path[1]); // 191
return builtInTemplateMacros[path[1]]; // 192
} // 193
// 194
var firstPathItem = BlazeTools.toJSLiteral(path[0]); // 195
var lookupMethod = 'lookup'; // 196
if (opts && opts.lookupTemplate && path.length === 1) // 197
lookupMethod = 'lookupTemplate'; // 198
var code = 'view.' + lookupMethod + '(' + firstPathItem + ')'; // 199
// 200
if (path.length > 1) { // 201
code = 'Spacebars.dot(' + code + ', ' + // 202
_.map(path.slice(1), BlazeTools.toJSLiteral).join(', ') + ')'; // 203
} // 204
// 205
return code; // 206
}, // 207
// 208
// Generates code for an `[argType, argValue]` argument spec, // 209
// ignoring the third element (keyword argument name) if present. // 210
// // 211
// The resulting code may be reactive (in the case of a PATH of // 212
// more than one element) and is not wrapped in a closure. // 213
codeGenArgValue: function (arg) { // 214
var self = this; // 215
// 216
var argType = arg[0]; // 217
var argValue = arg[1]; // 218
// 219
var argCode; // 220
switch (argType) { // 221
case 'STRING': // 222
case 'NUMBER': // 223
case 'BOOLEAN': // 224
case 'NULL': // 225
argCode = BlazeTools.toJSLiteral(argValue); // 226
break; // 227
case 'PATH': // 228
argCode = self.codeGenPath(argValue); // 229
break; // 230
default: // 231
// can't get here // 232
throw new Error("Unexpected arg type: " + argType); // 233
} // 234
// 235
return argCode; // 236
}, // 237
// 238
// Generates a call to `Spacebars.fooMustache` on evaluated arguments. // 239
// The resulting code has no function literals and must be wrapped in // 240
// one for fine-grained reactivity. // 241
codeGenMustache: function (path, args, mustacheType) { // 242
var self = this; // 243
// 244
var nameCode = self.codeGenPath(path); // 245
var argCode = self.codeGenMustacheArgs(args); // 246
var mustache = (mustacheType || 'mustache'); // 247
// 248
return 'Spacebars.' + mustache + '(' + nameCode + // 249
(argCode ? ', ' + argCode.join(', ') : '') + ')'; // 250
}, // 251
// 252
// returns: array of source strings, or null if no // 253
// args at all. // 254
codeGenMustacheArgs: function (tagArgs) { // 255
var self = this; // 256
// 257
var kwArgs = null; // source -> source // 258
var args = null; // [source] // 259
// 260
// tagArgs may be null // 261
_.each(tagArgs, function (arg) { // 262
var argCode = self.codeGenArgValue(arg); // 263
// 264
if (arg.length > 2) { // 265
// keyword argument (represented as [type, value, name]) // 266
kwArgs = (kwArgs || {}); // 267
kwArgs[arg[2]] = argCode; // 268
} else { // 269
// positional argument // 270
args = (args || []); // 271
args.push(argCode); // 272
} // 273
}); // 274
// 275
// put kwArgs in options dictionary at end of args // 276
if (kwArgs) { // 277
args = (args || []); // 278
args.push('Spacebars.kw(' + makeObjectLiteral(kwArgs) + ')'); // 279
} // 280
// 281
return args; // 282
}, // 283
// 284
codeGenBlock: function (content) { // 285
return SpacebarsCompiler.codeGen(content); // 286
}, // 287
// 288
codeGenInclusionDataFunc: function (args) { // 289
var self = this; // 290
// 291
var dataFuncCode = null; // 292
// 293
if (! args.length) { // 294
// e.g. `{{#foo}}` // 295
return null; // 296
} else if (args[0].length === 3) { // 297
// keyword arguments only, e.g. `{{> point x=1 y=2}}` // 298
var dataProps = {}; // 299
_.each(args, function (arg) { // 300
var argKey = arg[2]; // 301
dataProps[argKey] = 'Spacebars.call(' + self.codeGenArgValue(arg) + ')'; // 302
}); // 303
dataFuncCode = makeObjectLiteral(dataProps); // 304
} else if (args[0][0] !== 'PATH') { // 305
// literal first argument, e.g. `{{> foo "blah"}}` // 306
// // 307
// tag validation has confirmed, in this case, that there is only // 308
// one argument (`args.length === 1`) // 309
dataFuncCode = self.codeGenArgValue(args[0]); // 310
} else if (args.length === 1) { // 311
// one argument, must be a PATH // 312
dataFuncCode = 'Spacebars.call(' + self.codeGenPath(args[0][1]) + ')'; // 313
} else { // 314
// Multiple positional arguments; treat them as a nested // 315
// "data mustache" // 316
dataFuncCode = self.codeGenMustache(args[0][1], args.slice(1), // 317
'dataMustache'); // 318
} // 319
// 320
return 'function () { return ' + dataFuncCode + '; }'; // 321
} // 322
// 323
}); // 324
// 325
////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function () {
////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/spacebars-compiler/compiler.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////
//
// 1
SpacebarsCompiler.parse = function (input) { // 2
// 3
var tree = HTMLTools.parseFragment( // 4
input, // 5
{ getTemplateTag: TemplateTag.parseCompleteTag }); // 6
// 7
return tree; // 8
}; // 9
// 10
SpacebarsCompiler.compile = function (input, options) { // 11
var tree = SpacebarsCompiler.parse(input); // 12
return SpacebarsCompiler.codeGen(tree, options); // 13
}; // 14
// 15
SpacebarsCompiler._TemplateTagReplacer = HTML.TransformingVisitor.extend(); // 16
SpacebarsCompiler._TemplateTagReplacer.def({ // 17
visitObject: function (x) { // 18
if (x instanceof HTMLTools.TemplateTag) { // 19
// 20
// Make sure all TemplateTags in attributes have the right // 21
// `.position` set on them. This is a bit of a hack // 22
// (we shouldn't be mutating that here), but it allows // 23
// cleaner codegen of "synthetic" attributes like TEXTAREA's // 24
// "value", where the template tags were originally not // 25
// in an attribute. // 26
if (this.inAttributeValue) // 27
x.position = HTMLTools.TEMPLATE_TAG_POSITION.IN_ATTRIBUTE; // 28
// 29
return this.codegen.codeGenTemplateTag(x); // 30
} // 31
// 32
return HTML.TransformingVisitor.prototype.visitObject.call(this, x); // 33
}, // 34
visitAttributes: function (attrs) { // 35
if (attrs instanceof HTMLTools.TemplateTag) // 36
return this.codegen.codeGenTemplateTag(attrs); // 37
// 38
// call super (e.g. for case where `attrs` is an array) // 39
return HTML.TransformingVisitor.prototype.visitAttributes.call(this, attrs); // 40
}, // 41
visitAttribute: function (name, value, tag) { // 42
this.inAttributeValue = true; // 43
var result = this.visit(value); // 44
this.inAttributeValue = false; // 45
// 46
if (result !== value) { // 47
// some template tags must have been replaced, because otherwise // 48
// we try to keep things `===` when transforming. Wrap the code // 49
// in a function as per the rules. You can't have // 50
// `{id: Blaze.View(...)}` as an attributes dict because the View // 51
// would be rendered more than once; you need to wrap it in a function // 52
// so that it's a different View each time. // 53
return BlazeTools.EmitCode(this.codegen.codeGenBlock(result)); // 54
} // 55
return result; // 56
} // 57
}); // 58
// 59
SpacebarsCompiler.codeGen = function (parseTree, options) { // 60
// is this a template, rather than a block passed to // 61
// a block helper, say // 62
var isTemplate = (options && options.isTemplate); // 63
var isBody = (options && options.isBody); // 64
// 65
var tree = parseTree; // 66
// 67
// The flags `isTemplate` and `isBody` are kind of a hack. // 68
if (isTemplate || isBody) { // 69
// optimizing fragments would require being smarter about whether we are // 70
// in a TEXTAREA, say. // 71
tree = SpacebarsCompiler.optimize(tree); // 72
} // 73
// 74
var codegen = new SpacebarsCompiler.CodeGen; // 75
tree = (new SpacebarsCompiler._TemplateTagReplacer( // 76
{codegen: codegen})).visit(tree); // 77
// 78
var code = '(function () { '; // 79
if (isTemplate || isBody) { // 80
code += 'var view = this; '; // 81
} // 82
code += 'return '; // 83
code += BlazeTools.toJS(tree); // 84
code += '; })'; // 85
// 86
code = SpacebarsCompiler._beautify(code); // 87
// 88
return code; // 89
}; // 90
// 91
SpacebarsCompiler._beautify = function (code) { // 92
if (Package.minifiers && Package.minifiers.UglifyJSMinify) { // 93
var result = Package.minifiers.UglifyJSMinify( // 94
code, // 95
{ fromString: true, // 96
mangle: false, // 97
compress: false, // 98
output: { beautify: true, // 99
indent_level: 2, // 100
width: 80 } }); // 101
var output = result.code; // 102
// Uglify interprets our expression as a statement and may add a semicolon. // 103
// Strip trailing semicolon. // 104
output = output.replace(/;$/, ''); // 105
return output; // 106
} else { // 107
// don't actually beautify; no UglifyJS // 108
return code; // 109
} // 110
}; // 111
// 112
////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['spacebars-compiler'] = {
SpacebarsCompiler: SpacebarsCompiler
};
})();
//# sourceMappingURL=spacebars-compiler.js.map