|
1 | 1 | 'use strict'; |
2 | 2 |
|
| 3 | +var arrayBack = require('array-back'); |
| 4 | + |
3 | 5 | /** |
4 | 6 | * Similar to find-replace with two exceptions: |
5 | 7 | * - fromTo finds multiple items, find-replace finds single items |
|
9 | 11 | * - Find one or more items and return (all return values are arrays) |
10 | 12 | * - Find one or more items, return them, remove them from the input array |
11 | 13 | * |
| 14 | + * arr {string[]} - Input array. Only mutated if `options.remove` is set. |
12 | 15 | * [options.rtol] {boolean} - Enable right-to-left scans. Either that or pass in a custom iterator. |
13 | 16 | * [options.remove] {boolean} - Remove from source array |
14 | | - * [options.from] {boolean} |
15 | | - * [options.to] {boolean} |
| 17 | + * [options.inclusive] {boolean} - If `true` includes the to item. |
| 18 | + * [options.from] {function} |
| 19 | + * [options.to] {function} |
| 20 | + * [options.noFurtherThan] {function} |
16 | 21 | * @returns string[] |
17 | 22 | */ |
18 | 23 | function fromTo (arr, options = {}) { |
19 | | - const { from: fromFn, to: toFn, noFurtherThan, remove } = options; |
20 | | - const fromIndex = arr.findIndex(fromFn); |
| 24 | + let { from: fromFn, to: toFn, noFurtherThan, remove, inclusive, toEnd } = options; |
| 25 | + if (inclusive === undefined && !noFurtherThan && toFn) { |
| 26 | + inclusive = true; |
| 27 | + } |
| 28 | + toFn = toFn || noFurtherThan; |
| 29 | + fromFn = arrayBack(fromFn).map(fn => { |
| 30 | + if (typeof fn === 'string') { |
| 31 | + return function (val) { return val === fn } |
| 32 | + } else { |
| 33 | + return fn |
| 34 | + } |
| 35 | + }); |
| 36 | + toFn = arrayBack(toFn).map(fn => { |
| 37 | + if (typeof fn === 'string') { |
| 38 | + return function (item, index, arr, valueIndex) { return item === fn } |
| 39 | + } else { |
| 40 | + return fn |
| 41 | + } |
| 42 | + }); |
| 43 | + |
| 44 | + let fromIndex; |
| 45 | + for (const fn of fromFn) { |
| 46 | + fromIndex = arr.findIndex(fn); |
| 47 | + if (fromIndex > -1) { |
| 48 | + break |
| 49 | + } |
| 50 | + } |
| 51 | + |
21 | 52 | let toIndex; |
22 | 53 | if (toFn) { |
23 | | - toIndex = arr.findIndex((item, index, arr) => { |
24 | | - if (index > fromIndex) { |
25 | | - const valueIndex = index - fromIndex; |
26 | | - return toFn(valueIndex, item, index, arr) |
27 | | - } else { |
28 | | - return false |
29 | | - } |
30 | | - }); |
31 | | - } else if (noFurtherThan) { |
32 | | - toIndex = arr.findIndex((item, index, arr) => { |
33 | | - if (index > fromIndex) { |
34 | | - const valueIndex = index - fromIndex; |
35 | | - return noFurtherThan(valueIndex, item, index, arr) |
36 | | - } else { |
37 | | - return false |
| 54 | + for (const fn of toFn) { |
| 55 | + toIndex = arr.findIndex((item, index, arr) => { |
| 56 | + if (index > fromIndex) { |
| 57 | + const valueIndex = index - fromIndex; |
| 58 | + return fn(item, index, arr, valueIndex) |
| 59 | + } else { |
| 60 | + return false |
| 61 | + } |
| 62 | + }); |
| 63 | + if (toIndex > -1) { |
| 64 | + break |
38 | 65 | } |
39 | | - }); |
40 | | - if (toIndex > 0) { |
41 | | - toIndex -= 1; |
42 | | - } else if (toIndex === -1) { |
43 | | - toIndex = arr.length - 1; |
44 | 66 | } |
45 | | - } else { |
46 | | - toIndex = fromIndex; |
47 | 67 | } |
| 68 | + |
48 | 69 | if (remove) { |
49 | | - return arr.splice(fromIndex, toIndex === -1 ? 1 : toIndex - fromIndex + 1) |
| 70 | + let deleteCount; |
| 71 | + if (toEnd) { |
| 72 | + deleteCount = arr.length; |
| 73 | + } |
| 74 | + if (toIndex === -1) { |
| 75 | + /* TODO: If to is not found, should it behave the same as "no to" (just return the from value)? Scanning to the end supports `--option value value` */ |
| 76 | + deleteCount = arr.length; |
| 77 | + } else if (toIndex === undefined) { |
| 78 | + /* When to is omitted, just pick the single value at the from index */ |
| 79 | + /* This differs to arr.slice which slices to the end of the array if end is omitted */ |
| 80 | + deleteCount = 1; |
| 81 | + } else { |
| 82 | + if (inclusive) { |
| 83 | + deleteCount = toIndex - fromIndex; |
| 84 | + } else { |
| 85 | + deleteCount = toIndex - fromIndex - 1; |
| 86 | + } |
| 87 | + } |
| 88 | + return arr.splice(fromIndex, deleteCount) |
| 89 | + /* deleteCount: An integer indicating the number of elements in the array to remove from start. */ |
| 90 | + /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice */ |
50 | 91 | } else { |
51 | | - // return arr.slice(fromIndex, toIndex + 1) |
| 92 | + if (toEnd) { |
| 93 | + toIndex = arr.length + 1; |
| 94 | + } |
| 95 | + if (toIndex === -1) { |
| 96 | + return arr.slice(fromIndex) |
| 97 | + } else if (toIndex === undefined) { |
| 98 | + /* When to is omitted, just pick the single value at the from index */ |
| 99 | + /* This differs to arr.slice which slices to the end of the array if end is omitted */ |
| 100 | + return arr.slice(fromIndex, fromIndex + 1) |
| 101 | + } else { |
| 102 | + if (inclusive) { |
| 103 | + return arr.slice(fromIndex, toIndex + 1) |
| 104 | + } else { |
| 105 | + return arr.slice(fromIndex, toIndex) |
| 106 | + } |
| 107 | + } |
52 | 108 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice |
53 | | - return arr.slice(fromIndex, toIndex === -1 ? 1 : toIndex - fromIndex + 1) |
| 109 | + /* End: Zero-based index at which to end extraction. slice() extracts up to but not including end. */ |
54 | 110 | } |
55 | 111 | } |
56 | 112 |
|
57 | | -/* |
58 | | -TODO: add `noFurtherThan` function as an additional alternative, or replacement, for `to`.. Might result in easier code, e.g. "no further than a --option", rather than "stop here if the next item is an option or the end". This is also how slice() works: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice |
59 | | -
|
60 | | -*/ |
61 | | - |
62 | 113 | class CommandLineArgs { |
63 | 114 | constructor (args, optionDefinitions) { |
64 | 115 | this.origArgv = args.slice(); |
|
0 commit comments