riot_js-rails 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }