50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/docs/assets/css/style.scss:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 | @import "{{ site.theme }}";
5 |
6 | .wrapper {
7 | width: 900px;
8 | }
9 |
10 | section {
11 | width: 540px;
12 | }
13 |
14 | ul {
15 | margin-bottom: 5px;
16 | list-style: none;
17 | padding-left: 2em;
18 | }
19 |
20 | hr {
21 | margin: 20px 0;
22 | }
23 |
24 | a:hover {
25 | font-weight: inherit;
26 | }
27 |
28 | a code {
29 | color: #267CB9;
30 | }
31 |
32 | a code:hover {
33 | color: #006699;
34 | }
35 |
36 | details {
37 | padding-bottom: 20px;
38 | }
39 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Home
3 | ---
4 |
5 | VerbalExpressions is a JavaScript library that helps to construct difficult regular expressions. For more details, refer to the [README](//github.com/VerbalExpressions/JSVerbalExpressions#readme).
6 |
7 | # Table Of Contents
8 |
9 | - [`VerEx`](VerEx)
10 | - [`VerbalExpression`](VerbalExpression)
11 | - [Constructor](VerbalExpression/constructor)
12 | - [Utilities](VerbalExpression/utilities)
13 | - [Rules](VerbalExpression/rules)
14 | - [Special Characters](VerbalExpression/special-characters)
15 | - [Modifiers](VerbalExpression/modifiers)
16 | - [Loops](VerbalExpression/loops)
17 | - [Capture Groups](VerbalExpression/capture-groups)
18 | - [Miscellaneous](VerbalExpression/miscellaneous)
19 |
20 | ___
21 |
22 | Methods have a return type of [`VerbalExpression`](VerbalExpressions) except where mentioned otherwise. If there is no mention of a method's parameters, it is to be assumed that it has none.
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "verbal-expressions",
3 | "description": "JavaScript Regular expressions made easy",
4 | "version": "1.0.2",
5 | "keywords": [
6 | "regular expressions",
7 | "regex"
8 | ],
9 | "homepage": "https://github.com/VerbalExpressions/JSVerbalExpressions",
10 | "devDependencies": {
11 | "@babel/preset-env": "^7.12.1",
12 | "ava": "^3.13.0",
13 | "babel-core": "^6.26.3",
14 | "babel-plugin-transform-builtin-extend": "^1.1.2",
15 | "eslint": "^8.10.0",
16 | "eslint-config-airbnb": "^19.0.4",
17 | "eslint-plugin-import": "^2.22.1",
18 | "eslint-plugin-jsx-a11y": "^6.4.1",
19 | "eslint-plugin-react": "^7.21.5",
20 | "grunt": "^1.3.0",
21 | "grunt-ava": "^0.19.0",
22 | "grunt-babel": "^8.0.0",
23 | "grunt-contrib-uglify": "^5.0.0",
24 | "grunt-eslint": "^24.0.0",
25 | "grunt-markdownlint": "^3.1.0",
26 | "grunt-sourcemap-localize": "^0.1.0",
27 | "grunt-umd": "^3.0.0",
28 | "nyc": "^15.1.0"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git://github.com/VerbalExpressions/JSVerbalExpressions.git"
33 | },
34 | "bugs": {
35 | "url": "https://github.com/VerbalExpressions/JSVerbalExpressions/issues"
36 | },
37 | "main": "dist/verbalexpressions.js",
38 | "license": "MIT",
39 | "scripts": {
40 | "test": "grunt test",
41 | "test:verbose": "grunt test:verbose",
42 | "compile": "grunt compile",
43 | "grunt": "grunt",
44 | "build": "grunt build"
45 | },
46 | "types": "./typings/VerbalExpressions.d.ts",
47 | "engines": {
48 | "node": ">=9.2.0"
49 | },
50 | "dependencies": {},
51 | "prettier": {
52 | "singleQuote": true
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/test/tests.js:
--------------------------------------------------------------------------------
1 | const test = require('ava');
2 | const VerEx = require('../dist/verbalexpressions');
3 |
4 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#Using_test()_on_a_regex_with_the_global_flag
5 | function resetLastIndex(regex) {
6 | regex.lastIndex = 0;
7 | }
8 |
9 | test('constructor', (t) => {
10 | const testRegex = VerEx();
11 |
12 | t.true(testRegex instanceof RegExp, 'Should extend RegExp');
13 | t.is(testRegex.toString(), '/(?:)/gm', 'Should be empty regex with global, multiline matching');
14 | });
15 |
16 | // Utility //
17 |
18 | test('sanitize', (t) => {
19 | const testString = '$a^b\\c|d(e)f[g]h{i}j.k*l+m?n:o=p';
20 | const escaped = '\\$a\\^b\\\\c\\|d\\(e\\)f\\[g\\]h\\{i\\}j\\.k\\*l\\+m\\?n\\:o\\=p';
21 |
22 | t.is(VerEx().sanitize(testString), escaped, 'Special characters should be sanitized');
23 |
24 | t.is(VerEx().sanitize(42), 42);
25 | t.is(VerEx().sanitize(/foo/), 'foo');
26 |
27 | t.notThrows(() => VerEx().sanitize());
28 | t.notThrows(() => VerEx().sanitize(NaN));
29 | t.notThrows(() => VerEx().sanitize(null));
30 | t.notThrows(() => VerEx().sanitize(true));
31 | });
32 |
33 | test('add', (t) => {
34 | let testRegex = VerEx().startOfLine().withAnyCase().endOfLine();
35 | testRegex = testRegex.add('(?:foo)?');
36 |
37 | t.true(testRegex.source.startsWith('^'), 'Should retain old prefixes');
38 | t.true(testRegex.source.endsWith('$'), 'Should retain old suffixes');
39 |
40 | t.true(testRegex.test('foo'), 'Should add new rules');
41 | resetLastIndex(testRegex);
42 | t.true(testRegex.test(''), 'Should add new rules');
43 |
44 | t.true(testRegex.flags.includes('i'), 'Should retain old modifiers');
45 | });
46 |
47 | // Rules //
48 |
49 | test('startOfLine', (t) => {
50 | let testRegex = VerEx().startOfLine().then('a');
51 | let testString = 'a';
52 |
53 | t.true(testRegex.test(testString));
54 |
55 | resetLastIndex(testRegex);
56 | testString = 'ba';
57 | t.false(testRegex.test(testString));
58 |
59 | testRegex = testRegex.startOfLine(false); // start of line is no longer necessary
60 | testString = 'ba';
61 | t.true(testRegex.test(testString));
62 | });
63 |
64 | test('endOfLine', (t) => {
65 | let testRegex = VerEx().find('a').endOfLine();
66 | let testString = 'a';
67 |
68 | t.true(testRegex.test(testString));
69 |
70 | resetLastIndex(testRegex);
71 | testString = 'ab';
72 | t.false(testRegex.test(testString));
73 |
74 | testRegex = testRegex.endOfLine(false); // end of line is no longer necessary
75 | testString = 'ab';
76 | t.true(testRegex.test(testString));
77 | });
78 |
79 | function then(name, t) {
80 | let testRegex = VerEx()[name]('a');
81 | let testString = 'a';
82 |
83 | t.true(testRegex.test(testString));
84 |
85 | resetLastIndex(testRegex);
86 | testString = 'b';
87 | t.false(testRegex.test(testString));
88 |
89 | resetLastIndex(testRegex);
90 | testString = '';
91 | t.false(testRegex.test(testString));
92 |
93 | testRegex = VerEx()[name]('a')[name]('b');
94 | testString = 'ab';
95 | t.true(testRegex.test(testString));
96 |
97 | resetLastIndex(testRegex);
98 | testString = 'ac';
99 | t.false(testRegex.test(testString));
100 | }
101 |
102 | test('then', (t) => {
103 | then('then', t);
104 | });
105 |
106 | test('find', (t) => {
107 | then('find', t);
108 | });
109 |
110 | test('maybe', (t) => {
111 | const testRegex = VerEx().startOfLine().then('a').maybe('b');
112 | let testString = 'acb';
113 |
114 | t.true(testRegex.test(testString));
115 |
116 | resetLastIndex(testRegex);
117 | testString = 'abc';
118 | t.true(testRegex.test(testString));
119 | });
120 |
121 | test('or', (t) => {
122 | let testRegex = VerEx().startOfLine().then('abc').or('def');
123 | let testString = 'defzzz';
124 |
125 | t.true(testRegex.test(testString));
126 |
127 | resetLastIndex(testRegex);
128 | testString = 'abczzz';
129 | t.true(testRegex.test(testString));
130 |
131 | resetLastIndex(testRegex);
132 | testString = 'xyzabc';
133 | t.false(testRegex.test(testString));
134 |
135 | testRegex = VerEx().startOfLine().then('abc').or().then('def');
136 | testString = 'defzzz';
137 | t.true(testRegex.test(testString));
138 |
139 | resetLastIndex(testRegex);
140 | testString = 'abczzz';
141 | t.true(testRegex.test(testString));
142 |
143 | resetLastIndex(testRegex);
144 | testString = 'xyzabc';
145 | t.false(testRegex.test(testString));
146 | });
147 |
148 | test('anything', (t) => {
149 | const testRegex = VerEx().startOfLine().anything();
150 | let testString = 'foo';
151 |
152 | t.true(testRegex.test(testString));
153 |
154 | resetLastIndex(testRegex);
155 | testString = '';
156 | t.true(testRegex.test(testString), 'Should be able to match zero characters');
157 | });
158 |
159 | test('anythingBut', (t) => {
160 | let testRegex = VerEx().startOfLine().anythingBut('br').endOfLine();
161 | let testString = 'foobar';
162 |
163 | t.false(testRegex.test(testString));
164 |
165 | resetLastIndex(testRegex);
166 | testString = 'foo_a_';
167 | t.true(testRegex.test(testString));
168 |
169 | testRegex = VerEx().startOfLine().anythingBut('br');
170 | testString = 'bar';
171 | t.true(testRegex.test(testString), 'Should be able to match zero characters');
172 |
173 | testRegex = VerEx().startOfLine().anythingBut(['b', 'r']).endOfLine();
174 | testString = 'foobar';
175 | t.false(testRegex.test(testString));
176 |
177 | resetLastIndex(testRegex);
178 | testString = 'foo_a_';
179 | t.true(testRegex.test(testString));
180 |
181 | testRegex = VerEx().startOfLine().anythingBut(['b', 'r']);
182 | testString = 'bar';
183 | t.true(testRegex.test(testString), 'Should be able to match zero characters');
184 | });
185 |
186 | test('something', (t) => {
187 | const testRegex = VerEx().something();
188 | let testString = '';
189 |
190 | t.false(testRegex.test(testString));
191 |
192 | resetLastIndex(testRegex);
193 | testString = 'a';
194 | t.true(testRegex.test(testString));
195 | });
196 |
197 | test('somethingBut', (t) => {
198 | let testRegex = VerEx().startOfLine().somethingBut('abc').endOfLine();
199 | let testString = '';
200 | t.false(testRegex.test(testString));
201 |
202 | resetLastIndex(testRegex);
203 | testString = 'foo';
204 | t.true(testRegex.test(testString));
205 |
206 | resetLastIndex(testRegex);
207 | testString = 'fab';
208 | t.false(testRegex.test(testString));
209 |
210 | testRegex = VerEx().startOfLine().somethingBut(['a', 'b', 'c']).endOfLine();
211 | testString = '';
212 | t.false(testRegex.test(testString));
213 |
214 | resetLastIndex(testRegex);
215 | testString = 'foo';
216 | t.true(testRegex.test(testString));
217 |
218 | resetLastIndex(testRegex);
219 | testString = 'fab';
220 | t.false(testRegex.test(testString));
221 | });
222 |
223 | function anyOf(name, t) {
224 | let testRegex = VerEx().startOfLine().then('a')[name]('xyz');
225 | let testString = 'ay';
226 |
227 | t.true(testRegex.test(testString));
228 |
229 | resetLastIndex(testRegex);
230 | testString = 'ab';
231 | t.false(testRegex.test(testString));
232 |
233 | resetLastIndex(testRegex);
234 | testString = 'a';
235 | t.false(testRegex.test(testString));
236 |
237 | testRegex = VerEx().startOfLine().then('a')[name](['x', 'y', 'z']);
238 | testString = 'ay';
239 |
240 | t.true(testRegex.test(testString));
241 |
242 | resetLastIndex(testRegex);
243 | testString = 'ab';
244 | t.false(testRegex.test(testString));
245 |
246 | resetLastIndex(testRegex);
247 | testString = 'a';
248 | t.false(testRegex.test(testString));
249 | }
250 |
251 | test('anyOf', (t) => {
252 | anyOf('anyOf', t);
253 | });
254 |
255 | test('any', (t) => {
256 | anyOf('any', t);
257 | });
258 |
259 | test('not', (t) => {
260 | const testRegex = VerEx().startOfLine().not('foo').anything().endOfLine();
261 | let testString = 'foobar';
262 |
263 | t.false(testRegex.test(testString));
264 |
265 | resetLastIndex(testRegex);
266 | testString = 'bar';
267 | t.true(testRegex.test(testString));
268 | });
269 |
270 | test('range', (t) => {
271 | let testRegex = VerEx().startOfLine().range('a', 'z', '0', '9').oneOrMore().endOfLine();
272 | let testString = 'foobarbaz123';
273 |
274 | t.true(testRegex.test(testString));
275 |
276 | resetLastIndex(testRegex);
277 | testString = 'fooBarBaz_123';
278 | t.false(testRegex.test(testString));
279 |
280 | testRegex = VerEx().startOfLine().range('a', 'z', '0').oneOrMore().endOfLine();
281 | testString = 'foobarbaz';
282 | t.true(testRegex.test(testString));
283 |
284 | resetLastIndex(testRegex);
285 | testString = 'foobarbaz123';
286 | t.false(testRegex.test(testString), 'Should ignore extra parameters');
287 | });
288 |
289 | // Special characters //
290 |
291 | function lineBreak(name, t) {
292 | const testRegex = VerEx().startOfLine().then('abc')[name]().then('def');
293 | let testString = 'abc\r\ndef';
294 |
295 | t.true(testRegex.test(testString));
296 |
297 | resetLastIndex(testRegex);
298 | testString = 'abc\ndef';
299 | t.true(testRegex.test(testString));
300 |
301 | resetLastIndex(testRegex);
302 | testString = 'abc\rdef';
303 | t.true(testRegex.test(testString));
304 |
305 | resetLastIndex(testRegex);
306 | testString = 'abc\r\n\ndef';
307 | t.false(testRegex.test(testString));
308 | }
309 |
310 | test('lineBreak', (t) => {
311 | lineBreak('lineBreak', t);
312 | });
313 |
314 | test('br', (t) => {
315 | lineBreak('br', t);
316 | });
317 |
318 | test('tab', (t) => {
319 | const testRegex = VerEx().startOfLine().tab().then('abc');
320 | let testString = '\tabc';
321 |
322 | t.true(testRegex.test(testString));
323 |
324 | resetLastIndex(testRegex);
325 | testString = 'abc';
326 | t.false(testRegex.test(testString));
327 | });
328 |
329 | test('word', (t) => {
330 | let testRegex = VerEx().startOfLine().word().endOfLine();
331 | let testString = 'azertyuiopqsdfghjklmwxcvbn0123456789_';
332 |
333 | t.true(testRegex.test(testString));
334 |
335 | testRegex = VerEx().word();
336 | testString = '. @[]|,&~-';
337 | t.false(testRegex.test(testString));
338 | });
339 |
340 | test('digit', (t) => {
341 | let testRegex = VerEx().startOfLine().digit().oneOrMore().endOfLine();
342 | let testString = '0123456789';
343 |
344 | t.true(testRegex.test(testString));
345 |
346 | testRegex = VerEx().digit();
347 | testString = '-.azertyuiopqsdfghjklmwxcvbn @[]|,_&~';
348 | t.false(testRegex.test(testString));
349 | });
350 |
351 | test('whitespace', (t) => {
352 | const testRegex = VerEx().startOfLine().whitespace().oneOrMore().searchOneLine().endOfLine();
353 | let testString = ' \t\r\n\v\f';
354 |
355 | t.true(testRegex.test(testString));
356 |
357 | resetLastIndex(testRegex);
358 | testString = 'a z';
359 | t.false(testRegex.test(testString));
360 | });
361 |
362 | // Modifiers //
363 |
364 | test('addModifier', (t) => {
365 | let testRegex = VerEx().addModifier('y');
366 | t.true(testRegex.flags.includes('y'));
367 |
368 | t.notThrows(() => {
369 | testRegex = VerEx().addModifier('g');
370 | }, 'Should not add extra modifier if it already exists');
371 | });
372 |
373 | test('removeModifier', (t) => {
374 | const testRegex = VerEx().removeModifier('g');
375 | t.false(testRegex.flags.includes('g'));
376 | });
377 |
378 | test('withAnyCase', (t) => {
379 | let testRegex = VerEx().startOfLine().then('a');
380 | let testString = 'A';
381 |
382 | t.false(testRegex.test(testString));
383 |
384 | testRegex = VerEx().startOfLine().then('a').withAnyCase();
385 | testString = 'A';
386 | t.true(testRegex.test(testString));
387 |
388 | resetLastIndex(testRegex);
389 | testString = 'a';
390 | t.true(testRegex.test(testString));
391 |
392 | testRegex = VerEx().startOfLine().then('a').withAnyCase(false);
393 | testString = 'A';
394 | t.false(testRegex.test(testString));
395 | });
396 |
397 | test('stopAtFirst', (t) => {
398 | let testRegex = VerEx().find('foo');
399 | const testString = 'foofoofoo';
400 |
401 | t.is(testString.match(testRegex).length, 3, 'Should match all "foo"s');
402 |
403 | testRegex = VerEx().find('foo').stopAtFirst();
404 | t.is(testString.match(testRegex).length, 1, 'Should match one "foo"');
405 |
406 | testRegex = VerEx().find('foo').stopAtFirst(false);
407 | t.is(testString.match(testRegex).length, 3, 'Should match all "foo"s');
408 | });
409 |
410 | test('searchOneLine', (t) => {
411 | let testRegex = VerEx().startOfLine().then('b').endOfLine();
412 | const testString = 'a\nb\nc';
413 |
414 | t.true(testRegex.test(testString));
415 |
416 | testRegex = VerEx().startOfLine().then('b').endOfLine().searchOneLine();
417 | t.false(testRegex.test(testString));
418 |
419 | testRegex = VerEx().startOfLine().then('b').endOfLine().searchOneLine(false);
420 | t.true(testRegex.test(testString));
421 | });
422 |
423 | // Loops //
424 |
425 | test('repeatPrevious', (t) => {
426 | let testRegex = VerEx().startOfLine().find('foo').repeatPrevious(3).endOfLine();
427 | let testString = 'foofoofoo';
428 |
429 | t.true(testRegex.test(testString));
430 |
431 | resetLastIndex(testRegex);
432 | testString = 'foofoo';
433 | t.false(testRegex.test(testString));
434 |
435 | resetLastIndex(testRegex);
436 | testString = 'foofoofoofoo';
437 | t.false(testRegex.test(testString));
438 |
439 | resetLastIndex(testRegex);
440 | testString = 'bar';
441 | t.false(testRegex.test(testString));
442 |
443 | testRegex = VerEx().startOfLine().find('foo').repeatPrevious(1, 3).endOfLine();
444 |
445 | for (let i = 0; i <= 4; i++) {
446 | resetLastIndex(testRegex);
447 | testString = 'foo'.repeat(i);
448 |
449 | if (i < 1 || i > 3) {
450 | t.false(testRegex.test(testString));
451 | } else {
452 | t.true(testRegex.test(testString));
453 | }
454 | }
455 |
456 | testRegex = VerEx().startOfLine().find('foo').repeatPrevious().endOfLine();
457 | testString = 'foofoo';
458 | t.false(testRegex.test(testString), 'Should silently fail on edge cases');
459 |
460 | testRegex = VerEx().startOfLine().find('foo').repeatPrevious(1, 2, 3).endOfLine();
461 | testString = 'foofoo';
462 | t.false(testRegex.test(testString), 'Should silently fail on edge cases');
463 | });
464 |
465 | test('oneOrMore', (t) => {
466 | const testRegex = VerEx().startOfLine().then('foo').oneOrMore().endOfLine();
467 | let testString = 'foo';
468 |
469 | t.true(testRegex.test(testString));
470 |
471 | resetLastIndex(testRegex);
472 | testString = 'foofoo';
473 | t.true(testRegex.test(testString));
474 |
475 | resetLastIndex(testRegex);
476 | testString = 'bar';
477 | t.false(testRegex.test(testString));
478 | });
479 |
480 | test('multiple', (t) => {
481 | let testRegex = VerEx().startOfLine().find(' ').multiple().endOfLine();
482 | let testString = ' ';
483 | t.true(testRegex.test(testString));
484 |
485 | resetLastIndex(testRegex);
486 | testString = ' a ';
487 | t.false(testRegex.test(testString));
488 |
489 | testRegex = VerEx().startOfLine().multiple('foo').endOfLine();
490 | testString = 'foo';
491 |
492 | t.true(testRegex.test(testString));
493 |
494 | resetLastIndex(testRegex);
495 | testString = 'foofoofoo';
496 | t.true(testRegex.test(testString));
497 |
498 | resetLastIndex(testRegex);
499 | testString = '';
500 | t.true(testRegex.test(testString));
501 |
502 | testRegex = VerEx().startOfLine().multiple('foo', 2).endOfLine();
503 | testString = 'foo';
504 | t.false(testRegex.test(testString));
505 |
506 | resetLastIndex(testRegex);
507 | testString = 'foofoo';
508 | t.true(testRegex.test(testString));
509 |
510 | resetLastIndex(testRegex);
511 | testString = 'foofoofoo';
512 | t.true(testRegex.test(testString));
513 |
514 | testRegex = VerEx().startOfLine().multiple('foo', 2, 5).endOfLine();
515 |
516 | for (let i = 0; i <= 6; i++) {
517 | resetLastIndex(testRegex);
518 | testString = 'foo'.repeat(i);
519 |
520 | if (i < 2 || i > 5) {
521 | t.false(testRegex.test(testString));
522 | } else {
523 | t.true(testRegex.test(testString));
524 | }
525 | }
526 | });
527 |
528 | // Capture groups //
529 |
530 | test('capture groups', (t) => {
531 | let testRegex = VerEx().find('foo').beginCapture().then('bar');
532 | let testString = 'foobar';
533 |
534 | t.true(testRegex.test(testString), 'Expressions with incomplete capture groups should work');
535 |
536 | testRegex = testRegex.endCapture().then('baz');
537 | testString = 'foobarbaz';
538 | t.true(testRegex.test(testString));
539 |
540 | resetLastIndex(testRegex);
541 | const matches = testRegex.exec(testString);
542 | t.is(matches[1], 'bar');
543 | });
544 |
545 | // Miscellaneous //
546 |
547 | test('replace', (t) => {
548 | const testRegex = VerEx().find(' ');
549 | const testString = 'foo bar baz';
550 |
551 | t.is(testRegex.replace(testString, '_'), 'foo_bar_baz');
552 | });
553 |
554 | test('toRegExp', (t) => {
555 | const testRegex = VerEx().anything();
556 | const converted = testRegex.toRegExp();
557 |
558 | t.is(converted.toString(), testRegex.toString(), 'Converted regex should have same behaviour');
559 | });
560 |
--------------------------------------------------------------------------------
/typings/VerbalExpressions.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for JSVerbalExpressions
2 | // Project: https://github.com/VerbalExpressions/JSVerbalExpressions
3 | // Definitions by: Mihai Ionut Vilcu