Skip to content

Commit 6f264e8

Browse files
refactor(lib): move utility functions into their own modules
1 parent c2f53b7 commit 6f264e8

File tree

6 files changed

+272
-109
lines changed

6 files changed

+272
-109
lines changed

lib/backup.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict'
2+
3+
const cloneDeep = require('fclone')
4+
5+
/**
6+
* @description Creates a backup of keys values
7+
*
8+
* @method makeLocalsBackup
9+
*
10+
* @param {Object} keys Keys
11+
* @param {Object} locals Locals
12+
*
13+
* @return {Object} backup Backup Locals
14+
*/
15+
function makeLocalsBackup (keys, locals) {
16+
let backup = {}
17+
18+
for (let i = 0; i < keys.length; i++) {
19+
const key = keys[i]
20+
if (locals.hasOwnProperty(key)) backup[key] = cloneDeep(locals[key])
21+
}
22+
23+
return backup
24+
}
25+
26+
/**
27+
* @description Returns the original keys values
28+
*
29+
* @method revertBackupedLocals
30+
*
31+
* @param {Object} keys Keys
32+
* @param {Object} locals Locals
33+
* @param {Object} backup Backup
34+
*
35+
* @return {Object} locals Reverted Locals
36+
*/
37+
function revertBackupedLocals (keys, locals, backup) {
38+
for (let i = 0; i < keys.length; i++) {
39+
const key = keys[i]
40+
// remove key from locals
41+
delete locals[key]
42+
43+
// revert copied key value
44+
if (backup.hasOwnProperty(key)) locals[key] = backup[key]
45+
}
46+
47+
return locals
48+
}
49+
50+
/**
51+
* @module backup
52+
*
53+
* @requires fclone
54+
*
55+
* @type {Object}
56+
*
57+
* @prop {Function} make Make Locals backup
58+
* @prop {Function} revert Revert backuped Locals
59+
*/
60+
module.exports = { make: makeLocalsBackup, revert: revertBackupedLocals }

lib/escape.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @description Replace String based on RegExp
3+
*
4+
* @method escapeRegexpString
5+
*
6+
* @param {String} input Input
7+
*
8+
* @return {Function} input Replaced Input
9+
*/
10+
function escapeRegexpString (input) {
11+
// match Operators
12+
const match = /[|\\{}()[\]^$+*?.]/
13+
14+
return input.replace(match, '\\$&')
15+
}
16+
17+
/**
18+
* @module escape
19+
*
20+
* @type {Function}
21+
*/
22+
module.exports = escapeRegexpString

lib/index.js

Lines changed: 82 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,91 @@
11
'use strict'
22

33
const 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
510
const placeholders = require('./placeholders')
611

712
let delimitersSettings = []
813
let 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(/\sin\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-
}

lib/loops.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @description Given a "loop" parameter from an "each" tag, parses out the param names and expression to be looped.
3+
*
4+
* @method parseLoopStatement
5+
*
6+
* @param {String} input Input
7+
*
8+
* @return {Object} {} Keys && Expression
9+
*/
10+
function parseLoopStatement (input) {
11+
// try to find ` in ` keyword
12+
const inKeywordIndex = input.search(/\sin\s/)
13+
14+
// if we reach the end of the string without getting "in", it's an error
15+
if (inKeywordIndex === -1) {
16+
throw new Error("Loop statement lacking 'in' keyword")
17+
}
18+
19+
// expression is always after `in` keyword
20+
const expression = input.substr(inKeywordIndex + 4)
21+
22+
// keys is always before `in` keyword
23+
const keys = input.substr(0, inKeywordIndex).split(',')
24+
25+
for (let i = 0; i < keys.length; i++) {
26+
keys[i] = keys[i].trim()
27+
}
28+
29+
return { keys, expression }
30+
}
31+
32+
/**
33+
* @module loops
34+
*
35+
* @type {Function}
36+
*/
37+
module.exports = parseLoopStatement

0 commit comments

Comments
 (0)