@trenskow/parse 0.1.7 → 0.1.9

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.
@@ -5,7 +5,7 @@
5
5
  "version": "0.2.0",
6
6
  "configurations": [
7
7
  {
8
- "type": "pwa-node",
8
+ "type": "node",
9
9
  "request": "launch",
10
10
  "name": "Run test",
11
11
  "skipFiles": [
@@ -0,0 +1,6 @@
1
+ {
2
+ "[javascript]": {
3
+ "editor.formatOnSave": true,
4
+ "editor.formatOnPaste": true
5
+ }
6
+ }
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,6 +59,12 @@ export default (opening, closing, options) => {
37
59
  throw new TypeError('Max depth must be a number.');
38
60
  }
39
61
 
62
+ if (typeof options.boundaries === 'undefined') options.boundaries = 'exclude';
63
+
64
+ if (!['exclude', 'include'].includes(options.boundaries)) {
65
+ throw new TypeError('Boundaries must be either `\'exclude\'` (default) or `\'include\'`.');
66
+ }
67
+
40
68
  if (maxDepth < 0) {
41
69
  throw new TypeError('Max depth must be greater than zero.');
42
70
  }
@@ -46,18 +74,24 @@ export default (opening, closing, options) => {
46
74
  return {
47
75
  do: (text) => {
48
76
 
49
- const next = (text, offset, depth) => {
77
+ let foundBoundaries = [];
50
78
 
51
- let result = [];
79
+ const next = (text, offset, depth) => {
52
80
 
53
- let literal = '';
81
+ let result = [''];
54
82
 
55
83
  let idx = offset;
56
84
  let ignoredDepths = 0;
57
85
 
86
+ const appendResult = (text) => {
87
+ result[result.length - 1] += text;
88
+ };
89
+
58
90
  for (idx ; idx < text.length ; idx++) {
59
91
 
60
- if (text[idx] === '\\') literal += text[++idx];
92
+ const nextBoundary = foundBoundaries[foundBoundaries.length - 1];
93
+
94
+ if (text[idx] === '\\') appendResult(text[++idx]);
61
95
  else {
62
96
 
63
97
  const matchedIgnore = ignoreInside
@@ -66,7 +100,7 @@ export default (opening, closing, options) => {
66
100
  .map(([_, matched]) => matched)[0];
67
101
 
68
102
  if (typeof matchedIgnore !== 'undefined') {
69
- literal += matchedIgnore;
103
+ appendResult(matchedIgnore);
70
104
  if (matchedIgnore === ignoring[ignoring.length - 1]) {
71
105
  ignoring.pop();
72
106
  } else {
@@ -74,50 +108,72 @@ export default (opening, closing, options) => {
74
108
  }
75
109
  }
76
110
  else if (ignoring.length === 0) {
77
- if (text.substring(idx, idx + opening.length) === opening) {
111
+
112
+ const boundary = boundaries.find((boundaries) => text.substring(idx, idx + boundaries[0].length) === boundaries[0]);
113
+
114
+ if (typeof boundary !== 'undefined') {
78
115
 
79
116
  if (maxDepth > depth) {
80
- result.push(literal);
117
+
118
+ foundBoundaries.push(boundary);
119
+
81
120
  let value;
82
- [idx, value] = next(text, idx + opening.length, depth + 1);
121
+
122
+ [idx, value] = next(text, idx + boundary[0].length, depth + 1);
123
+
83
124
  result.push(value);
84
- literal = '';
125
+
126
+ result.push('');
127
+
85
128
  } else {
86
- literal += text[idx];
129
+ appendResult(text[idx]);
87
130
  ignoredDepths++;
88
131
  }
89
132
 
90
- } else if (text.substring(idx, idx + closing.length) === closing) {
133
+ } else if (typeof nextBoundary !== 'undefined' && text.substring(idx, idx + nextBoundary[1].length) === nextBoundary[1]) {
91
134
 
92
135
  if (ignoredDepths === 0) {
93
136
 
94
- if (literal.length > 0) result.push(literal);
137
+ foundBoundaries.pop();
95
138
 
96
- if (result.length === 1 && typeof result[0] === 'string') {
97
- 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]}`;
98
144
  }
99
145
 
100
- return [idx + closing.length - 1, result];
146
+ idx += nextBoundary[1].length - 1;
147
+
148
+ break;
101
149
 
102
150
  } else {
103
- literal += text[idx];
151
+ appendResult(text[idx]);
104
152
  ignoredDepths--;
105
153
  }
106
154
 
107
155
  } else {
108
- literal += text[idx];
156
+ appendResult(text[idx]);
109
157
  }
110
158
  }
111
- else literal += text[idx];
159
+ else appendResult(text[idx]);
112
160
 
113
161
  }
114
162
  }
115
163
 
116
- 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];
117
173
 
118
174
  };
119
175
 
120
- return next(text + closing, 0, 0)[1];
176
+ return next(text, 0, 0)[1];
121
177
 
122
178
  }
123
179
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/parse",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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
@@ -34,6 +34,21 @@ describe('parser', () => {
34
34
  '.'
35
35
  ]);
36
36
  });
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
+ [
40
+ 'This ',
41
+ [
42
+ '[is ',
43
+ [
44
+ '{my ',
45
+ '[[nested]]',
46
+ ' string}'
47
+ ],
48
+ ']'
49
+ ],
50
+ '.']);
51
+ });
37
52
  it ('should come back with parsed tree (long tokens).', () => {
38
53
  expect(parse('hello', 'goodbye').do('This hello is hello my hello nested goodbye string goodbye goodbye.')).to.eql([
39
54
  'This ',
@@ -71,23 +86,23 @@ describe('parser', () => {
71
86
  });
72
87
  it ('should throw an error if closing token is missing.', () => {
73
88
  expect(() => {
74
- parse('[', ']').do('[this');
75
- }).to.throw('Missing closing token.');
89
+ parse('[').do('[this');
90
+ }).to.throw('Boundaries must be an array containing the opening and closing boundary.');
76
91
  });
77
92
  it ('should throw an error if opening or closing tokens are not a string.', () => {
78
93
  expect(() => {
79
- parse(0, 1).do('[this');
80
- }).to.throw('Opening and closing tokens must be strings.');
94
+ parse([[0, 1]]).do('[this');
95
+ }).to.throw('Boundaries must be strings.');
81
96
  });
82
97
  it ('should throw an error if opening or closing tokens are zero-length.', () => {
83
98
  expect(() => {
84
99
  parse('', '').do('[this');
85
- }).to.throw('Opening and closing tokens cannot be zero-length.');
100
+ }).to.throw('Boundaries cannot be zero-length.');
86
101
  });
87
102
  it ('should throw an error if opening or closing are the same.', () => {
88
103
  expect(() => {
89
104
  parse('123', '123').do('[this');
90
- }).to.throw('Opening and closing tokens cannot be the same.');
105
+ }).to.throw('Boundary tokens cannot be the same.');
91
106
  });
92
107
  it ('should throw an error if options is not an object.', () => {
93
108
  expect(() => {
@@ -109,4 +124,9 @@ describe('parser', () => {
109
124
  parse('[', ']', { maxDepth: -1 });
110
125
  }).to.throw('Max depth must be greater than zero.');
111
126
  });
127
+ it ('should throw an error if boundaries is unknown value.', () => {
128
+ expect(() => {
129
+ parse('[', ']', { boundaries: 'wrong' });
130
+ }).to.throw('Boundaries must be either `\'exclude\'` (default) or `\'include\'`.');
131
+ });
112
132
  });