riot_js-rails 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3336fa961ac6cc151f8e325563c2c8def675d4a4
4
- data.tar.gz: 84134a612de5ae749d084399be8d23c4fd4084de
3
+ metadata.gz: 80b0ba35335567e7fd7bbcf8817e9065ae6a22b3
4
+ data.tar.gz: 4a1c402cab48fbd2abde9560987698c3e79f9873
5
5
  SHA512:
6
- metadata.gz: f985f48151efe46b088871a08232459e6dd29bb2b1d869ccd7f02fb2c99c88d86d99d3c9377a820d41cc534a94936fa946d7ff67fd62750009509a919e199e9f
7
- data.tar.gz: d12a75cc7b410a7ece60a27aff51ffb7f3fe6edde10ccd1f9e5bfcfb1fe10ecc457880db3a22ab96a23d46193d5880ac84bc49fb336fcc61fd7eca8335a35010
6
+ metadata.gz: 8c92c478dc4c74f8a0e34301173c4f3fa4fa6e36b9b2cee0fe9e93a269debb60ba0a655079a9352be55bb42c2dc8a36c7cfa33bf7487b5deb1fdaccca0fa93c7
7
+ data.tar.gz: e589230ca36f8124f435f7ec815a5850519ad0a2c02d5dc34b02286671f1e42c6ce630005bdfb0181ade0e56551cad4699ded2e7f407e63d189eb7538c103596
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
  /.idea
11
11
  /*.gem
12
12
  /.editorconfig
13
+ /login_to_vagrant.sh
@@ -16,8 +16,10 @@ module RiotJs
16
16
  @context.metadata.merge(data: data)
17
17
  end
18
18
 
19
- def self.register_self(app)
20
- app.assets.register_engine '.tag', self, mime_type: 'application/javascript'
19
+ def self.register_self(config)
20
+ config.assets.configure do |env|
21
+ env.register_engine '.tag', self, mime_type: 'application/javascript'
22
+ end
21
23
  end
22
24
 
23
25
  private
@@ -9,7 +9,7 @@ module RiotJs
9
9
  config.riot.node_paths = []
10
10
 
11
11
  initializer :setup_sprockets do |app|
12
- Processor.register_self app
12
+ Processor.register_self config
13
13
 
14
14
  if defined?(::Haml)
15
15
  require 'tilt/haml'
@@ -20,7 +20,7 @@ module RiotJs
20
20
  initializer :add_helpers do |app|
21
21
  helpers = %q{ include ::RiotJs::Rails::Helper }
22
22
  ::ActionView::Base.module_eval(helpers)
23
- ::Rails.application.assets.context_class.class_eval(helpers)
23
+ ::Rails.application.config.assets.context_class.class_eval(helpers)
24
24
  end
25
25
 
26
26
  config.after_initialize do |app|
@@ -1,5 +1,5 @@
1
1
  module RiotJs
2
2
  module Rails
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Brackets support for the node.js version of the riot-compiler
3
+ * @module
4
+ */
5
+
6
+ /**
7
+ * Matches valid, multiline JavaScript comments in almost all its forms.
8
+ * @const {RegExp}
9
+ * @static
10
+ */
11
+ var R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g
12
+
13
+ /**
14
+ * Matches single and double quoted strings. Don't care about inner EOLs, so it
15
+ * can be used to match HTML strings, but skips escaped quotes as JavaScript does.
16
+ * Useful to skip strings in values with expressions, e.g. `name={ 'John\'s' }`.
17
+ * @const {RegExp}
18
+ * @static
19
+ */
20
+ var R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g
21
+
22
+ /**
23
+ * The {@link module:brackets.R_STRINGS|R_STRINGS} source combined with sources of
24
+ * regexes matching division operators and literal regexes, for use with the RegExp
25
+ * constructor. The resulting regex captures in `$1` and `$2` a single slash, depending
26
+ * if it matches a division operator ($1) or a literal regex ($2).
27
+ * @const {string}
28
+ * @static
29
+ */
30
+ var S_QBLOCKS = R_STRINGS.source + '|' +
31
+ /(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
32
+ /\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source
33
+
34
+ /**
35
+ * Hash of regexes for matching JavaScript brackets out of quoted strings and literal
36
+ * regexes. Used by {@link module:brackets.split|split}, these are heavy, but their
37
+ * performance is acceptable.
38
+ * @const {object}
39
+ */
40
+ var FINDBRACES = {
41
+ '(': RegExp('([()])|' + S_QBLOCKS, 'g'),
42
+ '[': RegExp('([[\\]])|' + S_QBLOCKS, 'g'),
43
+ '{': RegExp('([{}])|' + S_QBLOCKS, 'g')
44
+ }
45
+
46
+ /**
47
+ * The predefined riot brackets
48
+ * @const {string}
49
+ * @default
50
+ */
51
+ var DEFAULT = '{ }'
52
+
53
+ // Pre-made string and regexes for the default brackets
54
+ var _pairs = [
55
+ '{', '}',
56
+ '{', '}',
57
+ /{[^}]*}/,
58
+ /\\([{}])/g,
59
+ /\\({)|{/g,
60
+ RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, 'g'),
61
+ DEFAULT
62
+ ]
63
+
64
+ // Pre-made string and regexes for the last bracket pair
65
+ var _cache = []
66
+
67
+ /*
68
+ Private functions
69
+ ---------------------------------------------------------------------------
70
+ */
71
+
72
+ /**
73
+ * Rewrite a regex with the default brackets replaced with the custom ones.
74
+ *
75
+ * @param {RegExp} re - RegExp with the default riot brackets
76
+ * @returns {RegExp} The new regex with the default brackets replaced.
77
+ */
78
+ function _rewrite (re) {
79
+ return RegExp(
80
+ re.source.replace(/{/g, _cache[2]).replace(/}/g, _cache[3]), re.global ? 'g' : ''
81
+ )
82
+ }
83
+
84
+ /*
85
+ Exported methods and properties
86
+ ---------------------------------------------------------------------------
87
+ */
88
+
89
+ module.exports = {
90
+ R_STRINGS: R_STRINGS,
91
+ R_MLCOMMS: R_MLCOMMS,
92
+ S_QBLOCKS: S_QBLOCKS
93
+ }
94
+
95
+ /**
96
+ * Splits the received string in its template text and expression parts using
97
+ * balanced brackets detection to avoid require escaped brackets from the users.
98
+ *
99
+ * _For internal use by the riot-compiler._
100
+ *
101
+ * @param {string} str - Template source to split, can be one expression
102
+ * @param {number} _ - unused
103
+ * @param {Array} _bp - Info of custom brackets to use
104
+ * @returns {Array} Array of alternating template text and expressions.
105
+ * If _str_ has one unique expression, returns two elements: `["", expression]`.
106
+ */
107
+ module.exports.split = function split (str, _, _bp) {
108
+ /*
109
+ Template text is easy: closing brackets are ignored, all we have to do is find
110
+ the first unescaped bracket. The real work is with the expressions...
111
+
112
+ Expressions are not so easy. We can already ignore opening brackets, but finding
113
+ the correct closing bracket is tricky.
114
+ Strings and regexes can contain almost any combination of characters and we
115
+ can't deal with these complexity with our regexes, so let's hide and ignore
116
+ these. From there, all we need is to detect the bracketed parts and skip
117
+ them, as they contains most of the common characters used by riot brackets.
118
+ With that, we have a 90% reliability in the detection, although (hope few) some
119
+ custom brackets still requires to be escaped.
120
+ */
121
+ var
122
+ parts = [], // holds the resulting parts
123
+ match, // reused by both outer and nested searches
124
+ isexpr, // we are in ttext (0) or expression (1)
125
+ start, // start position of current template or expression
126
+ pos, // current position (exec() result)
127
+ re = _bp[6] // start with *updated* regex for opening bracket
128
+
129
+ isexpr = start = re.lastIndex = 0 // re is reused, we must reset lastIndex
130
+
131
+ while (match = re.exec(str)) {
132
+
133
+ pos = match.index
134
+
135
+ if (isexpr) {
136
+ /*
137
+ $1: optional escape character,
138
+ $2: opening js bracket `{[(`,
139
+ $3: closing riot bracket,
140
+ $4 & $5: qblocks
141
+ */
142
+ if (match[2]) { // if have a javascript opening bracket,
143
+ re.lastIndex = skipBraces(str, match[2], re.lastIndex)
144
+ continue // skip the bracketed block and loop
145
+ }
146
+ if (!match[3]) { // if don't have a closing bracket
147
+ continue // search again
148
+ }
149
+ }
150
+
151
+ /*
152
+ At this point, we expect an _unescaped_ openning bracket in $2 for text,
153
+ or a closing bracket in $3 for expression. $1 may be an backslash.
154
+ */
155
+ if (!match[1]) { // ignore it if have an escape char
156
+ unescapeStr(str.slice(start, pos)) // push part, even if empty
157
+ start = re.lastIndex // next position is the new start
158
+ re = _bp[6 + (isexpr ^= 1)] // switch mode and swap regexp
159
+ re.lastIndex = start // update the regex pointer
160
+ }
161
+ }
162
+
163
+ if (str && start < str.length) { // push remaining part, if we have one
164
+ unescapeStr(str.slice(start))
165
+ }
166
+
167
+ return parts
168
+
169
+ /*
170
+ Inner Helpers for _split()
171
+ */
172
+
173
+ /**
174
+ * Stores the processed string in the array `parts`.
175
+ * Unescape escaped brackets from expressions.
176
+ *
177
+ * @param {string} s - can be template text or an expression
178
+ */
179
+ function unescapeStr (s) {
180
+ if (isexpr) {
181
+ parts.push(s && s.replace(_bp[5], '$1'))
182
+ } else {
183
+ parts.push(s)
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Find the closing JS bracket for the current block in the given string.
189
+ * Skips strings, regexes, and other inner blocks.
190
+ *
191
+ * @param {string} s - The searched buffer
192
+ * @param {string} ch - Opening bracket character
193
+ * @param {number} ix - Position inside `str` following the opening bracket
194
+ * @returns {number} Position following the closing bracket.
195
+ *
196
+ * @throws Will throw "Unbalanced brackets in ..." if the closing bracket is not found.
197
+ */
198
+ function skipBraces (s, ch, ix) {
199
+ var
200
+ mm,
201
+ rr = FINDBRACES[ch]
202
+
203
+ rr.lastIndex = ix
204
+ ix = 1
205
+ while (mm = rr.exec(s)) {
206
+ if (mm[1] &&
207
+ !(mm[1] === ch ? ++ix : --ix)) break
208
+ }
209
+
210
+ if (ix) {
211
+ throw new Error('Unbalanced brackets in ...`' + s.slice(start) + '`?')
212
+ }
213
+ return rr.lastIndex
214
+ }
215
+ }
216
+
217
+ var
218
+ INVALIDCH = /[\x00-\x1F<>a-zA-Z0-9'",;\\]/, // invalid characters for brackets
219
+ ESCAPEDCH = /(?=[[\]()*+?.^$|])/g // this characters must be escaped
220
+
221
+ /**
222
+ * Returns an array with information for the given brackets using a cache for the
223
+ * last custom brackets pair required by the caller.
224
+ *
225
+ * _For internal use by the riot-compiler._
226
+ *
227
+ * @param {string} [pair=DEFAULT] - If used, take this pair as base
228
+ * @returns {Array} Information about the given brackets, in internal format.
229
+ *
230
+ * @throws Will throw "Unsupported brackets ..." if _pair_ contains invalid characters
231
+ * or is not separated by one space.
232
+ */
233
+ module.exports.array = function array (pair) {
234
+ if (!pair || pair === DEFAULT) return _pairs
235
+
236
+ if (_cache[8] !== pair) {
237
+ _cache = pair.split(' ')
238
+
239
+ if (_cache.length !== 2 || INVALIDCH.test(pair)) {
240
+ throw new Error('Unsupported brackets "' + pair + '"')
241
+ }
242
+ _cache = _cache.concat(pair.replace(ESCAPEDCH, '\\').split(' '))
243
+
244
+ _cache[4] = _rewrite(_cache[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4])
245
+ _cache[5] = _rewrite(/\\({|})/g)
246
+ _cache[6] = _rewrite(_pairs[6]) // for _split()
247
+ _cache[7] = RegExp('\\\\(' + _cache[3] + ')|([[({])|(' + _cache[3] + ')|' + S_QBLOCKS, 'g')
248
+ _cache[8] = pair
249
+ }
250
+
251
+ return _cache
252
+ }
@@ -1,16 +1,974 @@
1
- var compilerPath = require('path').join(__dirname, './compiler_core.js')
1
+ /**
2
+ * The riot-compiler WIP
3
+ *
4
+ * @module compiler
5
+ * @version WIP
6
+ * @license MIT
7
+ * @copyright 2015 Muut Inc. + contributors
8
+ */
9
+ 'use strict'
2
10
 
3
- global.riot = require(process.env.RIOT || '../riot')
4
- global.riot.parsers = require('./parsers')
11
+ var brackets = require('./brackets')
12
+ var parsers = require('./parsers')
13
+ var path = require('path')
5
14
 
6
- // Evaluate the compiler shared functions in this context
7
- /*eslint-disable*/
8
- eval(require('fs').readFileSync(compilerPath, 'utf8'))
9
- /*eslint-enable*/
15
+ /**
16
+ * Source for creating regexes matching valid quoted, single-line JavaScript strings.
17
+ * It recognizes escape characters, including nested quotes and line continuation.
18
+ * @const {string}
19
+ */
20
+ var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source
21
+
22
+ /**
23
+ * Source of {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} for creating regexes
24
+ * matching multiline HTML strings and/or skip literal strings inside expressions.
25
+ * @const {string}
26
+ * @todo Bad thing. It recognizes escaped quotes (incorrect for HTML strings) and multiline
27
+ * strings without line continuation `'\'` (incorrect for expressions). Needs to be fixed
28
+ * ASAP but the current logic requires it to parse expressions inside attribute values :[
29
+ */
30
+ var S_STRINGS = brackets.R_STRINGS.source
31
+
32
+ /**
33
+ * Matches pairs attribute=value, both quoted and unquoted.
34
+ * Names can contain almost all iso-8859-1 character set.
35
+ * Used by {@link module:compiler~parseAttribs|parseAttribs}, assume hidden
36
+ * expressions and compact spaces (no EOLs).
37
+ * @const {RegExp}
38
+ */
39
+ var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g
40
+
41
+ /**
42
+ * Matches valid HTML comments (to remove) and JS strings/regexes (to skip).
43
+ * Used by [cleanSource]{@link module:compiler~cleanSource}.
44
+ * @const {RegExp}
45
+ */
46
+ var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g')
47
+
48
+ /**
49
+ * HTML_TAGS matches opening and self-closing tags, not the content.
50
+ * Used by {@link module:compiler~_compileHTML|_compileHTML} after hidding
51
+ * the expressions.
52
+ *
53
+ * 2016-01-18: exclude `'\s'` from attr capture to avoid unnecessary call to
54
+ * {@link module:compiler~parseAttribs|parseAttribs}
55
+ * @const {RegExp}
56
+ */
57
+ var HTML_TAGS = /<([-\w]+)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
58
+
59
+ /**
60
+ * Matches boolean HTML attributes, prefixed with `"__"` in the riot tag definition.
61
+ * Used by {@link module:compiler~parseAttribs|parseAttribs} with lowercase names.
62
+ * With a long list like this, a regex is faster than `[].indexOf` in most browsers.
63
+ * @const {RegExp}
64
+ * @see [attributes.md](https://github.com/riot/compiler/blob/dev/doc/attributes.md)
65
+ */
66
+ var BOOL_ATTRS = RegExp(
67
+ '^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' +
68
+ 'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' +
69
+ 'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' +
70
+ 'selected|sortable|truespeed|typemustmatch)$')
71
+
72
+ /**
73
+ * These attributes give error when parsed on browsers with an expression in its value.
74
+ * Ex: `<img src={ exrp_value }>`.
75
+ * Used by {@link module:compiler~parseAttribs|parseAttribs} with lowercase names only.
76
+ * @const {Array}
77
+ * @see [attributes.md](https://github.com/riot/compiler/blob/dev/doc/attributes.md)
78
+ */
79
+ var RIOT_ATTRS = ['style', 'src', 'd']
80
+
81
+ /**
82
+ * HTML5 void elements that cannot be auto-closed.
83
+ * @const {RegExp}
84
+ * @see {@link http://www.w3.org/TR/html-markup/syntax.html#syntax-elements}
85
+ * @see {@link http://www.w3.org/TR/html5/syntax.html#void-elements}
86
+ */
87
+ var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/
88
+
89
+ /**
90
+ * Matches `<pre>` elements to hide its content ($1) from whitespace compactation.
91
+ * Used by {@link module:compiler~_compileHTML|_compileHTML} after processing the
92
+ * attributes and the self-closing tags.
93
+ * @const {RegExp}
94
+ */
95
+ var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi
96
+
97
+ /**
98
+ * Matches values of the property 'type' of input elements which cause issues with
99
+ * invalid values in some browsers. These are compiled with an invalid type (an
100
+ * expression) so the browser defaults to `type="text"`. At runtime, the type is reset
101
+ * _after_ its value is replaced with the evaluated expression or an empty value.
102
+ * @const {RegExp}
103
+ */
104
+ var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i
105
+
106
+ /**
107
+ * Matches trailing spaces and tabs by line.
108
+ * @const {RegExp}
109
+ */
110
+ var TRIM_TRAIL = /[ \t]+$/gm
111
+
112
+ var
113
+ DQ = '"',
114
+ SQ = "'"
115
+
116
+ /**
117
+ * Normalizes eols and removes HTML comments without touching the strings,
118
+ * avoiding unnecesary replacements.
119
+ * Skip the strings is less expansive than replacing with itself.
120
+ *
121
+ * @param {string} src - The HTML source with comments
122
+ * @returns {string} HTML without comments
123
+ * @since v2.3.22
124
+ */
125
+ function cleanSource (src) {
126
+ var
127
+ mm,
128
+ re = HTML_COMMS
129
+
130
+ if (~src.indexOf('\r')) {
131
+ src = src.replace(/\r\n?/g, '\n')
132
+ }
133
+
134
+ re.lastIndex = 0
135
+ while (mm = re.exec(src)) {
136
+ if (mm[0][0] === '<') {
137
+ src = RegExp.leftContext + RegExp.rightContext
138
+ re.lastIndex = mm[3] + 1
139
+ }
140
+ }
141
+ return src
142
+ }
143
+
144
+ /**
145
+ * Parses attributes. Force names to lowercase, enclose the values in double quotes,
146
+ * and compact spaces.
147
+ * Take care about issues in some HTML5 input elements with expressions in its value.
148
+ *
149
+ * @param {string} str - Attributes, with expressions replaced by their hash
150
+ * @param {Array} pcex - Has a `_bp` property with info about brackets
151
+ * @returns {string} Formated attributes
152
+ */
153
+ function parseAttribs (str, pcex) {
154
+ var
155
+ list = [],
156
+ match,
157
+ type, vexp
158
+
159
+ HTML_ATTRS.lastIndex = 0
160
+
161
+ str = str.replace(/\s+/g, ' ')
162
+
163
+ while (match = HTML_ATTRS.exec(str)) {
164
+ var
165
+ k = match[1].toLowerCase(),
166
+ v = match[2]
167
+
168
+ if (!v) {
169
+ list.push(k)
170
+ } else {
171
+
172
+ if (v[0] !== DQ) {
173
+ v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ
174
+ }
175
+
176
+ if (k === 'type' && SPEC_TYPES.test(v)) {
177
+ type = v
178
+ } else {
179
+ if (/\u0001\d/.test(v)) {
180
+
181
+ if (k === 'value') vexp = 1
182
+ else if (BOOL_ATTRS.test(k)) k = '__' + k
183
+ else if (~RIOT_ATTRS.indexOf(k)) k = 'riot-' + k
184
+ }
185
+
186
+ list.push(k + '=' + v)
187
+ }
188
+ }
189
+ }
190
+
191
+ if (type) {
192
+ if (vexp) type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ
193
+ list.push('type=' + type)
194
+ }
195
+ return list.join(' ')
196
+ }
197
+
198
+ /**
199
+ * Replaces expressions with a marker and runs expressions through the parser,
200
+ * if any, except those beginning with `"{^"` (hack for riot#1014 and riot#1090).
201
+ *
202
+ * @param {string} html - Raw html without comments
203
+ * @param {object} opts - The options, as passed to the compiler
204
+ * @param {Array} pcex - To store the extracted expressions
205
+ * @returns {string} html with its expressions replaced with markers
206
+ *
207
+ * @see {@link module:brackets.split|brackets.split}
208
+ */
209
+ function splitHtml (html, opts, pcex) {
210
+ var _bp = pcex._bp
211
+
212
+ if (html && _bp[4].test(html)) {
213
+ var
214
+ jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0,
215
+ list = brackets.split(html, 0, _bp),
216
+ expr, israw
217
+
218
+ for (var i = 1; i < list.length; i += 2) {
219
+ expr = list[i]
220
+ if (expr[0] === '^') {
221
+ expr = expr.slice(1)
222
+ } else if (jsfn) {
223
+ israw = expr[0] === '='
224
+ expr = jsfn(israw ? expr.slice(1) : expr, opts).trim()
225
+ if (expr.slice(-1) === ';') expr = expr.slice(0, -1)
226
+ if (israw) expr = '=' + expr
227
+ }
228
+ list[i] = '\u0001' + (pcex.push(expr) - 1) + _bp[1]
229
+ }
230
+ html = list.join('')
231
+ }
232
+ return html
233
+ }
234
+
235
+ /**
236
+ * Cleans and restores hidden expressions encoding double quotes to prevent issues
237
+ * with browsers (breaks attributes) and encode `"<>"` for expressions with raw HTML.
238
+ *
239
+ * @param {string} html - The HTML source with hidden expresions
240
+ * @param {Array} pcex - Array with unformatted expressions
241
+ * @returns {string} HTML with clean expressions in its place.
242
+ */
243
+ function restoreExpr (html, pcex) {
244
+ if (pcex.length) {
245
+ html = html
246
+ .replace(/\u0001(\d+)/g, function (_, d) {
247
+ var expr = pcex[d]
248
+
249
+ if (expr[0] === '=') {
250
+ expr = expr.replace(brackets.R_STRINGS, function (qs) {
251
+ return qs
252
+ .replace(/</g, '&lt;')
253
+ .replace(/>/g, '&gt;')
254
+ })
255
+ }
256
+
257
+ return pcex._bp[0] + expr.trim().replace(/[\r\n]+/g, ' ').replace(/"/g, '\u2057')
258
+ })
259
+ }
260
+ return html
261
+ }
262
+
263
+ /**
264
+ * The internal HTML compiler.
265
+ *
266
+ * @param {string} html - Raw HTML string
267
+ * @param {object} opts - Compilation options received by compile or compileHTML
268
+ * @param {Array} pcex - To store extracted expressions, must include `_bp`
269
+ * @returns {string} Parsed HTML code which can be used by `riot.tag2`.
270
+ *
271
+ * @see {@link http://www.w3.org/TR/html5/syntax.html}
272
+ */
273
+ function _compileHTML (html, opts, pcex) {
274
+
275
+ html = splitHtml(html, opts, pcex)
276
+ .replace(HTML_TAGS, function (_, name, attr, ends) {
277
+
278
+ name = name.toLowerCase()
279
+
280
+ ends = ends && !VOID_TAGS.test(name) ? '></' + name : ''
281
+
282
+ if (attr) name += ' ' + parseAttribs(attr, pcex)
283
+
284
+ return '<' + name + ends + '>'
285
+ })
286
+
287
+ if (!opts.whitespace) {
288
+ var p = []
289
+
290
+ if (/<pre[\s>]/.test(html)) {
291
+ html = html.replace(PRE_TAGS, function (q) {
292
+ p.push(q)
293
+ return '\u0002'
294
+ })
295
+ }
296
+
297
+ html = html.trim().replace(/\s+/g, ' ')
298
+
299
+ if (p.length) html = html.replace(/\u0002/g, function () { return p.shift() })
300
+ }
301
+
302
+ if (opts.compact) html = html.replace(/>[ \t]+<([-\w\/])/g, '><$1')
303
+
304
+ return restoreExpr(html, pcex).replace(TRIM_TRAIL, '')
305
+ }
306
+
307
+ /**
308
+ * Public interface to the internal HTML compiler, parses and formats the HTML part.
309
+ *
310
+ * - Runs each expression through the parser and replace it with a marker
311
+ * - Removes trailing tab and spaces
312
+ * - Normalizes and formats the attribute-value pairs
313
+ * - Closes self-closing tags
314
+ * - Normalizes and restores the expressions
315
+ *
316
+ * @param {string} html - Can contain embedded HTML comments and literal whitespace
317
+ * @param {Object} [opts] - User options.
318
+ * @param {Array} [pcex] - To store precompiled expressions
319
+ * @returns {string} The parsed HTML markup, which can be used by `riot.tag2`
320
+ * @static
321
+ */
322
+ function compileHTML (html, opts, pcex) {
323
+
324
+ if (Array.isArray(opts)) {
325
+ pcex = opts
326
+ opts = {}
327
+ } else {
328
+ if (!pcex) pcex = []
329
+ if (!opts) opts = {}
330
+ }
331
+
332
+ pcex._bp = brackets.array(opts.brackets)
333
+
334
+ return _compileHTML(cleanSource(html), opts, pcex)
335
+ }
336
+
337
+ /**
338
+ * Matches ES6 methods across multiple lines up to its first curly brace.
339
+ * Used by the {@link module:compiler~riotjs|riotjs} parser.
340
+ *
341
+ * 2016-01-18: rewritten to capture only the method name (performant)
342
+ * @const {RegExp}
343
+ */
344
+ var JS_ES6SIGN = /^[ \t]*([$_A-Za-z][$\w]*)\s*\([^()]*\)\s*{/m
345
+
346
+ /**
347
+ * Regex for remotion of multiline and single-line JavaScript comments, merged with
348
+ * {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} to skip literal string and regexes.
349
+ * Used by the {@link module:compiler~riotjs|riotjs} parser.
350
+ *
351
+ * 2016-01-18: rewritten to not capture the brackets (reduces 9 steps)
352
+ * @const {RegExp}
353
+ */
354
+ var JS_ES6END = RegExp('[{}]|' + brackets.S_QBLOCKS, 'g')
355
+
356
+ /**
357
+ * Regex for remotion of multiline and single-line JavaScript comments, merged with
358
+ * {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} to skip literal string and regexes.
359
+ * @const {RegExp}
360
+ */
361
+ var JS_COMMS = RegExp(brackets.R_MLCOMMS.source + '|//[^\r\n]*|' + brackets.S_QBLOCKS, 'g')
362
+
363
+ /**
364
+ * Default parser for JavaScript, supports ES6-like method syntax
365
+ *
366
+ * @param {string} js - Raw JavaScript code
367
+ * @returns {string} Code with ES6 methods converted to ES5, comments removed.
368
+ */
369
+ function riotjs (js) {
370
+ var
371
+ parts = [],
372
+ match,
373
+ toes5,
374
+ pos,
375
+ name,
376
+ RE = RegExp
377
+
378
+ if (~js.indexOf('/')) js = rmComms(js, JS_COMMS)
379
+
380
+ while (match = js.match(JS_ES6SIGN)) {
381
+
382
+ parts.push(RE.leftContext)
383
+ js = RE.rightContext
384
+ pos = skipBody(js, JS_ES6END)
385
+
386
+ name = match[1]
387
+ toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name)
388
+ name = toes5 ? match[0].replace(name, 'this.' + name + ' = function') : match[0]
389
+ parts.push(name, js.slice(0, pos))
390
+ js = js.slice(pos)
391
+
392
+ if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)')
393
+ }
394
+
395
+ return parts.length ? parts.join('') + js : js
396
+
397
+ function rmComms (s, r, m) {
398
+ r.lastIndex = 0
399
+ while (m = r.exec(s)) {
400
+ if (m[0][0] === '/' && !m[1] && !m[2]) {
401
+ s = RE.leftContext + ' ' + RE.rightContext
402
+ r.lastIndex = m[3] + 1
403
+ }
404
+ }
405
+ return s
406
+ }
407
+
408
+ function skipBody (s, r) {
409
+ var m, i = 1
410
+
411
+ r.lastIndex = 0
412
+ while (i && (m = r.exec(s))) {
413
+ if (m[0] === '{') ++i
414
+ else if (m[0] === '}') --i
415
+ }
416
+ return i ? s.length : r.lastIndex
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Internal JavaScript compilation.
422
+ *
423
+ * @param {string} js - Raw JavaScript code
424
+ * @param {object} opts - Compiler options
425
+ * @param {string} type - Parser name, one of {@link module:parsers.js|parsers.js}
426
+ * @param {object} parserOpts - User options passed to the parser
427
+ * @param {string} url - Of the file being compiled, passed to the parser
428
+ * @returns {string} Compiled code, eols normalized, trailing spaces removed
429
+ *
430
+ * @throws Will throw "JS parser not found" if the JS parser cannot be loaded.
431
+ * @see {@link module:compiler.compileJS|compileJS}
432
+ */
433
+ function _compileJS (js, opts, type, parserOpts, url) {
434
+ if (!/\S/.test(js)) return ''
435
+ if (!type) type = opts.type
436
+
437
+ var parser = opts.parser || (type ? parsers.js[type] : riotjs)
438
+
439
+ if (!parser) {
440
+ throw new Error('JS parser not found: "' + type + '"')
441
+ }
442
+ return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '')
443
+ }
444
+
445
+ /**
446
+ * Public interface to the internal JavaScript compiler, runs the parser with
447
+ * the JavaScript code, defaults to `riotjs`.
448
+ *
449
+ * - If the given code is empty or whitespaces only, returns an empty string
450
+ * - Determines the parser to use, by default the internal riotjs function
451
+ * - Call the parser, the default {@link module:compiler~riotjs|riotjs} removes comments,
452
+ * converts ES6 method signatures to ES5 and bind to `this` if neccesary
453
+ * - Normalizes line-endings and trims trailing spaces before return the result
454
+ *
455
+ * @param {string} js - Buffer with the javascript code
456
+ * @param {Object} [opts] - Compiler options (DEPRECATED parameter, don't use it)
457
+ * @param {string} [type=riotjs] - Parser name, one of {@link module:parsers.js|parsers.js}
458
+ * @param {Object} [userOpts={}] - User options
459
+ * @param {string} [userOpts.url=process.cwd] - Url of the .tag file (passed to the parser)
460
+ * @param {object} [userOpts.parserOpts={}] - User options (passed to the parser)
461
+ * @returns {string} Parsed JavaScript, eols normalized, trailing spaces removed
462
+ * @static
463
+ *
464
+ * @see {@link module:compiler~_compileJS|_compileJS}
465
+ */
466
+ function compileJS (js, opts, type, userOpts) {
467
+ if (typeof opts === 'string') {
468
+ userOpts = type
469
+ type = opts
470
+ opts = {}
471
+ }
472
+ if (type && typeof type === 'object') {
473
+ userOpts = type
474
+ type = ''
475
+ }
476
+ if (!userOpts) userOpts = {}
477
+
478
+ return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url)
479
+ }
480
+
481
+ var CSS_SELECTOR = RegExp('([{}]|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + S_LINESTR, 'g')
482
+
483
+ /**
484
+ * Parses styles enclosed in a "scoped" tag (`scoped` was removed from HTML5).
485
+ * The "css" string is received without comments or surrounding spaces.
486
+ *
487
+ * @param {string} tag - Tag name of the root element
488
+ * @param {string} css - The CSS code
489
+ * @returns {string} CSS with the styles scoped to the root element
490
+ */
491
+ function scopedCSS (tag, css) {
492
+ var scope = ':scope'
493
+
494
+ return css.replace(CSS_SELECTOR, function (m, p1, p2) {
495
+
496
+ if (!p2) return m
497
+
498
+ p2 = p2.replace(/[^,]+/g, function (sel) {
499
+ var s = sel.trim()
500
+
501
+ if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') {
502
+ return sel
503
+ }
504
+
505
+ if (s.indexOf(scope) < 0) {
506
+ s = tag + ' ' + s + ',[riot-tag="' + tag + '"] ' + s
507
+ } else {
508
+ s = s.replace(scope, tag) + ',' +
509
+ s.replace(scope, '[riot-tag="' + tag + '"]')
510
+ }
511
+ return sel.slice(-1) === ' ' ? s + ' ' : s
512
+ })
513
+
514
+ return p1 ? p1 + ' ' + p2 : p2
515
+ })
516
+ }
517
+
518
+ /**
519
+ * Internal CSS compilation.
520
+ * Runs any parser for style blocks and calls scopedCSS if required.
521
+ *
522
+ * @param {string} css - Raw CSS
523
+ * @param {string} tag - Tag name to which the style belongs to
524
+ * @param {string} [type=css] - Parser name to run
525
+ * @param {object} [opts={}] - User options
526
+ * @returns {string} The compiled style, whitespace compacted and trimmed
527
+ *
528
+ * @throws Will throw "CSS parser not found" if the CSS parser cannot be loaded.
529
+ * @throws Using the _scoped_ option with no tagName will throw an error.
530
+ * @see {@link module:compiler.compileCSS|compileCSS}
531
+ */
532
+ function _compileCSS (css, tag, type, opts) {
533
+ var scoped = (opts || (opts = {})).scoped
534
+
535
+ if (type) {
536
+ if (type === 'scoped-css') {
537
+ scoped = true
538
+ } else if (parsers.css[type]) {
539
+ css = parsers.css[type](tag, css, opts.parserOpts || {}, opts.url)
540
+ } else if (type !== 'css') {
541
+ throw new Error('CSS parser not found: "' + type + '"')
542
+ }
543
+ }
544
+
545
+ css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim()
546
+
547
+ if (scoped) {
548
+ if (!tag) {
549
+ throw new Error('Can not parse scoped CSS without a tagName')
550
+ }
551
+ css = scopedCSS(tag, css)
552
+ }
553
+ return css
554
+ }
555
+
556
+ /**
557
+ * Public API, wrapper of the internal {@link module:compiler~_compileCSS|_compileCSS}
558
+ * function, rearranges its parameters and makes these can be omitted.
559
+ *
560
+ * - If the given code is empty or whitespaces only, returns an empty string
561
+ * - Determines the parser to use, none by default
562
+ * - Call the parser, if any
563
+ * - Normalizes line-endings and trims trailing spaces
564
+ * - Call the {@link module:compiler~scopedCSS|scopedCSS} function if required by the
565
+ * parameter _opts_
566
+ *
567
+ * @param {string} css - Raw style block
568
+ * @param {string} [type] - Parser name, one of {@link module:parsers.css|parsers.css}
569
+ * @param {object} [opts] - User options
570
+ * @param {boolean} [opts.scoped] - Convert to Scoped CSS (requires _tagName_)
571
+ * @param {string} [opts.tagName] - Name of the root tag owner of the styles
572
+ * @param {string} [opts.url=process.cwd] - Url of the .tag file
573
+ * @param {object} [opts.parserOpts={}] - Options for the parser
574
+ * @returns {string} The processed style block
575
+ * @static
576
+ *
577
+ * @see {@link module:compiler~_compileCSS|_compileCSS}
578
+ */
579
+ function compileCSS (css, type, opts) {
580
+ if (type && typeof type === 'object') {
581
+ opts = type
582
+ type = ''
583
+ } else if (!opts) opts = {}
584
+
585
+ return _compileCSS(css, opts.tagName, type, opts)
586
+ }
587
+
588
+ /**
589
+ * The "defer" attribute is used to ignore `script` elements (useful for SSR).
590
+ *
591
+ * This regex is used by {@link module:compiler~getCode|getCode} to check and by
592
+ * {@link module:compiler.compile|compile} to remove the keyword `defer` from
593
+ * `<script>` tags.
594
+ * @const {RegExp}
595
+ */
596
+ var DEFER_ATTR = /\sdefer(?=\s|>|$)/i
597
+
598
+ /**
599
+ * Matches attributes 'type=value', for `<script>` and `<style>` tags.
600
+ * This regex does not expect expressions nor escaped quotes.
601
+ * @const {RegExp}
602
+ */
603
+ var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i
604
+
605
+ /**
606
+ * Source string for creating generic regexes matching pairs of `attribute=value`. Used
607
+ * by {@link module:compiler~getAttrib|getAttrib} for the `option`, `src`, and `charset`
608
+ * attributes, handles escaped quotes and unquoted JSON objects with no nested brackets.
609
+ * @const {string}
610
+ */
611
+ var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)'
612
+
613
+ /**
614
+ * Matches the last HTML tag ending a line. This can be one of:
615
+ * - self-closing tag
616
+ * - closing tag
617
+ * - tag without attributes
618
+ * - void tag with (optional) attributes
619
+ *
620
+ * Be aware that this regex still can be fooled by strange code like:
621
+ * ```js
622
+ * x <y -y>
623
+ * z
624
+ * ```
625
+ * @const {RegExp}
626
+ */
627
+ var END_TAGS = /\/>\n|^<(?:\/?[-\w]+\s*|[-\w]+\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/
628
+
629
+ /**
630
+ * Encloses the given string in single quotes.
631
+ *
632
+ * 2016-01-18: we must escape single quotes and backslashes before quoting the
633
+ * string, but there's no need to care about line-endings unless is required,
634
+ * as each submodule normalizes the lines.
635
+ *
636
+ * @param {string} s - The unquoted, source string
637
+ * @param {number} r - If 1, escape embeded EOLs in the source
638
+ * @returns {string} Quoted string, with escaped single-quotes and backslashes.
639
+ */
640
+ function _q (s, r) {
641
+ if (!s) return "''"
642
+ s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ
643
+ return r && ~s.indexOf('\n') ? s.replace(/\n/g, '\\n') : s
644
+ }
645
+
646
+ /**
647
+ * Generates code to call the `riot.tag2` function with the processed parts.
648
+ *
649
+ * @param {string} name - The tag name
650
+ * @param {string} html - HTML (can contain embeded eols)
651
+ * @param {string} css - Styles
652
+ * @param {string} attribs - Root attributes
653
+ * @param {string} js - JavaScript "constructor"
654
+ * @param {Array} pcex - Expressions
655
+ * @returns {string} Code to call `riot.tag2`
656
+ */
657
+ function mktag (name, html, css, attribs, js, pcex) {
658
+ var
659
+ c = ', ',
660
+ s = '}' + (pcex.length ? ', ' + _q(pcex._bp[8]) : '') + ');'
661
+
662
+ if (js && js.slice(-1) !== '\n') s = '\n' + s
663
+
664
+ return 'riot.tag2(\'' + name + SQ +
665
+ c + _q(html, 1) +
666
+ c + _q(css) +
667
+ c + _q(attribs) + ', function(opts) {\n' + js + s
668
+ }
669
+
670
+ /**
671
+ * Used by the main {@link module:compiler.compile|compile} function, separates the
672
+ * HTML and JS parts of the tag. The last HTML element (can be a `<script>` block) must
673
+ * terminate a line.
674
+ *
675
+ * @param {string} str - Tag content, normalized, without attributes
676
+ * @returns {Array} Parts: `[HTML, JavaScript]`
677
+ */
678
+ function splitBlocks (str) {
679
+ if (/<[-\w]/.test(str)) {
680
+ var
681
+ m,
682
+ k = str.lastIndexOf('<'),
683
+ n = str.length
684
+
685
+ while (~k) {
686
+ m = str.slice(k, n).match(END_TAGS)
687
+ if (m) {
688
+ k += m.index + m[0].length
689
+ return [str.slice(0, k), str.slice(k)]
690
+ }
691
+ n = k
692
+ k = str.lastIndexOf('<', k - 1)
693
+ }
694
+ }
695
+ return ['', str]
696
+ }
697
+
698
+ /**
699
+ * Returns the value of the 'type' attribute, with the prefix "text/" removed.
700
+ *
701
+ * @param {string} attribs - The attributes list
702
+ * @returns {string} Attribute value, defaults to empty string
703
+ */
704
+ function getType (attribs) {
705
+ if (attribs) {
706
+ var match = attribs.match(TYPE_ATTR)
707
+
708
+ match = match && (match[2] || match[3])
709
+ if (match) {
710
+ return match.replace('text/', '')
711
+ }
712
+ }
713
+ return ''
714
+ }
715
+
716
+ /**
717
+ * Returns the value of any attribute, or the empty string for missing attribute.
718
+ *
719
+ * @param {string} attribs - The attribute list
720
+ * @param {string} name - Attribute name
721
+ * @returns {string} Attribute value, defaults to empty string
722
+ */
723
+ function getAttrib (attribs, name) {
724
+ if (attribs) {
725
+ var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i'))
726
+
727
+ match = match && match[1]
728
+ if (match) {
729
+ return (/^['"]/).test(match) ? match.slice(1, -1) : match
730
+ }
731
+ }
732
+ return ''
733
+ }
734
+
735
+ /**
736
+ * Gets the parser options from the "options" attribute.
737
+ *
738
+ * @param {string} attribs - The attribute list
739
+ * @returns {object} Parsed options, or null if no options
740
+ */
741
+ function getParserOptions (attribs) {
742
+ var opts = getAttrib(attribs, 'options')
743
+
744
+ return opts ? JSON.parse(opts) : null
745
+ }
746
+
747
+ /**
748
+ * Gets the parsed code for the received JavaScript code.
749
+ * The node version can read the code from the file system. The filename is
750
+ * specified through the _src_ attribute and can be absolute or relative to _base_.
751
+ *
752
+ * @param {string} code - The unprocessed JavaScript code
753
+ * @param {object} opts - Compiler options
754
+ * @param {string} attribs - Attribute list
755
+ * @param {string} base - Full filename or path of the file being processed
756
+ * @returns {string} Parsed code
757
+ */
758
+ function getCode (code, opts, attribs, base) {
759
+ var type = getType(attribs)
760
+
761
+ var src = getAttrib(attribs, 'src')
762
+
763
+ if (src) {
764
+ if (DEFER_ATTR.test(attribs)) return false
765
+
766
+ var charset = getAttrib(attribs, 'charset'),
767
+ file = path.resolve(path.dirname(base), src)
768
+
769
+ code = require('fs').readFileSync(file, charset || 'utf8')
770
+ }
771
+ return _compileJS(code, opts, type, getParserOptions(attribs), base)
772
+ }
773
+
774
+ /**
775
+ * Gets the parsed styles for the received CSS code.
776
+ *
777
+ * @param {string} code - Unprocessed CSS
778
+ * @param {object} opts - Compiler options
779
+ * @param {string} attribs - Attribute list
780
+ * @param {string} url - Of the file being processed
781
+ * @param {string} tag - Tag name of the root element
782
+ * @returns {string} Parsed styles
783
+ */
784
+ function cssCode (code, opts, attribs, url, tag) {
785
+ var extraOpts = {
786
+ parserOpts: getParserOptions(attribs),
787
+ scoped: attribs && /\sscoped(\s|=|$)/i.test(attribs),
788
+ url: url
789
+ }
790
+
791
+ return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts)
792
+ }
793
+
794
+ /**
795
+ * Runs the external HTML parser for the entire tag file
796
+ *
797
+ * @param {string} html - Entire, untouched html received for the compiler
798
+ * @param {string} url - The source url or file name
799
+ * @param {string} lang - Parser's name, one of {@link module:parsers.html|parsers.html}
800
+ * @param {object} opts - Extra option passed to the parser
801
+ * @returns {string} parsed html
802
+ *
803
+ * @throws Will throw "Template parser not found" if the HTML parser cannot be loaded.
804
+ */
805
+ function compileTemplate (html, url, lang, opts) {
806
+ var parser = parsers.html[lang]
807
+
808
+ if (!parser) {
809
+ throw new Error('Template parser not found: "' + lang + '"')
810
+ }
811
+ return parser(html, opts, url)
812
+ }
813
+
814
+ var
815
+ /**
816
+ * Matches HTML elements. The opening and closing tags of multiline elements must have
817
+ * the same indentation (size and type).
818
+ *
819
+ * `CUST_TAG` recognizes escaped quotes, allowing its insertion into JS strings inside
820
+ * unquoted expressions, but disallows the character '>' within unquoted attribute values.
821
+ * @const {RegExp}
822
+ */
823
+ CUST_TAG = RegExp(/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
824
+ .source.replace('@', S_STRINGS), 'gim'),
825
+ /**
826
+ * Matches `script` elements, capturing its attributes in $1 and its content in $2.
827
+ * Disallows the character '>' inside quoted or unquoted attribute values.
828
+ * @const {RegExp}
829
+ */
830
+ SCRIPTS = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi,
831
+ /**
832
+ * Matches `style` elements, capturing its attributes in $1 and its content in $2.
833
+ * Disallows the character '>' inside quoted or unquoted attribute values.
834
+ * @const {RegExp}
835
+ */
836
+ STYLES = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi
837
+
838
+ /**
839
+ * The main compiler processes all custom tags, one by one.
840
+ *
841
+ * - Sends the received source to the html parser, if any is specified
842
+ * - Normalizes eols, removes HTML comments and trim trailing spaces
843
+ * - Searches the HTML elements and extract: tag name, root attributes, and content
844
+ * - Parses the root attributes. Found expressions are stored in `pcex[]`
845
+ * - Removes _HTML_ comments and trims trailing whitespace from the content
846
+ * - For one-line tags, process all the content as HTML
847
+ * - For multiline tags, separates the HTML from any untagged JS block and, from
848
+ * the html, extract and process the `style` and `script` elements
849
+ * - Parses the remaining html, found expressions are added to `pcex[]`
850
+ * - Parses the untagged JavaScript block, if any
851
+ * - If the `entities` option was received, returns an object with the parts,
852
+ * if not, returns the code neccesary to call the `riot.tag2` function to
853
+ * create a Tag instance at runtime.
854
+ *
855
+ * In .tag files, a custom tag can span multiple lines, but there should be no other
856
+ * elements at the start of the line (comments inclusive). Custom tags in html files
857
+ * don't have this restriction.
858
+ *
859
+ * @param {string} src - String with zero or more custom riot tags
860
+ * @param {Object} [opts={}] - User options
861
+ * @param {string} [url=./.] - Filename or url of the file being processed
862
+ * @returns {string} JavaScript code to build a Tag by the `riot.tag2` function
863
+ * @static
864
+ */
865
+ function compile (src, opts, url) {
866
+ var
867
+ parts = [],
868
+ included
869
+
870
+ if (!opts) opts = {}
871
+
872
+ included = opts.exclude
873
+ ? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 }
874
+
875
+ if (!url) url = process.cwd() + '/.'
876
+
877
+ var _bp = brackets.array(opts.brackets)
878
+
879
+ if (opts.template) {
880
+ src = compileTemplate(src, url, opts.template, opts.templateOptions)
881
+ }
882
+
883
+ src = cleanSource(src)
884
+ .replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) {
885
+ var
886
+ jscode = '',
887
+ styles = '',
888
+ html = '',
889
+ pcex = []
890
+
891
+ pcex._bp = _bp
892
+
893
+ tagName = tagName.toLowerCase()
894
+
895
+ attribs = attribs && included('attribs')
896
+ ? restoreExpr(
897
+ parseAttribs(
898
+ splitHtml(attribs, opts, pcex),
899
+ pcex),
900
+ pcex) : ''
901
+
902
+ if ((body || (body = body2)) && /\S/.test(body)) {
903
+
904
+ if (body2) {
905
+
906
+ if (included('html')) html = _compileHTML(body2, opts, pcex)
907
+ } else {
908
+
909
+ body = body.replace(RegExp('^' + indent, 'gm'), '')
910
+
911
+ body = body.replace(STYLES, function (_m, _attrs, _style) {
912
+ if (included('css')) {
913
+ styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName)
914
+ }
915
+ return ''
916
+ })
917
+
918
+ body = body.replace(SCRIPTS, function (_m, _attrs, _script) {
919
+ if (included('js')) {
920
+ var code = getCode(_script, opts, _attrs, url)
921
+
922
+ if (code === false) return _m.replace(DEFER_ATTR, '')
923
+ if (code) jscode += (jscode ? '\n' : '') + code
924
+ }
925
+ return ''
926
+ })
927
+
928
+ var blocks = splitBlocks(body.replace(TRIM_TRAIL, ''))
929
+
930
+ if (included('html')) {
931
+ html = _compileHTML(blocks[0], opts, pcex)
932
+ }
933
+
934
+ if (included('js')) {
935
+ body = _compileJS(blocks[1], opts, null, null, url)
936
+ if (body) jscode += (jscode ? '\n' : '') + body
937
+ }
938
+ }
939
+ }
940
+
941
+ jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : ''
942
+
943
+ if (opts.entities) {
944
+ parts.push({
945
+ tagName: tagName,
946
+ html: html,
947
+ css: styles,
948
+ attribs: attribs,
949
+ js: jscode
950
+ })
951
+ return ''
952
+ }
953
+
954
+ return mktag(tagName, html, styles, attribs, jscode, pcex)
955
+ })
956
+
957
+ if (opts.entities) return parts
958
+
959
+ if (opts.debug && url.slice(-2) !== '/.') {
960
+ if (/^[\\/]/.test(url)) url = path.relative('.', url)
961
+ src = '//src: ' + url.replace(/\\/g, '/') + '\n' + src
962
+ }
963
+ return src
964
+ }
10
965
 
11
966
  module.exports = {
12
967
  compile: compile,
13
968
  html: compileHTML,
14
- style: compileCSS,
15
- js: compileJS
16
- }
969
+ style: _compileCSS,
970
+ css: compileCSS,
971
+ js: compileJS,
972
+ parsers: parsers,
973
+ version: 'WIP'
974
+ }