Skip to content
This repository
Browse code

initial revision, after major overhall of library

  • Loading branch information...
commit 88db47fc240f8f4881800ae984d05f8f21bb014b 0 parents
Chris Stivers authored January 13, 2012
2  .gitignore
... ...
@@ -0,0 +1,2 @@
  1
+.DS_Store*
  2
+node_modules
12  README.md
Source Rendered
... ...
@@ -0,0 +1,12 @@
  1
+# bliss
  2
+
  3
+A template engine for Node.js, inspired by Microsoft Razor and Play! Framework template engines.
  4
+
  5
+I became hooked on using Play! framework's Scala templates, which was inspired by Razor templates. I liked it so much, that I found I really wanted to use something similar on Node.js. 
  6
+
  7
+
  8
+## Details
  9
+
  10
+See [wiki](https://github.com/cstivers78/bliss/wiki) for details on use and syntax.
  11
+
  12
+See [issues](https://github.com/cstivers78/bliss/issues) for details on bugs and features.
34  bin/bliss-compile
... ...
@@ -0,0 +1,34 @@
  1
+#!/usr/local/bin/node
  2
+const _ = require('underscore');
  3
+const uglify = require('uglify-js');
  4
+const minifier = require('html-minifier');
  5
+const util = require('util');
  6
+const bliss = require('..');
  7
+
  8
+const args = _.map(process.argv,_.identity);
  9
+const nodeExec = args.shift();
  10
+const nodeScript = args.shift();
  11
+
  12
+var template = ast = code = undefined, compileStart = compileStop = 0;
  13
+
  14
+compileStart = Date.now();
  15
+template = bliss.compileFile(args.shift(),{
  16
+  context: {
  17
+    _: _
  18
+  }
  19
+});
  20
+compileStop = Date.now();
  21
+
  22
+formattedSource = uglify.uglify.gen_code(uglify.parser.parse(template.toString()),{
  23
+  beautify: true,
  24
+  indent_start: 0,
  25
+  indent_level: 2,
  26
+  space_colon: true
  27
+});
  28
+
  29
+console.log(formattedSource);
  30
+console.log('/*')
  31
+console.log(' * generated by:','Bliss','(https://github.com/cstivers78/bliss)');
  32
+console.log(' * generated on:',new Date());
  33
+console.log(' * compile time:',compileStop-compileStart,'ms');
  34
+console.log(' */')
51  bin/bliss-render
... ...
@@ -0,0 +1,51 @@
  1
+#!/usr/local/bin/node
  2
+const _ = require('underscore');
  3
+const minifier = require('html-minifier');
  4
+const util = require('util');
  5
+const bliss = require('..');
  6
+
  7
+const args = _.map(process.argv,_.identity);
  8
+const nodeExec = args.shift();
  9
+const nodeScript = args.shift();
  10
+
  11
+try {
  12
+  var template = output = undefined, compileStart = compileStop = renderStart = renderStop = 0;
  13
+  
  14
+  compileStart = Date.now();
  15
+  template = bliss.compileFile(args.shift());
  16
+  compileStop = Date.now();
  17
+  
  18
+  renderStart = Date.now();
  19
+  output = template.apply(null,args);
  20
+  renderStop = Date.now();
  21
+
  22
+  console.log(output)
  23
+  console.log('<!--')
  24
+  console.log('  generated by:','Bliss','(https://github.com/cstivers78/bliss)');
  25
+  console.log('  generated on:',new Date());
  26
+  console.log('  compile time:',compileStop-compileStart,'ms');
  27
+  console.log('  render time:',renderStop-renderStart,'ms');
  28
+  console.log('-->')
  29
+}
  30
+catch (thrown) {
  31
+  console.error('')
  32
+  console.error('[error]',thrown.toString(),'at',thrown.fileName+':'+thrown.lineNumber);
  33
+  console.error('')
  34
+  lines = template.toSource().split('\n');
  35
+  start = thrown.lineNumber - 3 > 0 ? thrown.lineNumber - 3 : 0;
  36
+  end = thrown.lineNumber + 2 < lines.length ? thrown.lineNumber+2 : lines.length;
  37
+  for(l=start; l<=end; l++) {
  38
+    line = lines[l];
  39
+    if ( line === undefined ) 
  40
+      continue;
  41
+    if ( l == thrown.lineNumber-1) {
  42
+      marker = _.map(lines[l].split(''),function(){return '-'}).join('')
  43
+      console.error('[error] ----'+marker)
  44
+      console.error('[error]',l+1,':',line); 
  45
+      console.error('[error] ----'+marker)
  46
+    }
  47
+    else {
  48
+      console.error('[error]',l+1,':',line); 
  49
+    }
  50
+  }
  51
+}
68  lib/bliss.js
... ...
@@ -0,0 +1,68 @@
  1
+var Tokenizer, Writer, compile, compileFile, fs, path, render, tokenizer,
  2
+  __slice = Array.prototype.slice;
  3
+
  4
+fs = require('fs');
  5
+
  6
+path = require('path');
  7
+
  8
+Writer = require('./writer');
  9
+
  10
+Tokenizer = require('./tokenizer');
  11
+
  12
+tokenizer = new Tokenizer();
  13
+
  14
+compile = function(source, options) {
  15
+  var context, func, tmpl, tmplParams, tmplSource, writer, _ref;
  16
+  if (options == null) options = {};
  17
+  context = (_ref = options.context) != null ? _ref : options.context = {};
  18
+  context.render = function() {
  19
+    var args, dirname, exists, filename, filepath;
  20
+    filename = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  21
+    dirname = path.dirname(options.filename);
  22
+    filepath = path.resolve(dirname, filename);
  23
+    exists = path.existsSync(filepath);
  24
+    if (!exists) {
  25
+      filepath = filepath + '.js.html';
  26
+      exists = path.existsSync(filepath);
  27
+    }
  28
+    if (exists) {
  29
+      return render.apply(null, [filepath].concat(__slice.call(args)));
  30
+    } else {
  31
+      throw 'ENOENT';
  32
+    }
  33
+  };
  34
+  writer = new Writer();
  35
+  writer.write(tokenizer.tokenize(source));
  36
+  tmplParams = writer.parameters;
  37
+  tmplSource = writer.source(context);
  38
+  func = Function.apply(null, __slice.call(tmplParams).concat([tmplSource]));
  39
+  tmpl = func.bind(context);
  40
+  tmpl.toString = func.toString.bind(func);
  41
+  tmpl.toSource = function() {
  42
+    return source;
  43
+  };
  44
+  tmpl.name = options.filename;
  45
+  return tmpl;
  46
+};
  47
+
  48
+compileFile = function(filename, options) {
  49
+  var source, template;
  50
+  source = fs.readFileSync(filename, 'utf8');
  51
+  options = {
  52
+    filename: filename
  53
+  };
  54
+  return template = compile(source, options);
  55
+};
  56
+
  57
+render = function() {
  58
+  var args, filename, template;
  59
+  filename = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  60
+  template = compileFile(filename);
  61
+  return template.apply(null, args);
  62
+};
  63
+
  64
+module.exports = {
  65
+  compile: compile,
  66
+  compileFile: compileFile,
  67
+  render: render
  68
+};
376  lib/tags.js
... ...
@@ -0,0 +1,376 @@
  1
+var Access, Anchor, AnonFunction, Block, Content, DoWhile, Else, For, Func, Group, If, Invoke, Member, NamedFunction, Parameters, ScriptBlock, Tag, Value, While,
  2
+  __hasProp = Object.prototype.hasOwnProperty,
  3
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
  4
+
  5
+Tag = (function() {
  6
+
  7
+  function Tag() {}
  8
+
  9
+  Tag.prototype.tag = true;
  10
+
  11
+  Tag.prototype.name = void 0;
  12
+
  13
+  Tag.prototype.parts = function() {
  14
+    return [];
  15
+  };
  16
+
  17
+  return Tag;
  18
+
  19
+})();
  20
+
  21
+Anchor = (function(_super) {
  22
+
  23
+  __extends(Anchor, _super);
  24
+
  25
+  Anchor.prototype.name = 'Anchor';
  26
+
  27
+  function Anchor(content) {
  28
+    this.content = content;
  29
+  }
  30
+
  31
+  Anchor.prototype.parts = function() {
  32
+    return ['@', this.content];
  33
+  };
  34
+
  35
+  return Anchor;
  36
+
  37
+})(Tag);
  38
+
  39
+Content = (function(_super) {
  40
+
  41
+  __extends(Content, _super);
  42
+
  43
+  Content.prototype.name = 'Content';
  44
+
  45
+  function Content(content) {
  46
+    this.content = content;
  47
+  }
  48
+
  49
+  Content.prototype.parts = function() {
  50
+    return [this.content];
  51
+  };
  52
+
  53
+  return Content;
  54
+
  55
+})(Tag);
  56
+
  57
+Group = (function(_super) {
  58
+
  59
+  __extends(Group, _super);
  60
+
  61
+  Group.prototype.name = 'Group';
  62
+
  63
+  function Group(content) {
  64
+    this.content = content;
  65
+  }
  66
+
  67
+  Group.prototype.parts = function() {
  68
+    return ['(', this.content, ')'];
  69
+  };
  70
+
  71
+  return Group;
  72
+
  73
+})(Tag);
  74
+
  75
+Block = (function(_super) {
  76
+
  77
+  __extends(Block, _super);
  78
+
  79
+  Block.prototype.name = 'Block';
  80
+
  81
+  function Block(content) {
  82
+    this.content = content;
  83
+  }
  84
+
  85
+  Block.prototype.parts = function() {
  86
+    return ['{', this.content, '}'];
  87
+  };
  88
+
  89
+  return Block;
  90
+
  91
+})(Tag);
  92
+
  93
+ScriptBlock = (function(_super) {
  94
+
  95
+  __extends(ScriptBlock, _super);
  96
+
  97
+  ScriptBlock.prototype.name = 'ScriptBlock';
  98
+
  99
+  function ScriptBlock(content) {
  100
+    this.content = content;
  101
+  }
  102
+
  103
+  ScriptBlock.prototype.parts = function() {
  104
+    return ['{', this.content, '}'];
  105
+  };
  106
+
  107
+  return ScriptBlock;
  108
+
  109
+})(Tag);
  110
+
  111
+If = (function(_super) {
  112
+
  113
+  __extends(If, _super);
  114
+
  115
+  If.prototype.name = 'If';
  116
+
  117
+  function If(test, block, _else) {
  118
+    this.test = test;
  119
+    this.block = block;
  120
+    this["else"] = _else;
  121
+  }
  122
+
  123
+  If.prototype.parts = function() {
  124
+    return ['if', this.test, this.block, this["else"]];
  125
+  };
  126
+
  127
+  return If;
  128
+
  129
+})(Tag);
  130
+
  131
+Else = (function(_super) {
  132
+
  133
+  __extends(Else, _super);
  134
+
  135
+  Else.prototype.name = 'Else';
  136
+
  137
+  function Else(content) {
  138
+    this.content = content;
  139
+  }
  140
+
  141
+  Else.prototype.parts = function() {
  142
+    return ['else ', this.content];
  143
+  };
  144
+
  145
+  return Else;
  146
+
  147
+})(Tag);
  148
+
  149
+For = (function(_super) {
  150
+
  151
+  __extends(For, _super);
  152
+
  153
+  For.prototype.name = 'For';
  154
+
  155
+  function For(test, block) {
  156
+    this.test = test;
  157
+    this.block = block;
  158
+  }
  159
+
  160
+  For.prototype.parts = function() {
  161
+    return ['for', this.test, this.block];
  162
+  };
  163
+
  164
+  return For;
  165
+
  166
+})(Tag);
  167
+
  168
+While = (function(_super) {
  169
+
  170
+  __extends(While, _super);
  171
+
  172
+  While.prototype.name = 'While';
  173
+
  174
+  function While(test, block) {
  175
+    this.test = test;
  176
+    this.block = block;
  177
+  }
  178
+
  179
+  While.prototype.parts = function() {
  180
+    return ['while', this.test, this.block];
  181
+  };
  182
+
  183
+  return While;
  184
+
  185
+})(Tag);
  186
+
  187
+DoWhile = (function(_super) {
  188
+
  189
+  __extends(DoWhile, _super);
  190
+
  191
+  DoWhile.prototype.name = 'DoWhile';
  192
+
  193
+  function DoWhile(block, test) {
  194
+    this.block = block;
  195
+    this.test = test;
  196
+  }
  197
+
  198
+  DoWhile.prototype.parts = function() {
  199
+    return ['do', this.block, 'while', this.test, ';'];
  200
+  };
  201
+
  202
+  return DoWhile;
  203
+
  204
+})(Tag);
  205
+
  206
+AnonFunction = (function(_super) {
  207
+
  208
+  __extends(AnonFunction, _super);
  209
+
  210
+  AnonFunction.prototype.name = 'AnonFunction';
  211
+
  212
+  function AnonFunction(args, block) {
  213
+    this.args = args;
  214
+    this.block = block;
  215
+  }
  216
+
  217
+  AnonFunction.prototype.parts = function() {
  218
+    return ['function', this.args, this.block];
  219
+  };
  220
+
  221
+  return AnonFunction;
  222
+
  223
+})(Tag);
  224
+
  225
+NamedFunction = (function(_super) {
  226
+
  227
+  __extends(NamedFunction, _super);
  228
+
  229
+  NamedFunction.prototype.name = 'NamedFunction';
  230
+
  231
+  function NamedFunction(identifier, args, block) {
  232
+    this.identifier = identifier;
  233
+    this.args = args;
  234
+    this.block = block;
  235
+  }
  236
+
  237
+  NamedFunction.prototype.parts = function() {
  238
+    return ['function ', this.identifier, this.args, this.block];
  239
+  };
  240
+
  241
+  return NamedFunction;
  242
+
  243
+})(Tag);
  244
+
  245
+Func = (function(_super) {
  246
+
  247
+  __extends(Func, _super);
  248
+
  249
+  Func.prototype.name = 'Func';
  250
+
  251
+  function Func(identifier, args, block) {
  252
+    this.identifier = identifier;
  253
+    this.args = args;
  254
+    this.block = block;
  255
+  }
  256
+
  257
+  Func.prototype.parts = function() {
  258
+    return ['function ', this.identifier, this.args, this.block];
  259
+  };
  260
+
  261
+  return Func;
  262
+
  263
+})(Tag);
  264
+
  265
+Parameters = (function(_super) {
  266
+
  267
+  __extends(Parameters, _super);
  268
+
  269
+  Parameters.prototype.name = 'Parameters';
  270
+
  271
+  function Parameters(parameters) {
  272
+    this.parameters = parameters;
  273
+  }
  274
+
  275
+  Parameters.prototype.parts = function() {
  276
+    return [this.parameters];
  277
+  };
  278
+
  279
+  return Parameters;
  280
+
  281
+})(Tag);
  282
+
  283
+Value = (function(_super) {
  284
+
  285
+  __extends(Value, _super);
  286
+
  287
+  Value.prototype.name = 'Value';
  288
+
  289
+  function Value(identifier, next) {
  290
+    this.identifier = identifier;
  291
+    this.next = next;
  292
+  }
  293
+
  294
+  Value.prototype.parts = function() {
  295
+    return [this.identifier, this.next];
  296
+  };
  297
+
  298
+  return Value;
  299
+
  300
+})(Tag);
  301
+
  302
+Member = (function(_super) {
  303
+
  304
+  __extends(Member, _super);
  305
+
  306
+  Member.prototype.name = 'Member';
  307
+
  308
+  function Member(value) {
  309
+    this.value = value;
  310
+  }
  311
+
  312
+  Member.prototype.parts = function() {
  313
+    return [this.value];
  314
+  };
  315
+
  316
+  return Member;
  317
+
  318
+})(Tag);
  319
+
  320
+Access = (function(_super) {
  321
+
  322
+  __extends(Access, _super);
  323
+
  324
+  Access.prototype.name = 'Access';
  325
+
  326
+  function Access(content) {
  327
+    this.content = content;
  328
+  }
  329
+
  330
+  Access.prototype.parts = function() {
  331
+    return ['[', this.content, ']'];
  332
+  };
  333
+
  334
+  return Access;
  335
+
  336
+})(Tag);
  337
+
  338
+Invoke = (function(_super) {
  339
+
  340
+  __extends(Invoke, _super);
  341
+
  342
+  Invoke.prototype.name = 'Invoke';
  343
+
  344
+  function Invoke(content) {
  345
+    this.content = content;
  346
+  }
  347
+
  348
+  Invoke.prototype.parts = function() {
  349
+    return ['(', this.content, ')'];
  350
+  };
  351
+
  352
+  return Invoke;
  353
+
  354
+})(Tag);
  355
+
  356
+module.exports = {
  357
+  Tag: Tag,
  358
+  Anchor: Anchor,
  359
+  Content: Content,
  360
+  Group: Group,
  361
+  Block: Block,
  362
+  ScriptBlock: ScriptBlock,
  363
+  If: If,
  364
+  Else: Else,
  365
+  For: For,
  366
+  While: While,
  367
+  DoWhile: DoWhile,
  368
+  AnonFunction: AnonFunction,
  369
+  NamedFunction: NamedFunction,
  370
+  Func: Func,
  371
+  Parameters: Parameters,
  372
+  Value: Value,
  373
+  Member: Member,
  374
+  Access: Access,
  375
+  Invoke: Invoke
  376
+};
420  lib/tokenizer.js
... ...
@@ -0,0 +1,420 @@
  1
+var ACCESS, ANCHOR, Access, Anchor, BLOCK, Block, Content, DO, DO_WHILE, DoWhile, ELSE, Else, FOR, FUNC, Failure, For, Func, GROUP, Group, IDENTIFIER, IF, INVOKE, If, Invoke, MEMBER, Member, NoMatch, PARAMETERS, Parameters, ScriptBlock, Success, TRAILING_SPACES, Tokenizer, Value, WHILE, WHITESPACE, While, failure, success, _ref, _ref2;
  2
+
  3
+_ref = require('./tags'), Anchor = _ref.Anchor, Content = _ref.Content, Group = _ref.Group, Block = _ref.Block, ScriptBlock = _ref.ScriptBlock, If = _ref.If, Else = _ref.Else, For = _ref.For, While = _ref.While, DoWhile = _ref.DoWhile, Func = _ref.Func, Parameters = _ref.Parameters, Value = _ref.Value, Member = _ref.Member, Access = _ref.Access, Invoke = _ref.Invoke;
  4
+
  5
+_ref2 = require('./validation'), Success = _ref2.Success, success = _ref2.success, Failure = _ref2.Failure, failure = _ref2.failure, NoMatch = _ref2.NoMatch;
  6
+
  7
+WHITESPACE = /^[^\n\S]+/;
  8
+
  9
+TRAILING_SPACES = /\s+$/;
  10
+
  11
+ANCHOR = /^@/;
  12
+
  13
+PARAMETERS = /^!\(/;
  14
+
  15
+IF = /^if\s*\(/;
  16
+
  17
+ELSE = /^\s*else\s*/;
  18
+
  19
+FOR = /^for\s*\(/;
  20
+
  21
+WHILE = /^while\s*\(/;
  22
+
  23
+DO = /^do\s*\{/;
  24
+
  25
+DO_WHILE = /^\s*while\s*\(/;
  26
+
  27
+FUNC = /^function(?:\s+([$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*))?\s*\(/;
  28
+
  29
+GROUP = /^\s*\(/;
  30
+
  31
+BLOCK = /^\s*\{/;
  32
+
  33
+IDENTIFIER = /^[$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*/;
  34
+
  35
+MEMBER = /^\s*\./;
  36
+
  37
+ACCESS = /^\s*\[/;
  38
+
  39
+INVOKE = /^\s*\(/;
  40
+
  41
+module.exports = Tokenizer = (function() {
  42
+
  43
+  function Tokenizer() {}
  44
+
  45
+  Tokenizer.prototype.tokenize = function(source, options) {
  46
+    if (WHITESPACE.test(source)) source = "\n" + source;
  47
+    source = source.replace(/\r/g, '').replace(TRAILING_SPACES, '');
  48
+    return this.replace(source, '@', this.Anchor.bind(this));
  49
+  };
  50
+
  51
+  Tokenizer.prototype.replace = function(source, token, callback) {
  52
+    var index, offset, result, results;
  53
+    results = [];
  54
+    while ((index = source.indexOf(token)) >= 0) {
  55
+      if (index > 0) results.push(source.slice(0, (index - 1) + 1 || 9e9));
  56
+      source = source.slice(index);
  57
+      result = callback(source);
  58
+      if (result != null) {
  59
+        if (result.success) {
  60
+          offset = result.offset;
  61
+          source = source.slice(offset);
  62
+          results.push(result.get());
  63
+        } else {
  64
+          throw result;
  65
+        }
  66
+      }
  67
+    }
  68
+    if (source.length > 0) results.push(source);
  69
+    return results;
  70
+  };
  71
+
  72
+  Tokenizer.prototype.pair = function(str, left, right) {
  73
+    var c, i, pairs, start, _len;
  74
+    pairs = 0;
  75
+    start = 0;
  76
+    for (i = 0, _len = str.length; i < _len; i++) {
  77
+      c = str[i];
  78
+      switch (c) {
  79
+        case left:
  80
+          pairs++;
  81
+          break;
  82
+        case right:
  83
+          pairs--;
  84
+          if (pairs === 0) return i + 1;
  85
+      }
  86
+    }
  87
+    return 0;
  88
+  };
  89
+
  90
+  Tokenizer.prototype.Anchor = function(chunk) {
  91
+    var offset, result, start, value;
  92
+    if (chunk[0] !== '@') return NoMatch;
  93
+    start = 1;
  94
+    chunk = chunk.slice(start);
  95
+    result = this.Parameters(chunk) || this.Escape(chunk) || this.If(chunk) || this.For(chunk) || this.While(chunk) || this.DoWhile(chunk) || this.Func(chunk) || this.Group(chunk) || this.ScriptBlock(chunk) || this.Value(chunk);
  96
+    if (result != null ? result.success : void 0) {
  97
+      offset = start + result.offset;
  98
+      value = new Anchor(result.get());
  99
+      return success(offset, value);
  100
+    } else {
  101
+      return success(1, new Content('@'));
  102
+    }
  103
+    return result;
  104
+  };
  105
+
  106
+  Tokenizer.prototype.Parameters = function(chunk) {
  107
+    var end, error, match, offset, parameters, start, value;
  108
+    if (!(match = PARAMETERS.exec(chunk))) return NoMatch;
  109
+    start = match[0].length - 1;
  110
+    chunk = chunk.slice(start);
  111
+    end = this.pair(chunk, '(', ')');
  112
+    if (end) {
  113
+      parameters = chunk.slice(1, (end - 1)).split(',').map(function(p) {
  114
+        return p.trim();
  115
+      });
  116
+      offset = start + end;
  117
+      value = new Parameters(parameters);
  118
+      return success(offset, value);
  119
+    } else {
  120
+      offset = start;
  121
+      error = 'malformed parameters';
  122
+      return failure(offset, error);
  123
+    }
  124
+  };
  125
+
  126
+  Tokenizer.prototype.Escape = function(chunk) {
  127
+    var match;
  128
+    if (!(match = ANCHOR.exec(chunk))) return NoMatch;
  129
+    return success(1, new Content('@'));
  130
+  };
  131
+
  132
+  Tokenizer.prototype.Group = function(chunk) {
  133
+    var end, error, match, offset, start, value;
  134
+    if (!(match = GROUP.exec(chunk))) return NoMatch;
  135
+    start = match[0].length - 1;
  136
+    chunk = chunk.slice(start);
  137
+    end = this.pair(chunk, '(', ')');
  138
+    if (!end) {
  139
+      offset = start;
  140
+      error = 'malformed group';
  141
+      return failure(offset, error);
  142
+    }
  143
+    offset = start + end;
  144
+    value = new Group(chunk.slice(1, (end - 1)));
  145
+    return success(offset, value);
  146
+  };
  147
+
  148
+  Tokenizer.prototype.Block = function(chunk) {
  149
+    var content, end, error, match, offset, result, results, start, value, _i, _len;
  150
+    if (!(match = BLOCK.exec(chunk))) return NoMatch;
  151
+    start = match[0].length - 1;
  152
+    chunk = chunk.slice(start);
  153
+    end = this.pair(chunk, '{', '}');
  154
+    if (!end) {
  155
+      offset = start;
  156
+      error = 'malformed block';
  157
+      return failure(offset, error);
  158
+    }
  159
+    chunk = chunk.slice(1, (end - 1));
  160
+    results = this.tokenize(chunk);
  161
+    content = [];
  162
+    for (_i = 0, _len = results.length; _i < _len; _i++) {
  163
+      result = results[_i];
  164
+      if (!result || result.failure) {
  165
+        return result;
  166
+      } else if (result.value != null) {
  167
+        content.push(result.value);
  168
+      } else {
  169
+        content.push(result);
  170
+      }
  171
+    }
  172
+    offset = start + end;
  173
+    value = new Block(content);
  174
+    return success(offset, value);
  175
+  };
  176
+
  177
+  Tokenizer.prototype.ScriptBlock = function(chunk) {
  178
+    var end, error, match, offset, start, value;
  179
+    if (!(match = BLOCK.exec(chunk))) return NoMatch;
  180
+    start = match[0].length - 1;
  181
+    chunk = chunk.slice(start);
  182
+    end = this.pair(chunk, '{', '}');
  183
+    if (!end) {
  184
+      offset = start;
  185
+      error = 'malformed block';
  186
+      return failure(offset, error);
  187
+    }
  188
+    offset = start + end;
  189
+    value = new ScriptBlock(chunk.slice(1, (end - 1)));
  190
+    return success(offset, value);
  191
+  };
  192
+
  193
+  Tokenizer.prototype.Else = function(chunk) {
  194
+    var block, error, match, offset, start, stmt, value;
  195
+    if (!(match = ELSE.exec(chunk))) return NoMatch;
  196
+    start = match[0].length;
  197
+    chunk = chunk.slice(start);
  198
+    block = this.Block(chunk);
  199
+    if (!block) {
  200
+      stmt = this.If(chunk);
  201
+      if (!stmt) {
  202
+        offset = start;
  203
+        error = 'malformed else statement';
  204
+        return failure(offset, error);
  205
+      } else if (stmt.error) {
  206
+        return stmt.error;
  207
+      } else {
  208
+        offset = start + stmt.offset;
  209
+        value = new Else(stmt.get());
  210
+        return success(offset, value);
  211
+      }
  212
+    } else {
  213
+      offset = start + block.offset;
  214
+      value = new Else(block.get());
  215
+      return success(offset, value);
  216
+    }
  217
+  };
  218
+
  219
+  Tokenizer.prototype.If = function(chunk) {
  220
+    var block, error, ifElse, match, offset, start, test, value;
  221
+    if (!(match = IF.exec(chunk))) return NoMatch;
  222
+    start = match[0].length - 1;
  223
+    chunk = chunk.slice(start);
  224
+    test = this.Group(chunk);
  225
+    if (!test || test.error) {
  226
+      offset = start;
  227
+      error = 'malformed if condition';
  228
+      return failure(offset, error, test);
  229
+    }
  230
+    chunk = chunk.slice(test.offset);
  231
+    block = this.Block(chunk);
  232
+    if (!block || block.error) {
  233
+      offset = start + test.offset;
  234
+      error = 'malformed if block';
  235
+      return failure(offset, error, block);
  236
+    }
  237
+    chunk = chunk.slice(block.offset);
  238
+    ifElse = this.Else(chunk);
  239
+    if (!ifElse) {
  240
+      offset = start + test.offset + block.offset;
  241
+      value = new If(test.get(), block.get());
  242
+      return success(offset, value);
  243
+    }
  244
+    if (ifElse.error) {
  245
+      offset = start + test.offset + block.offset + ifElse.offset;
  246
+      error = ifElse.error;
  247
+      return failure(offset, error, ifElse);
  248
+    }
  249
+    offset = start + test.offset + block.offset + ifElse.offset;
  250
+    value = new If(test.get(), block.get(), ifElse.get());
  251
+    return success(offset, value);
  252
+  };
  253
+
  254
+  Tokenizer.prototype.For = function(chunk) {
  255
+    var block, error, match, offset, start, test, value;
  256
+    if (!(match = FOR.exec(chunk))) return NoMatch;
  257
+    start = match[0].length - 1;
  258
+    chunk = chunk.slice(start);
  259
+    test = this.Group(chunk);
  260
+    if (!test || test.error) {
  261
+      offset = start;
  262
+      error = 'malformed for condition';
  263
+      return failure(offset, error, test);
  264
+    }
  265
+    chunk = chunk.slice(test.offset);
  266
+    block = this.Block(chunk);
  267
+    if (!block || block.error) {
  268
+      offset = start + test.offset;
  269
+      error = 'malformed while block';
  270
+      return failure(offset, error, block);
  271
+    }
  272
+    offset = start + test.offset + block.offset;
  273
+    value = new For(test.get(), block.get());
  274
+    return success(offset, value);
  275
+  };
  276
+
  277
+  Tokenizer.prototype.While = function(chunk) {
  278
+    var block, error, match, offset, start, test, value;
  279
+    if (!(match = WHILE.exec(chunk))) return NoMatch;
  280
+    start = match[0].length - 1;
  281
+    chunk = chunk.slice(start);
  282
+    test = this.Group(chunk);
  283
+    if (!test || test.error) {
  284
+      offset = start;
  285
+      error = 'malformed while condition';
  286
+      return failure(offset, error, test);
  287
+    }
  288
+    chunk = chunk.slice(test.offset);
  289
+    block = this.Block(chunk);
  290
+    if (!block || block.error) {
  291
+      offset = start + test.offset;
  292
+      error = 'malformed while block';
  293
+      return failure(offset, error, block);
  294
+    }
  295
+    offset = start + test.offset + block.offset;
  296
+    value = new While(test.get(), block.get());
  297
+    return success(offset, value);
  298
+  };
  299
+
  300
+  Tokenizer.prototype.DoWhile = function(chunk) {
  301
+    var block, error, match, offset, start, test, value, whileMatch, whileStart;
  302
+    if (!(match = DO.exec(chunk))) return NoMatch;
  303
+    start = match[0].length - 1;
  304
+    chunk = chunk.slice(start);
  305
+    block = this.Block(chunk);
  306
+    if (!block || block.error) {
  307
+      offset = start;
  308
+      error = 'malformed do block';
  309
+      return failure(offset, error, block);
  310
+    }
  311
+    chunk = chunk.slice(block.offset);
  312
+    test = (whileMatch = DO_WHILE.exec(chunk)) ? (whileStart = whileMatch[0].length - 1, chunk = chunk.slice(whileStart), this.Group(chunk)) : NoMatch;
  313
+    if (!test || test.error) {
  314
+      offset = start + block.offset;
  315
+      error = 'malformed do while condition';
  316
+      return failure(offset, error, test);
  317
+    }
  318
+    offset = start + block.offset + whileStart + test.offset;
  319
+    value = new DoWhile(block.get(), test.get());
  320
+    return success(offset, value);
  321
+  };
  322
+
  323
+  Tokenizer.prototype.Func = function(chunk) {
  324
+    var args, block, error, match, name, offset, start, value;
  325
+    if (!(match = FUNC.exec(chunk))) return NoMatch;
  326
+    start = match[0].length - 1;
  327
+    name = match[1];
  328
+    chunk = chunk.slice(start);
  329
+    args = this.Group(chunk);
  330
+    if (!args || args.error) {
  331
+      offset = start;
  332
+      error = 'malformed function arguments';
  333
+      return failure(offset, error, args);
  334
+    }
  335
+    chunk = chunk.slice(args.offset);
  336
+    block = this.Block(chunk);
  337
+    if (!block || block.error) {
  338
+      offset = start + args.offset;
  339
+      error = 'malformed function block';
  340
+      return failure(offset, error, block);
  341
+    }
  342
+    offset = start + args.offset + block.offset;
  343
+    value = new Func(name, args.get(), block.get());
  344
+    return success(offset, value);
  345
+  };
  346
+
  347
+  Tokenizer.prototype.Value = function(chunk) {
  348
+    var error, match, offset, result, start, value;
  349
+    if (!(match = IDENTIFIER.exec(chunk))) return NoMatch;
  350
+    start = match[0].length;
  351
+    chunk = chunk.slice(start);
  352
+    result = this.Member(chunk) || this.Access(chunk) || this.Invoke(chunk);
  353
+    if (!result) {
  354
+      offset = start;
  355
+      value = new Value(match[0]);
  356
+      return success(offset, value);
  357
+    } else if (result.failure) {
  358
+      offset = start;
  359
+      error = 'malformed value';
  360
+      return failure(offset, error, result);
  361
+    } else {
  362
+      offset = start + result.offset;
  363
+      value = new Value(match[0], result.get());
  364
+      return success(offset, value);
  365
+    }
  366
+  };
  367
+
  368
+  Tokenizer.prototype.Member = function(chunk) {
  369
+    var error, match, offset, result, start, value;
  370
+    if (!(match = MEMBER.exec(chunk))) return NoMatch;
  371
+    start = match[0].length;
  372
+    chunk = chunk.slice(start);
  373
+    result = this.Value(chunk);
  374
+    if (!result || result.failure) {
  375
+      offset = start;
  376
+      error = 'malformed member access';
  377
+      return failure(offset, error, result);
  378
+    }
  379
+    offset = start + result.offset;
  380
+    value = new Member(result.get());
  381
+    return success(offset, value);
  382
+  };
  383
+
  384
+  Tokenizer.prototype.Access = function(chunk) {
  385
+    var end, error, match, offset, results, start, value;
  386
+    if (!(match = ACCESS.exec(chunk))) return NoMatch;
  387
+    start = match[0].length - 1;
  388
+    chunk = chunk.slice(start);
  389
+    end = this.pair(chunk, '[', ']');
  390
+    if (!end) {
  391
+      offset = start;
  392
+      error = 'malformed array access';
  393
+      return failure(offset, error);
  394
+    }
  395
+    results = this.replace(chunk.slice(start + 1, (end - 1)), 'function', this.Function.bind(this));
  396
+    offset = start + end;
  397
+    value = new Access(results);
  398
+    return success(offset, value);
  399
+  };
  400
+
  401
+  Tokenizer.prototype.Invoke = function(chunk) {
  402
+    var end, error, match, offset, results, start, value;
  403
+    if (!(match = INVOKE.exec(chunk))) return NoMatch;
  404
+    start = match[0].length - 1;
  405
+    chunk = chunk.slice(start);
  406
+    end = this.pair(chunk, '(', ')');
  407
+    if (!end) {
  408
+      offset = start;
  409
+      error = 'malformed group';
  410
+      return failure(offset, error);
  411
+    }
  412
+    results = this.replace(chunk.slice(start + 1, (end - 1)), 'function', this.Func.bind(this));
  413
+    offset = start + end;
  414
+    value = new Invoke(results);
  415
+    return success(offset, value);
  416
+  };
  417
+
  418
+  return Tokenizer;
  419
+
  420
+})();
78  lib/validation.js
... ...
@@ -0,0 +1,78 @@
  1
+var Failure, NoMatch, Success, Validation, failure, success,
  2
+  __hasProp = Object.prototype.hasOwnProperty,
  3
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
  4
+
  5
+Validation = (function() {
  6
+
  7
+  function Validation() {}
  8
+
  9
+  Validation.success = false;
  10
+
  11
+  Validation.failure = false;
  12
+
  13
+  Validation.prototype.get = function() {
  14
+    return;
  15
+  };
  16
+
  17
+  return Validation;
  18
+
  19
+})();
  20
+
  21
+Success = (function(_super) {
  22
+
  23
+  __extends(Success, _super);
  24
+
  25
+  function Success(offset, value) {
  26
+    this.offset = offset;
  27
+    this.value = value;
  28
+    this.success = true;
  29
+  }
  30
+
  31
+  Success.prototype.get = function() {
  32
+    return this.value;
  33
+  };
  34
+
  35
+  Success.prototype.toString = function() {
  36
+    return toString('Success[', this.offset, '] := ', this.value);
  37
+  };
  38
+
  39
+  return Success;
  40
+
  41
+})(Validation);
  42
+
  43
+success = function(offset, value) {
  44
+  return new Success(offset, value);
  45
+};
  46
+
  47
+Failure = (function(_super) {
  48
+
  49
+  __extends(Failure, _super);
  50
+
  51
+  function Failure(offset, error, cause) {
  52
+    this.offset = offset;
  53
+    this.error = error;
  54
+    this.cause = cause;
  55
+    this.failure = true;
  56
+  }
  57
+
  58
+  Failure.prototype.toString = function() {
  59
+    return toString('Failure[', this.offset, '] := ', this.error);
  60
+  };
  61
+
  62
+  return Failure;
  63
+
  64
+})(Validation);
  65
+
  66
+failure = function(offset, error) {
  67
+  return new Failure(offset, error);
  68
+};
  69
+
  70
+NoMatch = void 0;
  71
+
  72
+module.exports = {
  73
+  Success: Success,
  74
+  success: success,
  75
+  Failure: Failure,
  76
+  failure: failure,
  77
+  NoMatch: NoMatch
  78
+};
151  lib/writer.js
... ...
@@ -0,0 +1,151 @@
  1
+var Writer;
  2
+
  3
+module.exports = Writer = (function() {
  4
+
  5
+  function Writer() {
  6
+    this.buffer = [];
  7
+    this.parameters = [];
  8
+  }
  9
+
  10
+  Writer.prototype.code = function(code) {
  11
+    return this.buffer.push(code);
  12
+  };
  13
+
  14
+  Writer.prototype.text = function(text) {
  15
+    text = text.replace(/\n/g, '\\n');
  16
+    this.buffer.push('write("');
  17
+    this.buffer.push(text);
  18
+    return this.buffer.push('");\n');
  19
+  };
  20
+
  21
+  Writer.prototype.write = function(elements) {
  22
+    var element, _i, _len, _results;
  23
+    _results = [];
  24
+    for (_i = 0, _len = elements.length; _i < _len; _i++) {
  25
+      element = elements[_i];
  26
+      if (element != null) {
  27
+        if (element.tag != null) {
  28
+          _results.push(this.tag(element));
  29
+        } else {
  30
+          _results.push(this.text(element));
  31
+        }
  32
+      } else {
  33
+        _results.push(void 0);
  34
+      }
  35
+    }
  36
+    return _results;
  37
+  };
  38
+
  39
+  Writer.prototype.tag = function(tag) {
  40
+    var c, index, _i, _len, _ref;
  41
+    switch (tag.name) {
  42
+      case 'Anchor':
  43
+        return this.tag(tag.content);
  44
+      case 'Content':
  45
+        return this.text(tag.content);
  46
+      case 'Block':
  47
+        this.code('{');
  48
+        if ((tag.content != null) && Array.isArray(tag.content)) {
  49
+          _ref = tag.content;
  50
+          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  51
+            c = _ref[_i];
  52
+            if ((c != null ? c.tag : void 0) != null) {
  53
+              this.tag(c);
  54
+            } else if (c != null) {
  55
+              this.text(c);
  56
+            }
  57
+          }
  58
+        }
  59
+        return this.code('}');
  60
+      case 'Value':
  61
+        index = this.values;
  62
+        this.code('__tmp=');
  63
+        this.value(tag);
  64
+        this.code(';');
  65
+        this.code('if(__tmp !== undefined || __tmp !== null){');
  66
+        this.code('write(__tmp);');
  67
+        return this.code('}');
  68
+      case 'Parameters':
  69
+        return this.parameters = tag.parameters;
  70
+      case 'Func':
  71
+        this.code('function');
  72
+        this.tag(tag.args);
  73
+        this.code('{');
  74
+        this.code('var __out=[],write=__out.push.bind(__out),__tmp=0;');
  75
+        this.tag(tag.block);
  76
+        this.code('return __out.join(\'\');');
  77
+        return this.code('}');
  78
+      default:
  79
+        if (tag.parts != null) return this.parts(tag.parts());
  80
+    }
  81
+  };
  82
+
  83
+  Writer.prototype.parts = function(parts) {
  84
+    var part, _i, _len, _results;
  85
+    _results = [];
  86
+    for (_i = 0, _len = parts.length; _i < _len; _i++) {
  87
+      part = parts[_i];
  88
+      if (part != null) {
  89
+        if (part.tag != null) {
  90
+          _results.push(this.tag(part));
  91
+        } else {
  92
+          _results.push(this.code(part));
  93
+        }
  94
+      } else {
  95
+        _results.push(void 0);
  96
+      }
  97
+    }
  98
+    return _results;
  99
+  };
  100
+
  101
+  Writer.prototype.value = function(value) {
  102
+    var tag;
  103
+    this.code(value.identifier);
  104
+    if (value.next != null) {
  105
+      tag = value.next;
  106
+      switch (tag.name) {
  107
+        case 'Access':
  108
+          return this.group(tag, '[', ']');
  109
+        case 'Invoke':
  110
+          return this.group(tag, '(', ')');
  111
+        case 'Member':
  112
+          this.code('.');
  113
+          return this.value(tag.value);
  114
+      }
  115
+    }
  116
+  };
  117
+
  118
+  Writer.prototype.group = function(tag, open, close) {
  119
+    var c, _i, _len, _ref;
  120
+    if (open != null) this.code(open);
  121
+    if ((tag.content != null) && Array.isArray(tag.content)) {
  122
+      _ref = tag.content;
  123
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  124
+        c = _ref[_i];
  125
+        if ((c != null ? c.tag : void 0) != null) {
  126
+          this.tag(c);
  127
+        } else if (c != null) {
  128
+          this.code(c);
  129
+        }
  130
+      }
  131
+    }
  132
+    if (close != null) return this.code(close);
  133
+  };
  134
+
  135
+  Writer.prototype.source = function(context) {
  136
+    var ctx, k, v;
  137
+    if (context == null) context = {};
  138
+    ctx = [];
  139
+    for (k in context) {
  140
+      v = context[k];
  141
+      ctx.push(',');
  142
+      ctx.push(k);
  143
+      ctx.push('=this.');
  144
+      ctx.push(k);
  145
+    }
  146
+    return ['var __out=[],write=__out.push.bind(__out),__tmp=0', ctx.join(''), ';', this.buffer.join(''), 'return __out.join(\'\');'].join('');
  147
+  };
  148
+
  149
+  return Writer;
  150
+
  151
+})();
28  package.json
... ...
@@ -0,0 +1,28 @@
  1
+{
  2
+  "author": "Chris Stivers <chris@stivers.us>",
  3
+  "name": "bliss",
  4
+  "description": "Embedded JavaScript templates based on .NET Razor and Play! Framework templates.",
  5
+  "version": "0.1.0",
  6
+  "repository": {
  7
+    "type": "git",
  8
+    "url": "https://github.com/cstivers78/bliss"
  9
+  },
  10
+  "main": "lib/bliss.js",
  11
+  "bin": {
  12
+    "bliss-compile": "./bin/bliss-compile",
  13
+    "bliss-render": "./bin/bliss-render"
  14
+  },
  15
+  "scripts": {
  16
+    "clean": "rm -rf lib",