11'use strict'
22
33const vm = require ( 'vm' )
4- const cloneDeep = require ( 'fclone' )
4+
5+ const getNextTag = require ( './tags' )
6+ const parseLoopStatement = require ( './loops' )
7+ const escapeRegexpString = require ( './escape' )
8+ const makeLocalsBackup = require ( './backup' ) . make
9+ const revertBackupedLocals = require ( './backup' ) . revert
510const placeholders = require ( './placeholders' )
611
712let delimitersSettings = [ ]
813let conditionals , loops , scopes
914
10- const matchOperatorsRegexp = / [ | \\ { } ( ) [ \] ^ $ + * ? . ] / g
15+ /**
16+ * @description Creates a set of local variables within the loop, and evaluates all nodes within the loop, returning their contents
17+ *
18+ * @method executeLoop
19+ *
20+ * @param {Array } params Parameters
21+ * @param {String } p1 Parameter 1
22+ * @param {String } p2 Parameter 2
23+ * @param {Object } locals Locals
24+ * @param {String } tree Tree
25+ *
26+ * @return {Function } walk Walks the tree and parses all locals within the loop
27+ */
28+ function executeLoop ( params , p1 , p2 , locals , tree ) {
29+ // two loop locals are allowed
30+ // - for arrays it's the current value and the index
31+ // - for objects, it's the value and the key
32+ const scopes = locals
33+
34+ scopes [ params [ 0 ] ] = p1
1135
12- function escapeRegexpString ( input ) {
13- return input . replace ( matchOperatorsRegexp , '\\$&' )
36+ if ( params [ 1 ] ) scopes [ params [ 1 ] ] = p2
37+
38+ return walk ( { locals : scopes } , JSON . parse ( tree ) )
1439}
1540
16- module . exports = function PostHTMLExpressions ( options ) {
41+ /**
42+ * @description Runs walk function with arbitrary set of local variables
43+ *
44+ * @method executeScope
45+ *
46+ * @param {Object } scope Scoped Locals
47+ * @param {Object } locals Locals
48+ * @param {Object } node Node
49+ *
50+ * @return {Function } walk Walks the tree and parses all locals in scope
51+ */
52+ function executeScope ( scope , locals , node ) {
53+ scope = Object . assign ( locals , scope )
54+
55+ return walk ( { locals : scope } , node . content )
56+ }
57+
58+ /**
59+ * @author Jeff Escalante Denis (@jescalan),
60+ * Malinochkin (mrmlnc),
61+ * Michael Ciniawsky (@michael-ciniawsky)
62+ * @description Expressions Plugin for PostHTML
63+ * @license MIT
64+ *
65+ * @module posthtml-expressions
66+ * @version 1.0.0
67+ *
68+ * @requires vm
69+ *
70+ * @requires ./tags
71+ * @requires ./loops
72+ * @requires ./escape
73+ * @requires ./backup
74+ * @requires ./placeholders
75+ *
76+ * @param {Object } options Options
77+ *
78+ * @return {Object } tree PostHTML Tree
79+ */
80+ module . exports = function postHTMLExpressions ( options ) {
1781 // set default options
1882 options = Object . assign ( {
83+ locals : { } ,
1984 delimiters : [ '{{' , '}}' ] ,
2085 unescapeDelimiters : [ '{{{' , '}}}' ] ,
2186 conditionalTags : [ 'if' , 'elseif' , 'else' ] ,
2287 loopTags : [ 'each' ] ,
23- scopeTags : [ 'scope' ] ,
24- locals : { }
88+ scopeTags : [ 'scope' ]
2589 } , options )
2690
2791 // set tags
@@ -32,10 +96,12 @@ module.exports = function PostHTMLExpressions (options) {
3296 // make a RegExp's to search for placeholders
3397 let before = escapeRegexpString ( options . delimiters [ 0 ] )
3498 let after = escapeRegexpString ( options . delimiters [ 1 ] )
99+
35100 const delimitersRegexp = new RegExp ( `${ before } (.+?)${ after } ` , 'g' )
36101
37102 before = escapeRegexpString ( options . unescapeDelimiters [ 0 ] )
38103 after = escapeRegexpString ( options . unescapeDelimiters [ 1 ] )
104+
39105 const unescapeDelimitersRegexp = new RegExp ( `${ before } (.+?)${ after } ` , 'g' )
40106
41107 // make array of delimiters
@@ -75,7 +141,9 @@ function walk (opts, nodes) {
75141 // if we have a string, match and replace it
76142 if ( typeof node === 'string' ) {
77143 node = placeholders ( node , ctx , delimitersSettings )
144+
78145 m . push ( node )
146+
79147 return m
80148 }
81149
@@ -101,12 +169,15 @@ function walk (opts, nodes) {
101169
102170 // сalculate the first path of condition expression
103171 let expressionIndex = 1
172+
104173 let expression = `if (${ node . attrs . condition } ) { 0 } `
174+
105175 const branches = [ node . content ]
106176
107177 // move through the nodes and collect all others that are part of the same
108178 // conditional statement
109179 let computedNextTag = getNextTag ( nodes , ++ i )
180+
110181 let current = computedNextTag [ 0 ]
111182 let nextTag = computedNextTag [ 1 ]
112183
@@ -135,6 +206,7 @@ function walk (opts, nodes) {
135206 expression += statement + ( condition ? ` (${ condition } )` : '' ) + ` { ${ expressionIndex ++ } } `
136207
137208 computedNextTag = getNextTag ( nodes , ++ current )
209+
138210 current = computedNextTag [ 0 ]
139211 nextTag = computedNextTag [ 1 ]
140212 }
@@ -149,6 +221,7 @@ function walk (opts, nodes) {
149221
150222 // recursive evaluate of condition branch
151223 if ( branch ) Array . prototype . push . apply ( m , walk ( opts , branch ) )
224+
152225 return m
153226 }
154227
@@ -216,7 +289,7 @@ function walk (opts, nodes) {
216289 // creates a copy of the keys that will be changed within the loop
217290 const localsBackup = makeLocalsBackup ( keys , opts . locals )
218291
219- m . push ( executeScoped ( target , opts . locals , node ) )
292+ m . push ( executeScope ( target , opts . locals , node ) )
220293
221294 // returns the original keys values that was changed within the loop
222295 opts . locals = revertBackupedLocals ( keys , opts . locals , localsBackup )
@@ -227,100 +300,7 @@ function walk (opts, nodes) {
227300
228301 // return the node
229302 m . push ( node )
303+
230304 return m
231305 } , [ ] )
232306}
233-
234- function getNextTag ( nodes , i , nodeCount ) {
235- // loop until we get the next tag (bypassing newlines etc)
236- while ( i < nodes . length ) {
237- const node = nodes [ i ]
238- if ( typeof node === 'object' ) {
239- return [ i , node ]
240- } else {
241- i ++
242- }
243- }
244- return [ i , { tag : undefined } ]
245- }
246-
247- /**
248- * Given a "loop" parameter from an "each" tag, parses out the param names and
249- * expression to be looped.
250- */
251- function parseLoopStatement ( input ) {
252- // try to find ` in ` keyword
253- const inKeywordIndex = input . search ( / \s i n \s / )
254-
255- // if we reach the end of the string without getting "in", it's an error
256- if ( inKeywordIndex === - 1 ) {
257- throw new Error ( "Loop statement lacking 'in' keyword" )
258- }
259-
260- // expression is always after `in` keyword
261- const expression = input . substr ( inKeywordIndex + 4 )
262-
263- // keys is always before `in` keyword
264- const keys = input . substr ( 0 , inKeywordIndex ) . split ( ',' )
265- for ( let i = 0 ; i < keys . length ; i ++ ) {
266- keys [ i ] = keys [ i ] . trim ( )
267- }
268-
269- return {
270- keys,
271- expression
272- }
273- }
274-
275- /**
276- * Creates a backup of keys values
277- */
278- function makeLocalsBackup ( keys , locals ) {
279- let backup = { }
280-
281- for ( let i = 0 ; i < keys . length ; i ++ ) {
282- const key = keys [ i ]
283- if ( locals . hasOwnProperty ( key ) ) backup [ key ] = cloneDeep ( locals [ key ] )
284- }
285-
286- return backup
287- }
288-
289- /**
290- * Returns the original keys values
291- */
292- function revertBackupedLocals ( keys , locals , backup ) {
293- for ( let i = 0 ; i < keys . length ; i ++ ) {
294- const key = keys [ i ]
295- // remove key from locals
296- delete locals [ key ]
297-
298- // revert copied key value
299- if ( backup . hasOwnProperty ( key ) ) locals [ key ] = backup [ key ]
300- }
301-
302- return locals
303- }
304-
305- /**
306- * Creates a set of local variables within the loop, and evaluates all nodes
307- * within the loop, returning their contents
308- */
309- function executeLoop ( loopParams , p1 , p2 , locals , treeString ) {
310- // two loop locals are allowed
311- // - for arrays it's the current value and the index
312- // - for objects, it's the value and the key
313- const scopedLocals = locals
314- scopedLocals [ loopParams [ 0 ] ] = p1
315- if ( loopParams [ 1 ] ) scopedLocals [ loopParams [ 1 ] ] = p2
316-
317- return walk ( { locals : scopedLocals } , JSON . parse ( treeString ) )
318- }
319-
320- /**
321- * Runs walk function with arbitrary set of local variables
322- */
323- function executeScoped ( scopeLocals , locals , node ) {
324- const scopedLocals = Object . assign ( locals , scopeLocals )
325- return walk ( { locals : scopedLocals } , node . content )
326- }
0 commit comments