@trenskow/parse 0.1.8 → 0.1.10

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.
Files changed (3) hide show
  1. package/index.js +74 -43
  2. package/package.json +1 -1
  3. package/test.js +19 -19
package/index.js CHANGED
@@ -1,20 +1,42 @@
1
1
  // Created 02/05/22 by Kristian Trenskow
2
2
  // See LICENSE for license.
3
3
 
4
- export default (opening, closing, options) => {
4
+ export default (...args) => {
5
5
 
6
- if (typeof opening !== 'string' || typeof closing !== 'string') {
7
- throw new TypeError('Opening and closing tokens must be strings.');
6
+ let boundaries;
7
+ let options;
8
+
9
+ if (typeof args[0] === 'string') {
10
+ boundaries = args.slice(0, 2);
11
+ options = args[2];
12
+ } else {
13
+ [ boundaries, options ] = args;
8
14
  }
9
15
 
10
- if (opening.length === 0 || closing.length === 0) {
11
- throw new TypeError('Opening and closing tokens cannot be zero-length.');
16
+ if (!Array.isArray(boundaries)) {
17
+ throw new TypeError('Boundaries must be an array.');
12
18
  }
13
19
 
14
- if (opening === closing) {
15
- throw new TypeError('Opening and closing tokens cannot be the same.');
20
+ if (boundaries.every((boundary) => typeof boundary === 'string')) {
21
+ boundaries = [boundaries];
16
22
  }
17
23
 
24
+ boundaries
25
+ .forEach((boundaries) => {
26
+ if (boundaries.length !== 2) {
27
+ throw new TypeError('Boundaries must be an array containing the opening and closing boundary.');
28
+ }
29
+ if (!boundaries.every((boundary) => typeof boundary === 'string')) {
30
+ throw new TypeError('Boundaries must be strings.');
31
+ }
32
+ if (!boundaries.every((boundary) => boundary.length > 0)) {
33
+ throw new TypeError('Boundaries cannot be zero-length.');
34
+ }
35
+ if (boundaries.every((boundary) => boundary === boundaries[0])) {
36
+ throw new TypeError('Boundary tokens cannot be the same.');
37
+ }
38
+ });
39
+
18
40
  if (typeof options === 'undefined') options = {};
19
41
  if (typeof options !== 'object' || options === null) {
20
42
  throw new TypeError('Options must be an object.');
@@ -37,11 +59,9 @@ export default (opening, closing, options) => {
37
59
  throw new TypeError('Max depth must be a number.');
38
60
  }
39
61
 
40
- let boundaries = options.boundaries;
41
-
42
- if (typeof boundaries === 'undefined') boundaries = 'exclude';
62
+ if (typeof options.boundaries === 'undefined') options.boundaries = 'exclude';
43
63
 
44
- if (!['exclude', 'include'].includes(boundaries)) {
64
+ if (!['exclude', 'include'].includes(options.boundaries)) {
45
65
  throw new TypeError('Boundaries must be either `\'exclude\'` (default) or `\'include\'`.');
46
66
  }
47
67
 
@@ -54,18 +74,24 @@ export default (opening, closing, options) => {
54
74
  return {
55
75
  do: (text) => {
56
76
 
57
- const next = (text, offset, depth) => {
77
+ let foundBoundaries = [];
58
78
 
59
- let result = [];
79
+ const next = (text, offset, depth) => {
60
80
 
61
- let literal = '';
81
+ let result = [''];
62
82
 
63
83
  let idx = offset;
64
84
  let ignoredDepths = 0;
65
85
 
86
+ const appendResult = (text) => {
87
+ result[result.length - 1] += text;
88
+ };
89
+
66
90
  for (idx ; idx < text.length ; idx++) {
67
91
 
68
- if (text[idx] === '\\') literal += text[++idx];
92
+ const nextBoundary = foundBoundaries[foundBoundaries.length - 1];
93
+
94
+ if (text[idx] === '\\') appendResult(text[++idx]);
69
95
  else {
70
96
 
71
97
  const matchedIgnore = ignoreInside
@@ -74,7 +100,7 @@ export default (opening, closing, options) => {
74
100
  .map(([_, matched]) => matched)[0];
75
101
 
76
102
  if (typeof matchedIgnore !== 'undefined') {
77
- literal += matchedIgnore;
103
+ appendResult(matchedIgnore);
78
104
  if (matchedIgnore === ignoring[ignoring.length - 1]) {
79
105
  ignoring.pop();
80
106
  } else {
@@ -82,67 +108,72 @@ export default (opening, closing, options) => {
82
108
  }
83
109
  }
84
110
  else if (ignoring.length === 0) {
85
- if (text.substring(idx, idx + opening.length) === opening) {
86
111
 
87
- if (maxDepth > depth) {
112
+ const boundary = boundaries.find((boundaries) => text.substring(idx, idx + boundaries[0].length) === boundaries[0]);
113
+
114
+ if (typeof boundary !== 'undefined') {
88
115
 
89
- result.push(literal);
116
+ foundBoundaries.push(boundary);
117
+
118
+ if (maxDepth > depth) {
90
119
 
91
120
  let value;
92
121
 
93
- [idx, value] = next(text, idx + opening.length, depth + 1);
122
+ [idx, value] = next(text, idx + boundary[0].length, depth + 1);
94
123
 
95
124
  result.push(value);
96
125
 
97
- literal = '';
126
+ result.push('');
98
127
 
99
128
  } else {
100
- literal += text[idx];
129
+ appendResult(text[idx]);
101
130
  ignoredDepths++;
102
131
  }
103
132
 
104
- } else if (text.substring(idx, idx + closing.length) === closing) {
133
+ } else if (typeof nextBoundary !== 'undefined' && text.substring(idx, idx + nextBoundary[1].length) === nextBoundary[1]) {
105
134
 
106
- if (ignoredDepths === 0) {
135
+ foundBoundaries.pop();
107
136
 
108
- if (literal.length > 0) result.push(literal);
137
+ if (ignoredDepths === 0) {
109
138
 
110
- if (result.length === 1 && typeof result[0] === 'string') {
111
- result = result[0];
139
+ if (depth > 0 && options.boundaries === 'include') {
140
+ if (Array.isArray(result[0])) result[0] = [nextBoundary[0]].concat(result[0]);
141
+ else result[0] = `${nextBoundary[0]}${result[0]}`;
142
+ if (Array.isArray(result[result.length - 1])) result.push(nextBoundary[1]);
143
+ else result[result.length - 1] = `${result[result.length - 1]}${nextBoundary[1]}`;
112
144
  }
113
145
 
114
- if (depth > 0 && boundaries === 'include') {
115
- if (Array.isArray(result)) {
116
- if (Array.isArray(result[0])) result[0] = [opening].concat(result[0]);
117
- else result[0] = `${opening}${result[0]}`;
118
- if (Array.isArray(result[result.length - 1])) result.push(closing);
119
- else result[result.length - 1] = `${result[result.length - 1]}${closing}`;
120
- } else {
121
- result = `${opening}${result}${closing}`;
122
- }
123
- }
146
+ idx += nextBoundary[1].length - 1;
124
147
 
125
- return [idx + closing.length - 1, result];
148
+ break;
126
149
 
127
150
  } else {
128
- literal += text[idx];
151
+ appendResult(text[idx]);
129
152
  ignoredDepths--;
130
153
  }
131
154
 
132
155
  } else {
133
- literal += text[idx];
156
+ appendResult(text[idx]);
134
157
  }
135
158
  }
136
- else literal += text[idx];
159
+ else appendResult(text[idx]);
137
160
 
138
161
  }
139
162
  }
140
163
 
141
- throw new Error('Missing closing token.');
164
+ if (Array.isArray(result)) {
165
+ result = result.filter((value) => value.length > 0);
166
+ }
167
+
168
+ if (result.length === 1 && typeof result[0] === 'string') {
169
+ result = result[0];
170
+ }
171
+
172
+ return [idx, result];
142
173
 
143
174
  };
144
175
 
145
- return next(text + closing, 0, 0)[1];
176
+ return next(text, 0, 0)[1];
146
177
 
147
178
  }
148
179
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/parse",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "A small library for parsing a string into a tree.",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/test.js CHANGED
@@ -35,19 +35,19 @@ describe('parser', () => {
35
35
  ]);
36
36
  });
37
37
  it ('should come back with parsed tree (with escapes and boundaries).', () => {
38
- expect(parse('[', ']', { boundaries: 'include' }).do('This [is [my [\\[nested\\]] string]].')).to.eql([
39
- 'This ',
38
+ expect(parse([['[', ']'], ['{', '}']], { boundaries: 'include' }).do('This [is {my [\\[nested\\]] string}].')).to.eql(
40
39
  [
41
- '[is ',
40
+ 'This ',
42
41
  [
43
- '[my ',
44
- '[[nested]]',
45
- ' string]'
42
+ '[is ',
43
+ [
44
+ '{my ',
45
+ '[[nested]]',
46
+ ' string}'
47
+ ],
48
+ ']'
46
49
  ],
47
- ']'
48
- ],
49
- '.'
50
- ]);
50
+ '.']);
51
51
  });
52
52
  it ('should come back with parsed tree (long tokens).', () => {
53
53
  expect(parse('hello', 'goodbye').do('This hello is hello my hello nested goodbye string goodbye goodbye.')).to.eql([
@@ -73,10 +73,10 @@ describe('parser', () => {
73
73
  ]);
74
74
  });
75
75
  it ('should come back with parsed tree (max depth = 1).', () => {
76
- expect(parse('[', ']', { maxDepth: 1 }).do('This [is [my nested string]].')).to.eql([
76
+ expect(parse([['${', '}'], ['$[', ']']], { boundaries: 'include', maxDepth: 1 }).do('This ${is $[a]} test')).to.eql([
77
77
  'This ',
78
- 'is [my nested string]',
79
- '.'
78
+ '${is $[a]}',
79
+ ' test'
80
80
  ]);
81
81
  });
82
82
  it ('should come back with parsed tree (max depth = 0).', () => {
@@ -86,23 +86,23 @@ describe('parser', () => {
86
86
  });
87
87
  it ('should throw an error if closing token is missing.', () => {
88
88
  expect(() => {
89
- parse('[', ']').do('[this');
90
- }).to.throw('Missing closing token.');
89
+ parse('[').do('[this');
90
+ }).to.throw('Boundaries must be an array containing the opening and closing boundary.');
91
91
  });
92
92
  it ('should throw an error if opening or closing tokens are not a string.', () => {
93
93
  expect(() => {
94
- parse(0, 1).do('[this');
95
- }).to.throw('Opening and closing tokens must be strings.');
94
+ parse([[0, 1]]).do('[this');
95
+ }).to.throw('Boundaries must be strings.');
96
96
  });
97
97
  it ('should throw an error if opening or closing tokens are zero-length.', () => {
98
98
  expect(() => {
99
99
  parse('', '').do('[this');
100
- }).to.throw('Opening and closing tokens cannot be zero-length.');
100
+ }).to.throw('Boundaries cannot be zero-length.');
101
101
  });
102
102
  it ('should throw an error if opening or closing are the same.', () => {
103
103
  expect(() => {
104
104
  parse('123', '123').do('[this');
105
- }).to.throw('Opening and closing tokens cannot be the same.');
105
+ }).to.throw('Boundary tokens cannot be the same.');
106
106
  });
107
107
  it ('should throw an error if options is not an object.', () => {
108
108
  expect(() => {