patroon 0.0.5 → 0.1.2

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/.nycrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "branches": 100,
3
+ "lines": 100,
4
+ "functions": 100,
5
+ "statements": 100
6
+ }
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Patroon
2
2
 
3
+ ![Code Coverage](https://img.shields.io/badge/coverage-100%25-green?style=flat-square)
4
+ ![NPM](https://img.shields.io/npm/v/patroon?color=blue&style=flat-square)
5
+
3
6
  Pattern matching in Javascript without additional syntax.
4
7
 
5
8
  ## Implementation
@@ -13,10 +16,41 @@ Pattern matching in Javascript without additional syntax.
13
16
  3. [./src/helpers.js][4] - You might have noticed that both the patroon and
14
17
  walkable modules have common helper functions.
15
18
 
19
+ ## Installation
20
+
21
+ [Patroon][9] is hosted on the NPM repository.
22
+
23
+ ```bash
24
+ npm install patroon
25
+ ```
26
+
16
27
  ## Specifications
17
28
 
18
29
  Let's see what valid and less valid uses of patroon are.
19
30
 
31
+ ### Primitives
32
+
33
+ The simplest thing one can do is match on a primitive.
34
+
35
+ ```js ./tape-test
36
+ t.equal(patroon(
37
+ 2, 3,
38
+ 1, 2
39
+ )(1), 2)
40
+ t.end()
41
+ ```
42
+
43
+ ### Regular Expressions
44
+
45
+ Will check if a Regex matches the passed string using the `.test` method.
46
+
47
+ ```js ./tape-test
48
+ patroon(
49
+ /^bunion/, fail,
50
+ /^banana/,end
51
+ )('banana tree')
52
+ ```
53
+
20
54
  ### Arrays
21
55
 
22
56
  A less intuitive case (at least initially) is the matching with an empty array.
@@ -31,7 +65,7 @@ patroon(
31
65
  Notice that the empty array matches with `[1]`. This is because the empty array
32
66
  is a subset of `[1]`.
33
67
 
34
- In this case you might as well write the following for readability sake:
68
+ You might as well write the following for readability sake:
35
69
 
36
70
  ```js ./tape-test
37
71
  patroon(
@@ -39,8 +73,8 @@ patroon(
39
73
  )([1])
40
74
  ```
41
75
 
42
- Patroon even tries to determine if something is a constructor. No need to use
43
- typed in that case.
76
+ Patroon tries to determine if something is a constructor. No need to use typed
77
+ for this case.
44
78
 
45
79
  ```js ./tape-test
46
80
  patroon(
@@ -49,7 +83,8 @@ patroon(
49
83
  )([1])
50
84
  ```
51
85
 
52
- If you wish to match on the reference of a constructor you can use the `ref` helper.
86
+ If you wish to match on the reference of a constructor you can use the `ref`
87
+ helper.
53
88
 
54
89
  ```js ./tape-test
55
90
  patroon(
@@ -76,6 +111,20 @@ t.equal(arrayMatch([2, 3]), 1)
76
111
  t.end()
77
112
  ```
78
113
 
114
+ How to define default argument values:
115
+
116
+ ```js ./tape-test
117
+ const count = patroon(
118
+ [_], ([, ...xs], n=0) => count(xs, n + 1),
119
+ [], (array, c=0) => c
120
+ )
121
+
122
+ t.equal(count([]), 0)
123
+ t.equal(count([1]), 1)
124
+ t.equal(count([1, 2]), 2)
125
+ t.end()
126
+ ```
127
+
79
128
  The array pattern assumes that the array has rest elements. It's a design
80
129
  choice which avoids adding additional helpers with little to no downsides.
81
130
 
@@ -116,10 +165,10 @@ const toPairs = patroon(
116
165
  _, (_, p = []) => p
117
166
  )
118
167
 
119
- t.deepEquals(toPairs([1]), [])
120
- t.deepEquals(toPairs([1, 2]), [[1, 2]])
121
- t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
122
- t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
168
+ t.deepEqual(toPairs([1]), [])
169
+ t.deepEqual(toPairs([1, 2]), [[1, 2]])
170
+ t.deepEqual(toPairs([1, 2, 3]), [[1, 2]])
171
+ t.deepEqual(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
123
172
  t.end()
124
173
  ```
125
174
 
@@ -131,7 +180,7 @@ So that's arrays. What about objects.
131
180
 
132
181
  ### Objects
133
182
 
134
- Just like an empty array; matching on an empty object can be written in two
183
+ Just like an empty array; matching on an empty object can be written in three
135
184
  ways.
136
185
 
137
186
  ```js ./tape-test
@@ -195,6 +244,7 @@ patroon(
195
244
  ```
196
245
 
197
246
  An object of a certain type might also have values we would want to match on.
247
+ Here you do need to use the typed helper.
198
248
 
199
249
  ```js ./tape-test
200
250
  patroon(
@@ -250,37 +300,72 @@ patroon(
250
300
  )([{a: 42}])
251
301
  ```
252
302
 
303
+ ### Custom Helpers
304
+
305
+ It is very easy to write your own helpers. All the builtin helpers are really
306
+ just predicates. Let's look at the source of one of these helpers, the simplest
307
+ one being the `_` helper.
308
+
309
+ ```js
310
+ const _ = () => true
311
+ ```
312
+
313
+ Other more complex helpers like the typed helper are also predicates. See the
314
+ [./src/index.js][3] if you are interested in their implementation.
315
+
253
316
  ## Tests
254
317
 
255
318
  Now for some additional edge cases and some generative testing.
256
319
  [./src/index.test.js][5]
257
320
 
321
+ First make sure the dependencies are clean.
322
+
323
+ ```bash bash &> /dev/null
324
+ npm i && npm prune
325
+ ```
326
+
327
+ We also care about code coverage so we'll use [nyc][8] to generate a coverage
328
+ report.
329
+
258
330
  ```bash bash
259
- npm test
331
+ npx nyc npm t && npx nyc check-coverage
260
332
  ```
261
333
  ```
262
334
 
263
- > patroon@0.0.5 test
335
+ > patroon@0.1.2 test
264
336
  > tape ./src/index.test.js
265
337
 
266
338
  TAP version 13
339
+ # Matches on a simple primitive
340
+ ok 1 should be strictly equal
341
+ # Matches using a RegExp
342
+ # Match using reference
343
+ # Matches on type and value
267
344
  # Matches always when pattern equals value
268
345
  # Matches none of the patterns and throws
269
- ok 1 should be truthy
346
+ ok 2 should be truthy
270
347
  # Does not match when a value does not exist
271
348
  # Throws when a typed does not receice a constructor
272
- ok 2 should be truthy
273
- # Throws in a predicate function
274
349
  ok 3 should be truthy
275
- # Throws when an uneven amount of arguments are passed
350
+ # Throws in a predicate function
276
351
  ok 4 should be truthy
352
+ # Throws when an uneven amount of arguments are passed
353
+ ok 5 should be truthy
277
354
 
278
- 1..4
279
- # tests 4
280
- # pass 4
355
+ 1..5
356
+ # tests 5
357
+ # pass 5
281
358
 
282
359
  # ok
283
360
 
361
+ -------------|---------|----------|---------|---------|-------------------
362
+ File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
363
+ -------------|---------|----------|---------|---------|-------------------
364
+ All files | 100 | 100 | 100 | 100 |
365
+ helpers.js | 100 | 100 | 100 | 100 |
366
+ index.js | 100 | 100 | 100 | 100 |
367
+ walkable.js | 100 | 100 | 100 | 100 |
368
+ -------------|---------|----------|---------|---------|-------------------
284
369
  ```
285
370
 
286
371
  ## Formatting
@@ -291,12 +376,16 @@ Standard is good enough.
291
376
  npx standard || npx standard --fix
292
377
  ```
293
378
 
379
+ ## Stackoverflow
380
+
381
+ This project is mentioned in the following [stackoverflow question][7].
382
+
294
383
  ## Documentation
295
384
 
296
385
  The README.md is generated using [markatzea][6].
297
386
 
298
- ```bash bash
299
- test "$RECUR" -eq 1 || RECUR=1 markatzea README.mz | tee README.md
387
+ ```bash
388
+ markatzea README.mz > README.md
300
389
  ```
301
390
 
302
391
  ## Contribute
@@ -304,11 +393,11 @@ test "$RECUR" -eq 1 || RECUR=1 markatzea README.mz | tee README.md
304
393
  You may contribute in whatever manner you see fit. Do try to be helpful and
305
394
  polite. Some suggestions for contributions:
306
395
 
307
- - [ ] Matching on strings in a similar manner to arrays.
308
- - [ ] Allow regular expressions for matching strings.
396
+ - [ ] A fitting logo.
309
397
  - [ ] Find and report bugs and inconsistencies.
398
+ - [ ] Improve documentation.
399
+ - [ ] Prevent exceeding stack size by managing recursive patterns.
310
400
  - [ ] Suggest API improvements in naming and functionality.
311
- - [ ] A fitting logo.
312
401
 
313
402
  [1]:https://en.wikipedia.org/wiki/Tree_traversal
314
403
  [2]:https://github.com/bas080/patroon/blob/master/src/walkable.js
@@ -316,3 +405,6 @@ polite. Some suggestions for contributions:
316
405
  [4]:https://github.com/bas080/patroon/blob/master/src/helpers.js
317
406
  [5]:https://github.com/bas080/patroon/blob/master/src/index.test.js
318
407
  [6]:https://github.com/bas080/markatzea
408
+ [7]:https://stackoverflow.com/questions/50452844/functional-programming-style-pattern-matching-in-javascript/67376827#67376827
409
+ [8]:https://github.com/istanbuljs/nyc
410
+ [9]:https://www.npmjs.com/package/patroon
package/README.mz CHANGED
@@ -1,5 +1,8 @@
1
1
  # Patroon
2
2
 
3
+ ![Code Coverage](https://img.shields.io/badge/coverage-100%25-green?style=flat-square)
4
+ ![NPM](https://img.shields.io/npm/v/patroon?color=blue&style=flat-square)
5
+
3
6
  Pattern matching in Javascript without additional syntax.
4
7
 
5
8
  ## Implementation
@@ -13,10 +16,41 @@ Pattern matching in Javascript without additional syntax.
13
16
  3. [./src/helpers.js][4] - You might have noticed that both the patroon and
14
17
  walkable modules have common helper functions.
15
18
 
19
+ ## Installation
20
+
21
+ [Patroon][9] is hosted on the NPM repository.
22
+
23
+ ```bash
24
+ npm install patroon
25
+ ```
26
+
16
27
  ## Specifications
17
28
 
18
29
  Let's see what valid and less valid uses of patroon are.
19
30
 
31
+ ### Primitives
32
+
33
+ The simplest thing one can do is match on a primitive.
34
+
35
+ ```js ./tape-test
36
+ t.equal(patroon(
37
+ 2, 3,
38
+ 1, 2
39
+ )(1), 2)
40
+ t.end()
41
+ ```
42
+
43
+ ### Regular Expressions
44
+
45
+ Will check if a Regex matches the passed string using the `.test` method.
46
+
47
+ ```js ./tape-test
48
+ patroon(
49
+ /^bunion/, fail,
50
+ /^banana/,end
51
+ )('banana tree')
52
+ ```
53
+
20
54
  ### Arrays
21
55
 
22
56
  A less intuitive case (at least initially) is the matching with an empty array.
@@ -31,7 +65,7 @@ patroon(
31
65
  Notice that the empty array matches with `[1]`. This is because the empty array
32
66
  is a subset of `[1]`.
33
67
 
34
- In this case you might as well write the following for readability sake:
68
+ You might as well write the following for readability sake:
35
69
 
36
70
  ```js ./tape-test
37
71
  patroon(
@@ -39,8 +73,8 @@ patroon(
39
73
  )([1])
40
74
  ```
41
75
 
42
- Patroon even tries to determine if something is a constructor. No need to use
43
- typed in that case.
76
+ Patroon tries to determine if something is a constructor. No need to use typed
77
+ for this case.
44
78
 
45
79
  ```js ./tape-test
46
80
  patroon(
@@ -49,7 +83,8 @@ patroon(
49
83
  )([1])
50
84
  ```
51
85
 
52
- If you wish to match on the reference of a constructor you can use the `ref` helper.
86
+ If you wish to match on the reference of a constructor you can use the `ref`
87
+ helper.
53
88
 
54
89
  ```js ./tape-test
55
90
  patroon(
@@ -76,6 +111,20 @@ t.equal(arrayMatch([2, 3]), 1)
76
111
  t.end()
77
112
  ```
78
113
 
114
+ How to define default argument values:
115
+
116
+ ```js ./tape-test
117
+ const count = patroon(
118
+ [_], ([, ...xs], n=0) => count(xs, n + 1),
119
+ [], (array, c=0) => c
120
+ )
121
+
122
+ t.equal(count([]), 0)
123
+ t.equal(count([1]), 1)
124
+ t.equal(count([1, 2]), 2)
125
+ t.end()
126
+ ```
127
+
79
128
  The array pattern assumes that the array has rest elements. It's a design
80
129
  choice which avoids adding additional helpers with little to no downsides.
81
130
 
@@ -116,10 +165,10 @@ const toPairs = patroon(
116
165
  _, (_, p = []) => p
117
166
  )
118
167
 
119
- t.deepEquals(toPairs([1]), [])
120
- t.deepEquals(toPairs([1, 2]), [[1, 2]])
121
- t.deepEquals(toPairs([1, 2, 3]), [[1, 2]])
122
- t.deepEquals(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
168
+ t.deepEqual(toPairs([1]), [])
169
+ t.deepEqual(toPairs([1, 2]), [[1, 2]])
170
+ t.deepEqual(toPairs([1, 2, 3]), [[1, 2]])
171
+ t.deepEqual(toPairs([1, 2, 3, 4]), [[1, 2], [3, 4]])
123
172
  t.end()
124
173
  ```
125
174
 
@@ -131,7 +180,7 @@ So that's arrays. What about objects.
131
180
 
132
181
  ### Objects
133
182
 
134
- Just like an empty array; matching on an empty object can be written in two
183
+ Just like an empty array; matching on an empty object can be written in three
135
184
  ways.
136
185
 
137
186
  ```js ./tape-test
@@ -195,6 +244,7 @@ patroon(
195
244
  ```
196
245
 
197
246
  An object of a certain type might also have values we would want to match on.
247
+ Here you do need to use the typed helper.
198
248
 
199
249
  ```js ./tape-test
200
250
  patroon(
@@ -250,13 +300,35 @@ patroon(
250
300
  )([{a: 42}])
251
301
  ```
252
302
 
303
+ ### Custom Helpers
304
+
305
+ It is very easy to write your own helpers. All the builtin helpers are really
306
+ just predicates. Let's look at the source of one of these helpers, the simplest
307
+ one being the `_` helper.
308
+
309
+ ```js
310
+ const _ = () => true
311
+ ```
312
+
313
+ Other more complex helpers like the typed helper are also predicates. See the
314
+ [./src/index.js][3] if you are interested in their implementation.
315
+
253
316
  ## Tests
254
317
 
255
318
  Now for some additional edge cases and some generative testing.
256
319
  [./src/index.test.js][5]
257
320
 
321
+ First make sure the dependencies are clean.
322
+
323
+ ```bash bash &> /dev/null
324
+ npm i && npm prune
325
+ ```
326
+
327
+ We also care about code coverage so we'll use [nyc][8] to generate a coverage
328
+ report.
329
+
258
330
  ```bash bash
259
- npm test
331
+ npx nyc npm t && npx nyc check-coverage
260
332
  ```
261
333
 
262
334
  ## Formatting
@@ -267,12 +339,16 @@ Standard is good enough.
267
339
  npx standard || npx standard --fix
268
340
  ```
269
341
 
342
+ ## Stackoverflow
343
+
344
+ This project is mentioned in the following [stackoverflow question][7].
345
+
270
346
  ## Documentation
271
347
 
272
348
  The README.md is generated using [markatzea][6].
273
349
 
274
- ```bash bash
275
- test "$RECUR" -eq 1 || RECUR=1 markatzea README.mz | tee README.md
350
+ ```bash
351
+ markatzea README.mz > README.md
276
352
  ```
277
353
 
278
354
  ## Contribute
@@ -280,11 +356,11 @@ test "$RECUR" -eq 1 || RECUR=1 markatzea README.mz | tee README.md
280
356
  You may contribute in whatever manner you see fit. Do try to be helpful and
281
357
  polite. Some suggestions for contributions:
282
358
 
283
- - [ ] Matching on strings in a similar manner to arrays.
284
- - [ ] Allow regular expressions for matching strings.
359
+ - [ ] A fitting logo.
285
360
  - [ ] Find and report bugs and inconsistencies.
361
+ - [ ] Improve documentation.
362
+ - [ ] Prevent exceeding stack size by managing recursive patterns.
286
363
  - [ ] Suggest API improvements in naming and functionality.
287
- - [ ] A fitting logo.
288
364
 
289
365
  [1]:https://en.wikipedia.org/wiki/Tree_traversal
290
366
  [2]:https://github.com/bas080/patroon/blob/master/src/walkable.js
@@ -292,3 +368,6 @@ polite. Some suggestions for contributions:
292
368
  [4]:https://github.com/bas080/patroon/blob/master/src/helpers.js
293
369
  [5]:https://github.com/bas080/patroon/blob/master/src/index.test.js
294
370
  [6]:https://github.com/bas080/markatzea
371
+ [7]:https://stackoverflow.com/questions/50452844/functional-programming-style-pattern-matching-in-javascript/67376827#67376827
372
+ [8]:https://github.com/istanbuljs/nyc
373
+ [9]:https://www.npmjs.com/package/patroon
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patroon",
3
- "version": "0.0.5",
3
+ "version": "0.1.2",
4
4
  "description": "Pattern matching library",
5
5
  "repository": "github:bas080/patroon",
6
6
  "main": "./src/index.js",
@@ -8,8 +8,16 @@
8
8
  "test": "tape ./src/index.test.js"
9
9
  },
10
10
  "keywords": [
11
+ "array",
12
+ "condition",
13
+ "match",
14
+ "matching",
15
+ "object",
11
16
  "pattern",
12
- "matching"
17
+ "pattern-matching",
18
+ "predicate",
19
+ "types",
20
+ "string"
13
21
  ],
14
22
  "author": "Bassim Huis",
15
23
  "license": "GPLv3",
package/src/helpers.js CHANGED
@@ -12,7 +12,12 @@ function isConstructor (func) {
12
12
  return Boolean(func && typeof func === 'function' && func.prototype && func.prototype.constructor)
13
13
  }
14
14
 
15
+ function isDefined (x) {
16
+ return x != null
17
+ }
18
+
15
19
  module.exports = {
20
+ isDefined,
16
21
  always: x => () => x,
17
22
  isConstructor,
18
23
  toPairs,
@@ -26,7 +31,7 @@ module.exports = {
26
31
  }
27
32
 
28
33
  return instance =>
29
- instance.constructor === Ctor || instance instanceof Ctor
34
+ isDefined(instance) && (instance.constructor === Ctor || instance instanceof Ctor)
30
35
  },
31
36
  tryCatch (tryFn, catchFn) {
32
37
  return (...args) => {
@@ -45,8 +50,5 @@ module.exports = {
45
50
  },
46
51
  isFunction (x) {
47
52
  return typeof x === 'function'
48
- },
49
- isDefined (x) {
50
- return x != null
51
53
  }
52
54
  }
package/src/index.js CHANGED
@@ -1,16 +1,24 @@
1
1
  const { mapLeaves, path, PathError } = require('./walkable')()
2
2
  const { isConstructor, isFunction, equals, T, is, tryCatch, isEven, isNil, toPairs, always } = require('./helpers')
3
3
 
4
+ const isRegExp = is(RegExp)
5
+
4
6
  const match = pattern => {
5
7
  // TODO: also check if something is a constructor
6
8
 
9
+ const normalize = (value, pth) => {
10
+ if (isRegExp(value)) return arg => value.test(arg)
11
+
12
+ if (isConstructor(value)) { return typed(value) }
13
+
14
+ if (isFunction(value)) { return arg => value(path(pth, arg)) }
15
+
16
+ return arg => equals(path(pth, arg), value)
17
+ }
18
+
7
19
  const patternPredicates = mapLeaves(
8
20
  (value, pth) => tryCatch(
9
- isConstructor(value)
10
- ? typed(value)
11
- : (isFunction(value)
12
- ? arg => value(path(pth, arg))
13
- : arg => equals(path(pth, arg), value)),
21
+ normalize(value, pth),
14
22
  e => {
15
23
  if (e instanceof PathError) { return false }
16
24
 
package/src/index.test.js CHANGED
@@ -1,12 +1,44 @@
1
1
  const { test } = require('tape')
2
2
  const { check, gen } = require('tape-check')
3
3
  const {
4
+ ref,
4
5
  patroon,
5
6
  typed,
6
7
  NoMatchError,
7
8
  _
8
9
  } = require('./index')
9
10
 
11
+ test('Matches on a simple primitive', t => {
12
+ t.equal(patroon(
13
+ 1, 2,
14
+ 2, 3
15
+ )(2), 3)
16
+ t.end()
17
+ })
18
+
19
+ test('Matches using a RegExp', t => {
20
+ patroon(
21
+ /^bunion/, () => t.fail(),
22
+ /^banana/, () => t.end()
23
+ )('banana tree')
24
+ })
25
+
26
+ test('Match using reference', t => {
27
+ patroon(
28
+ 1, () => t.fail(),
29
+ Number, () => t.fail(),
30
+ ref(Number), t.end()
31
+ )(Number)
32
+ })
33
+
34
+ test('Matches on type and value', t => {
35
+ patroon(
36
+ typed(Error, { x: 20 }), () => t.fail(),
37
+ typed(Error, { x: 10 }), () => t.end(),
38
+ typed(Error), () => t.fail()
39
+ )(Object.assign(new Error(), { x: 10 }))
40
+ })
41
+
10
42
  test('Matches always when pattern equals value', check(gen.any, (t, val) => {
11
43
  patroon(val, () => t.end())(val)
12
44
  }))
package/src/walkable.js CHANGED
@@ -1,4 +1,4 @@
1
- const { isFunction, isDefined } = require('./helpers')
1
+ const { isFunction, isNil } = require('./helpers')
2
2
 
3
3
  function walk (config, transform, item, path = []) {
4
4
  if (config.isLeafe(item)) { return transform(item, path) }
@@ -25,7 +25,7 @@ function isObject (v) {
25
25
  }
26
26
 
27
27
  function isLeafe (x) {
28
- return Number.isNaN(x) || !isDefined(x) || isFunction(x) || (!isObject(x) && !isArray(x))
28
+ return Number.isNaN(x) || isNil(x) || x instanceof RegExp || isFunction(x) || (!isObject(x) && !isArray(x))
29
29
  }
30
30
 
31
31
  function mapLeaves (config, cb, item) {