-
Notifications
You must be signed in to change notification settings - Fork 3
/
tokens.js
204 lines (180 loc) · 6.66 KB
/
tokens.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
var sys = require('sys')
// Template module dependencies
, E = require('./exceptions')
, tags = require('./tags')
// Globals
, VAR_TOKEN_MISSING_WARNING = global.SETTINGS && SETTINGS.DEBUG ? "<span style='color:red'>Missing variable '%s'</span>" : ""
, STRING_TOKEN = 1
, BLOCK_TOKEN = 2
, BLOCK_TOKEN_END = 3
, VAR_TOKEN = 4
// Matches opening and closing tag delimiters
, blockTokenRegex = /^\{% *| *%\}$/g
, commentTokenRegex = /^\{# *| *#\}$/g
, varTokenRegex = /^\{\{ *| *\}\}$/g
// A valid block-tag name
, validBlockNameRegex = /^\w+/
// A valid variable-tag name
, validVarNameRegex = /^[A-Za-z0-9_.]+/
// Shorthands
, isArray = Array.isArray;
/** @class
* A Block Token (not to be confused with the 'block' tag)
* is a token defined by the {% and %} delimiters.
* Block tokens are template tags with a specific logic.
* The BlockToken class is responsible for deremining the tag type,
* overriding the default _render_ and _compile_ functions and
* calling the tag function on itself.
* The tag logic is defined in the ./tags module as a function,
* or can be defined by the user.
* Block tokens whose tag name begins with 'end' are considered
* closing tags. Example: {% if ... %} ... {% endif %}. This means
* that you can't make a tag with a name starting with 'end'
*
* @param {String} The tag name
* @param {String} Parameters passed to the tag
* @param {Object} A reference to the token/template that contains this token
* @param {Object} A reference to the main template
*/
var BlockToken = function( tagname, params, parent, main ){
var tag;
// Closing tags begin with "end" like in {% for %}{% endfor %}.
if( tagname.indexOf("end") === 0 ) {
tagname = tagname.substr(3);
if( !(tag = tags[ tagname ]) || !tag.expectsClosing )
throw new E.TSE("Unknown closing tag '%s'".fmt( tagname ));
if( parent.tagname !== tagname )
throw new E.TSE(
"Unexpected closing tag 'end%s'; Expecting 'end%s'".fmt( tagname, parent.tagname ));
this.type = BLOCK_TOKEN_END;
}
else {
// Check if tag exists
if( !( tag = tags[ tagname ] ) )
throw new E.TSE( "Tag '%s' is not defined".fmt( tagname ) );
this.type = BLOCK_TOKEN;
this.tagname = tagname;
// Copy some tag flags
tag.expectsClosing && ( this.children = [] );
tag.renderFunction && ( this.render = tag.renderFunction );
tag.compileFunction && ( this.compile = tag.compileFunction );
// Apply the tag function to this token
tag.call( this, params, parent, main );
}
}
BlockToken.prototype = {
/**
* This is the default block token render method
* It just loops through the token's children and appends
* whatever output their render method returns.
* Tags can everride this method for specific needs
* and can call this function by using:
* {{{
* Object.getPrototypeOf(this).render.call(this,arguments)
* }}}
*
* If the children parameter is passed (it's supposed to be an
* array of tokens) it will be used instead of this token's children.
*
* @param {Object} the context to use when rendering
* @param {Array} children to render instead of this token's children
* @return {Array} the output.
*/
render: function( context, children ) {
var i, j, r, token
, output = []
, tokens = children || this.children || [];
for( i=0, j=tokens.length; i<j; ++i ) {
token = tokens[ i ];
// It's a token
if( token.render )
output = output.concat( token.render( context ) );
// It's a string
else
output = output.concat( token );
}
return output;
}
/**
* Compile is called after all of the token's children have been parsed
* and created. It normally doesn't do anything, but can be overridden by
* the specific tag implementation. This method is executed during the parsing
* phase.
*/
, compile: function(){}
}
/**
* A Var Token is a token defined by the {{ and }} delimiters.
* Var Tokens are template tags that output context variables.
* The var token is created by passing it a string which is the
* context key of the value you need to putput. The key can be
* supplied in dot-separated form. In this case we look for
* a context variable names as the first segment and then try to
* get the following segments as properties (if the variable) is
* an object.
*
* @param {String} the context key
*/
var VarToken = function( string ) {
// TODO: Add executing functions
this.type = VAR_TOKEN;
this.lookup = string;
}
VarToken.prototype = {
/**
* Outputs the context variable whose key is
* in this var token. If no such key exists in
* the context nothing is output and no errors
* are raised. In debug mode a warning is rendered.
*
* @param {Object} the context
* @return {String} the value of the context variable.
*/
render: function( context ){
var output = context.get( this.lookup )
if( output === null )
return [VAR_TOKEN_MISSING_WARNING.fmt( this.lookup )];
return [output];
}
}
/**
* A helper function used by Template.prototype.parse to
* create tokens. It parses the raw token string, replaces
* the delimiters and passes it on to the appropriate token
* function. Comment tokens are ignored, and strings (non-tokens)
* are returned unchanged.
*
* @param {String} the raw string
* @param {Object} the parent of the token we're creating
* @param {Object} a reference to the main template
* @return {Mixed} VarToken, BlockToken, String or null
*/
var makeToken = function( string, parent, main ){
// Create a block token
if( string.match( blockTokenRegex ) ) {
var matches;
string = string.replace( blockTokenRegex, "" );
if( !(matches = string.match( validBlockNameRegex ) ) )
throw new E.TSE( "Invalid tag formation: '%s'".fmt(string) );
return new BlockToken(
matches[0], string.replace( validBlockNameRegex, "" ).trim(), parent, main );
}
// Create a var token
else if( string.match( varTokenRegex ) ) {
string = string.replace( varTokenRegex, "" );
if( !string.match( validVarNameRegex ) ){
throw new E.TSE( "'%s' is not a valid variable name".fmt( string ) );
}
return new VarToken( string );
}
// Comments are ignored
else if( string.match( commentTokenRegex ) )
return null;
// No need to turn strings into tokens
else
return string;
}
exports.makeToken = makeToken;
exports.setVarMissingWarning = function( bool ){
VAR_TOKEN_MISSING_WARNING = bool ? "<span style='color:red'>Missing variable '%s'</span>" : "";
}