UNPKG

3.78 kBJavaScriptView Raw
1'use strict';
2
3module.exports = (input, options = {}, fn) => {
4 if (typeof input !== 'string') throw new TypeError('expected a string');
5
6 if (typeof options === 'function') {
7 fn = options;
8 options = {};
9 }
10
11 let separator = options.separator || '.';
12 let ast = { type: 'root', nodes: [], stash: [''] };
13 let stack = [ast];
14 let state = { input, separator, stack };
15 let string = input;
16 let value, node;
17 let i = -1;
18
19 state.bos = () => i === 0;
20 state.eos = () => i === string.length;
21 state.prev = () => string[i - 1];
22 state.next = () => string[i + 1];
23
24 let quotes = options.quotes || [];
25 let openers = options.brackets || {};
26
27 if (options.brackets === true) {
28 openers = { '[': ']', '(': ')', '{': '}', '<': '>' };
29 }
30 if (options.quotes === true) {
31 quotes = ['"', '\'', '`'];
32 }
33
34 let closers = invert(openers);
35 let keep = options.keep || (value => value !== '\\');
36
37 const block = () => (state.block = stack[stack.length - 1]);
38 const peek = () => string[i + 1];
39 const next = () => string[++i];
40 const append = value => {
41 state.value = value;
42 if (value && keep(value, state) !== false) {
43 state.block.stash[state.block.stash.length - 1] += value;
44 }
45 };
46
47 const closeIndex = (value, startIdx) => {
48 let idx = string.indexOf(value, startIdx);
49 if (idx > -1 && string[idx - 1] === '\\') {
50 idx = closeIndex(value, idx + 1);
51 }
52 return idx;
53 };
54
55 for (; i < string.length - 1;) {
56 state.value = value = next();
57 state.index = i;
58 block();
59
60 // handle escaped characters
61 if (value === '\\') {
62 if (peek() === '\\') {
63 append(value + next());
64 } else {
65 // if the next char is not '\\', allow the "append" function
66 // to determine if the backslashes should be added
67 append(value);
68 append(next());
69 }
70 continue;
71 }
72
73 // handle quoted strings
74 if (quotes.includes(value)) {
75 let pos = i + 1;
76 let idx = closeIndex(value, pos);
77
78 if (idx > -1) {
79 append(value); // append opening quote
80 append(string.slice(pos, idx)); // append quoted string
81 append(string[idx]); // append closing quote
82 i = idx;
83 continue;
84 }
85
86 append(value);
87 continue;
88 }
89
90 // handle opening brackets, if not disabled
91 if (options.brackets !== false && openers[value]) {
92 node = { type: 'bracket', nodes: [] };
93 node.stash = keep(value) !== false ? [value] : [''];
94 node.parent = state.block;
95 state.block.nodes.push(node);
96 stack.push(node);
97 continue;
98 }
99
100 // handle closing brackets, if not disabled
101 if (options.brackets !== false && closers[value]) {
102 if (stack.length === 1) {
103 append(value);
104 continue;
105 }
106
107 append(value);
108 node = stack.pop();
109 block();
110 append(node.stash.join(''));
111 continue;
112 }
113
114 // push separator onto stash
115 if (value === separator && state.block.type === 'root') {
116 if (typeof fn === 'function' && fn(state) === false) {
117 append(value);
118 continue;
119 }
120 state.block.stash.push('');
121 continue;
122 }
123
124 // append value onto the last string on the stash
125 append(value);
126 }
127
128 node = stack.pop();
129
130 while (node !== ast) {
131 if (options.strict === true) {
132 let column = i - node.stash.length + 1;
133 throw new SyntaxError(`Unmatched: "${node.stash[0]}", at column ${column}`);
134 }
135
136 value = (node.parent.stash.pop() + node.stash.join('.'));
137 node.parent.stash = node.parent.stash.concat(value.split('.'));
138 node = stack.pop();
139 }
140
141 return node.stash;
142};
143
144function invert(obj) {
145 let inverted = {};
146 for (const key of Object.keys(obj)) inverted[obj[key]] = key;
147 return inverted;
148}