patroon 1.0.0 → 1.2.0
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.
- package/README.md +19 -10
- package/README.mz +10 -2
- package/package.json +1 -1
- package/src/helpers.js +10 -0
- package/src/index.js +8 -4
- package/src/index.test.js +69 -11
- package/src/walkable.js +18 -5
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Pattern matching in JavaScript without additional syntax.
|
|
|
27
27
|
+ [NoMatchError](#nomatcherror)
|
|
28
28
|
+ [UnevenArgumentCountError](#unevenargumentcounterror)
|
|
29
29
|
+ [PatroonError](#patroonerror)
|
|
30
|
+
- [Examples](#examples)
|
|
30
31
|
- [Tests](#tests)
|
|
31
32
|
- [Contribute](#contribute)
|
|
32
33
|
* [Contributors](#contributors)
|
|
@@ -517,12 +518,12 @@ const oneIsTwo = patroon(1, 2)
|
|
|
517
518
|
oneIsTwo(3)
|
|
518
519
|
```
|
|
519
520
|
```
|
|
520
|
-
|
|
521
|
-
/home/ant/projects/patroon/src/index.js:92
|
|
521
|
+
/home/ant/projects/patroon/src/index.js:96
|
|
522
522
|
throw error
|
|
523
523
|
^
|
|
524
524
|
|
|
525
525
|
NoMatchError: Not able to match any pattern for arguments
|
|
526
|
+
at /home/ant/projects/patroon/src/index.js:90:21
|
|
526
527
|
```
|
|
527
528
|
|
|
528
529
|
#### UnevenArgumentCountError
|
|
@@ -533,12 +534,12 @@ Another error that occurs is when the patroon function is not used correctly.
|
|
|
533
534
|
patroon(1)
|
|
534
535
|
```
|
|
535
536
|
```
|
|
536
|
-
/home/ant/projects/patroon/src/index.js:
|
|
537
|
+
/home/ant/projects/patroon/src/index.js:82
|
|
537
538
|
if (!isEven(list.length)) { throw new UnevenArgumentCountError('Patroon should have an even amount of arguments.') }
|
|
538
539
|
^
|
|
539
540
|
|
|
540
541
|
UnevenArgumentCountError: Patroon should have an even amount of arguments.
|
|
541
|
-
at patroon (/home/ant/projects/patroon/src/index.js:
|
|
542
|
+
at patroon (/home/ant/projects/patroon/src/index.js:82:37)
|
|
542
543
|
```
|
|
543
544
|
|
|
544
545
|
#### PatroonError
|
|
@@ -555,6 +556,14 @@ isPatroonError(new UnevenArgumentCountError())
|
|
|
555
556
|
patroon is causing an error
|
|
556
557
|
```
|
|
557
558
|
|
|
559
|
+
## Examples
|
|
560
|
+
|
|
561
|
+
Patroon can be used in any context that can benefit from pattern matching.
|
|
562
|
+
|
|
563
|
+
- The following tests show how patroon can help you test your JSON API by
|
|
564
|
+
pattern matching on status codes and the body:
|
|
565
|
+
https://github.com/bas080/didomi/blob/master/index.test.js.
|
|
566
|
+
|
|
558
567
|
## Tests
|
|
559
568
|
|
|
560
569
|
[./src/index.test.js][5] - Contains some tests for edge cases and it defines
|
|
@@ -564,8 +573,8 @@ We also care about code coverage so we'll use [nyc][8] to generate a coverage
|
|
|
564
573
|
report.
|
|
565
574
|
|
|
566
575
|
```bash bash -eo pipefail
|
|
567
|
-
#
|
|
568
|
-
|
|
576
|
+
# Clean install dependencies.
|
|
577
|
+
npm ci &> /dev/null
|
|
569
578
|
|
|
570
579
|
# Run tests and generate a coverage report
|
|
571
580
|
npx nyc npm t | npx tap-nyc
|
|
@@ -574,7 +583,7 @@ npx nyc npm t | npx tap-nyc
|
|
|
574
583
|
npx nyc check-coverage
|
|
575
584
|
```
|
|
576
585
|
```
|
|
577
|
-
> patroon@1.
|
|
586
|
+
> patroon@1.2.0 test
|
|
578
587
|
> tape ./src/index.test.js
|
|
579
588
|
-------------|---------|----------|---------|---------|-------------------
|
|
580
589
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
@@ -585,10 +594,10 @@ npx nyc check-coverage
|
|
|
585
594
|
walkable.js | 100 | 100 | 100 | 100 |
|
|
586
595
|
-------------|---------|----------|---------|---------|-------------------
|
|
587
596
|
|
|
588
|
-
total:
|
|
589
|
-
passing:
|
|
597
|
+
total: 28
|
|
598
|
+
passing: 28
|
|
590
599
|
|
|
591
|
-
duration:
|
|
600
|
+
duration: 8.8s
|
|
592
601
|
|
|
593
602
|
```
|
|
594
603
|
|
package/README.mz
CHANGED
|
@@ -425,6 +425,14 @@ isPatroonError(new NoMatchError())
|
|
|
425
425
|
isPatroonError(new UnevenArgumentCountError())
|
|
426
426
|
```
|
|
427
427
|
|
|
428
|
+
## Examples
|
|
429
|
+
|
|
430
|
+
Patroon can be used in any context that can benefit from pattern matching.
|
|
431
|
+
|
|
432
|
+
- The following tests show how patroon can help you test your JSON API by
|
|
433
|
+
pattern matching on status codes and the body:
|
|
434
|
+
https://github.com/bas080/didomi/blob/master/index.test.js.
|
|
435
|
+
|
|
428
436
|
## Tests
|
|
429
437
|
|
|
430
438
|
[./src/index.test.js][5] - Contains some tests for edge cases and it defines
|
|
@@ -434,8 +442,8 @@ We also care about code coverage so we'll use [nyc][8] to generate a coverage
|
|
|
434
442
|
report.
|
|
435
443
|
|
|
436
444
|
```bash bash -eo pipefail
|
|
437
|
-
#
|
|
438
|
-
|
|
445
|
+
# Clean install dependencies.
|
|
446
|
+
npm ci &> /dev/null
|
|
439
447
|
|
|
440
448
|
# Run tests and generate a coverage report
|
|
441
449
|
npx nyc npm t | npx tap-nyc
|
package/package.json
CHANGED
package/src/helpers.js
CHANGED
|
@@ -17,7 +17,17 @@ const T = () => true
|
|
|
17
17
|
const is = Ctor => instance => isDefined(instance) && (instance.constructor ===
|
|
18
18
|
Ctor || instance instanceof Ctor)
|
|
19
19
|
|
|
20
|
+
function isPrimitive (x) {
|
|
21
|
+
return x !== Object(x)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isEmpty (x) {
|
|
25
|
+
return !isPrimitive(x) && (Object.keys(x).length === 0)
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
module.exports = {
|
|
29
|
+
isPrimitive,
|
|
30
|
+
isEmpty,
|
|
21
31
|
isDefined,
|
|
22
32
|
always: x => () => x,
|
|
23
33
|
is,
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { mapLeaves, path, PathError } = require('./walkable')()
|
|
2
|
-
const { isFunction, equals, T, is, tryCatch, isEven, isNil, toPairs, always } = require('./helpers')
|
|
2
|
+
const { isFunction, equals, T, is, tryCatch, isEven, isNil, toPairs, always, isEmpty, isPrimitive } = require('./helpers')
|
|
3
|
+
const { inspect } = require('util')
|
|
3
4
|
|
|
4
5
|
const deprecated = (fn, message) => (...args) => {
|
|
5
6
|
console.error(`[patroon] deprecated: ${message}`)
|
|
@@ -39,10 +40,12 @@ const predicate = pattern => {
|
|
|
39
40
|
if (pattern && pattern[multiSymbol]) { return (...args) => pattern(...args) }
|
|
40
41
|
|
|
41
42
|
const normalize = (value, pth) => {
|
|
42
|
-
if (isRegExp(value)) return arg => value.test(arg)
|
|
43
|
+
if (isRegExp(value)) return arg => value.test(path(pth, arg))
|
|
43
44
|
|
|
44
45
|
if (isFunction(value)) { return arg => value(path(pth, arg)) }
|
|
45
46
|
|
|
47
|
+
if (isEmpty(value)) { return arg => !isPrimitive(path(pth, arg)) }
|
|
48
|
+
|
|
46
49
|
return arg => equals(path(pth, arg), value)
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -86,8 +89,9 @@ const patroon = (...list) => {
|
|
|
86
89
|
if (isNil(found)) {
|
|
87
90
|
const error = new NoMatchError('Not able to match any pattern for arguments')
|
|
88
91
|
error.arguments = args
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
error.patterns = list
|
|
93
|
+
error.arguments_inspect = inspect(args)
|
|
94
|
+
error.patterns_inspect = inspect(list)
|
|
91
95
|
|
|
92
96
|
throw error
|
|
93
97
|
}
|
package/src/index.test.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const { test } = require('tape')
|
|
2
|
+
const { isNil } = require('./helpers')
|
|
2
3
|
const { check, gen } = require('tape-check')
|
|
4
|
+
const { version } = require('../package')
|
|
3
5
|
const {
|
|
4
6
|
reference,
|
|
5
7
|
instanceOf,
|
|
@@ -24,6 +26,11 @@ test('Matches on a simple primitive', t => {
|
|
|
24
26
|
})
|
|
25
27
|
|
|
26
28
|
test('Matches using a RegExp', t => {
|
|
29
|
+
patroon(
|
|
30
|
+
{ a: /^bunion/ }, () => t.fail(),
|
|
31
|
+
{ a: /^banana/ }, () => t.pass()
|
|
32
|
+
)({ a: 'banana tree' })
|
|
33
|
+
|
|
27
34
|
patroon(
|
|
28
35
|
/^bunion/, () => t.fail(),
|
|
29
36
|
/^banana/, () => t.end()
|
|
@@ -51,16 +58,12 @@ test('Matches always when pattern equals value', check(gen.any, (t, val) => {
|
|
|
51
58
|
}))
|
|
52
59
|
|
|
53
60
|
test('Matches none of the patterns and throws', t => {
|
|
54
|
-
|
|
61
|
+
t.plan(1)
|
|
62
|
+
t.throws(() =>
|
|
55
63
|
patroon(
|
|
56
64
|
1, () => t.fail(),
|
|
57
65
|
2, () => t.fail()
|
|
58
|
-
)(3)
|
|
59
|
-
t.fail()
|
|
60
|
-
} catch (e) {
|
|
61
|
-
t.ok(e instanceof NoMatchError)
|
|
62
|
-
t.end()
|
|
63
|
-
}
|
|
66
|
+
)(3), NoMatchError)
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
test('Does not match when a value does not exist', t => {
|
|
@@ -145,11 +148,10 @@ test('Matches always when pattern equals value', check(gen.any, (t, val) => {
|
|
|
145
148
|
t.end()
|
|
146
149
|
}))
|
|
147
150
|
|
|
148
|
-
test('Matches when arguments match multi pattern', check(gen.any, (t,
|
|
151
|
+
test('Matches always when arguments match multi pattern', check(gen.array(gen.any, {}), (t, args) => {
|
|
149
152
|
patroon(
|
|
150
|
-
multi(
|
|
151
|
-
|
|
152
|
-
)(1, 2, 3)
|
|
153
|
+
multi(...args), () => t.end()
|
|
154
|
+
)(...args)
|
|
153
155
|
}))
|
|
154
156
|
|
|
155
157
|
test('Deprecated functions', ts => {
|
|
@@ -166,3 +168,59 @@ test('Deprecated functions', ts => {
|
|
|
166
168
|
console.error = consoleError
|
|
167
169
|
ts.end()
|
|
168
170
|
})
|
|
171
|
+
|
|
172
|
+
test('Returns primitive values', check(gen.primitive, (t, primitive) => {
|
|
173
|
+
t.equals(patroon(true, primitive)(true), primitive)
|
|
174
|
+
t.end()
|
|
175
|
+
}))
|
|
176
|
+
|
|
177
|
+
test(
|
|
178
|
+
'Does not match primitive with empty object or array',
|
|
179
|
+
check(gen.primitive, (t, primitive) => {
|
|
180
|
+
t.plan(2)
|
|
181
|
+
t.throws(() => patroon([], true)(primitive), NoMatchError)
|
|
182
|
+
t.throws(() => patroon({}, true)(primitive), NoMatchError)
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
test('Matching on nil values', check(gen.any, v => !isNil(v), (t, any) => {
|
|
186
|
+
t.plan(2)
|
|
187
|
+
t.throws(() => patroon(any, true)(undefined), NoMatchError)
|
|
188
|
+
t.throws(() => patroon(any, true)(null), NoMatchError)
|
|
189
|
+
}))
|
|
190
|
+
|
|
191
|
+
// Circular reference
|
|
192
|
+
const circular = {}
|
|
193
|
+
circular.a = circular
|
|
194
|
+
|
|
195
|
+
test('Allows recursive patterns', t => {
|
|
196
|
+
t.plan(1)
|
|
197
|
+
t.ok(patroon(circular, true)(circular))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('Throws and prints because path is not defined', t => {
|
|
201
|
+
t.plan(1)
|
|
202
|
+
t.throws(() => patroon({ b: _ }, true)(circular), NoMatchError)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('Does not throw when value is recursive', t => {
|
|
206
|
+
t.plan(1)
|
|
207
|
+
t.ok(patroon({ a: _ }, true)(circular))
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('Does not match on reference', t => {
|
|
211
|
+
t.plan(4)
|
|
212
|
+
t.ok(patroon({ a: {} }, true)({ a: {} }))
|
|
213
|
+
t.ok(patroon({ a: [] }, true)({ a: [] }))
|
|
214
|
+
t.ok(patroon({ a: [] }, true)({ a: {} }))
|
|
215
|
+
t.ok(patroon({ a: {} }, true)({ a: [] }))
|
|
216
|
+
t.end()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (version.startsWith('2')) {
|
|
220
|
+
test('Deprecated functions in version 2', t => {
|
|
221
|
+
t.plan(3)
|
|
222
|
+
t.ok(isNil(require('./index').typed))
|
|
223
|
+
t.ok(isNil(require('./index').t))
|
|
224
|
+
t.ok(isNil(require('./index').ref))
|
|
225
|
+
})
|
|
226
|
+
}
|
package/src/walkable.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
const { isFunction, isNil } = require('./helpers')
|
|
1
|
+
const { isEmpty, isFunction, isNil } = require('./helpers')
|
|
2
2
|
|
|
3
3
|
function walk (config, transform, item, path = []) {
|
|
4
4
|
if (config.isLeafe(item)) { return transform(item, path) }
|
|
5
5
|
|
|
6
|
+
if (config.isCircular(item)) { return transform(item, path) }
|
|
7
|
+
|
|
6
8
|
return config.map((v, i) => walk(config, transform, v, [...path, i]), item)
|
|
7
9
|
}
|
|
8
10
|
|
|
@@ -24,8 +26,16 @@ function isObject (v) {
|
|
|
24
26
|
return typeof v === 'object' && v !== null
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
const isCircular = (visited = []) => x => {
|
|
30
|
+
const isCircular = visited.includes(x)
|
|
31
|
+
|
|
32
|
+
visited.push(x)
|
|
33
|
+
|
|
34
|
+
return isCircular
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
function isLeafe (x) {
|
|
28
|
-
return Number.isNaN(x) || isNil(x) || x instanceof RegExp || isFunction(x) || (!isObject(x) && !isArray(x))
|
|
38
|
+
return Number.isNaN(x) || isNil(x) || x instanceof RegExp || isFunction(x) || isEmpty(x) || (!isObject(x) && !isArray(x))
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
function mapLeaves (config, cb, item) {
|
|
@@ -42,9 +52,9 @@ function path (pth, v, errorData) {
|
|
|
42
52
|
|
|
43
53
|
const [key, ...rest] = pth
|
|
44
54
|
|
|
45
|
-
if (!(key in v)) {
|
|
55
|
+
if (isNil(v) || !(key in v)) {
|
|
46
56
|
const error = new PathError(
|
|
47
|
-
`Cannot read
|
|
57
|
+
`Cannot read path ${pth}.`)
|
|
48
58
|
|
|
49
59
|
Object.assign(error, errorData)
|
|
50
60
|
|
|
@@ -83,7 +93,10 @@ function walkable (config = {}) {
|
|
|
83
93
|
reduce,
|
|
84
94
|
mapLeaves
|
|
85
95
|
].reduce((api, fn) => {
|
|
86
|
-
api[fn.name] = fn
|
|
96
|
+
api[fn.name] = (...args) => fn({
|
|
97
|
+
isCircular: isCircular(),
|
|
98
|
+
...config
|
|
99
|
+
}, ...args)
|
|
87
100
|
|
|
88
101
|
return api
|
|
89
102
|
}, api)
|