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 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
- [ 3 ]
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:79
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:79:37)
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
- # Install and prune dependencies
568
- { npm i && npm prune; } &> /dev/null
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.0.0 test
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: 15
589
- passing: 15
597
+ total: 28
598
+ passing: 28
590
599
 
591
- duration: 1s
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
- # Install and prune dependencies
438
- { npm i && npm prune; } &> /dev/null
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patroon",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Pattern matching library",
5
5
  "repository": "github:bas080/patroon",
6
6
  "main": "./src/index.js",
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
- console.error(args)
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
- try {
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, val) => {
151
+ test('Matches always when arguments match multi pattern', check(gen.array(gen.any, {}), (t, args) => {
149
152
  patroon(
150
- multi(0, 1, 2), () => t.fail(),
151
- multi(1, 2, 3), () => t.end()
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 property '${JSON.stringify(pth)}' of ${JSON.stringify(v)}`)
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.bind(null, config)
96
+ api[fn.name] = (...args) => fn({
97
+ isCircular: isCircular(),
98
+ ...config
99
+ }, ...args)
87
100
 
88
101
  return api
89
102
  }, api)