Skip to content

Commit a1bfbe6

Browse files
committed
refactor fromTo
1 parent 4b1732f commit a1bfbe6

File tree

2 files changed

+93
-129
lines changed

2 files changed

+93
-129
lines changed

lib/from-to.js

Lines changed: 54 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,51 +12,21 @@ import arrayBack from 'array-back'
1212
* arr {string[]} - Input array. Only mutated if `options.remove` is set.
1313
* [options.rtol] {boolean} - Enable right-to-left scans. Either that or pass in a custom iterator. TODO.
1414
* [options.remove] {boolean} - Remove from source array
15-
* [options.inclusive] {boolean} - If `true` includes the to item.
16-
* [options.from] {string[]|function[]}
17-
* [options.to] {string[]|function[]} - A "Stop Here" function. Set one or more strings as the terminating arg. Or, from the function `fn(arg, index, argv, valueIndex)`, return true for the first arg that is out of range. Set `inclusive` to also include it.
18-
* [options.toInclude] {string[]|function[]} - From the function `fn(arg, index, argv, valueIndex)`, return true for the first arg that is out of range. Set `inclusive` to also include it.
19-
* [options.noFurtherThan] {function}
20-
* [options.toEnd] {boolean}
15+
* [options.from] {string[]|function[]} - String literal or a [findIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) callback function.
16+
* [options.to] {string[]|function[]} - A "Stop Here" function. Set one or more strings as the terminating arg. Or, from the function `fn(arg, index, argv, valueIndex)`, return true for the first arg that is out of range. Set `inclusive` to also include it. To will always search to the end of the input array.
2117
* @returns string[]
2218
*/
23-
function fromTo (arr, options = {}) {
24-
let { from: fromFn, to: toFn, noFurtherThan, remove, inclusive, toEnd } = options
2519

26-
if (inclusive === undefined && !noFurtherThan && toFn) {
27-
inclusive = true
28-
}
29-
toFn = toFn || noFurtherThan
30-
fromFn = arrayBack(fromFn).map(fn => {
31-
if (typeof fn === 'string') {
32-
return function (val) { return val === fn }
33-
} else {
34-
return fn
35-
}
36-
})
3720

38-
if (fromFn.length === 0) {
39-
throw new Error('from required')
40-
}
41-
42-
toFn = arrayBack(toFn).map(fn => {
43-
if (typeof fn === 'string') {
44-
return function (item) { return item === fn }
45-
} else {
46-
return fn
47-
}
48-
})
21+
/* TODO: rename to extractFromTo? Rename `options.remove` to `extract`. */
22+
function fromTo (arr, options = {}) {
23+
let { from, to: toFn, remove } = options
4924

50-
let fromIndex
51-
for (const fn of fromFn) {
52-
fromIndex = arr.findIndex(fn)
53-
if (fromIndex > -1) {
54-
break
55-
}
56-
}
25+
const fromIndex = getFromIndex(arr, from)
5726

58-
let toIndex
59-
if (toFn) {
27+
toFn = arrayBack(toFn).map(convertToFunction)
28+
let toIndex = -1
29+
if (toFn.length) {
6030
for (const fn of toFn) {
6131
toIndex = arr.findIndex((item, index, arr) => {
6232
if (index > fromIndex) {
@@ -66,54 +36,62 @@ function fromTo (arr, options = {}) {
6636
return false
6737
}
6838
})
39+
/* Keep looping until a match is found. */
6940
if (toIndex > -1) {
7041
break
7142
}
7243
}
7344
}
7445

75-
if (remove) {
76-
let deleteCount
77-
if (toEnd) {
78-
deleteCount = arr.length
79-
}
46+
const output = toIndex === -1
47+
? arr.slice(fromIndex) /* Return all to the end */
48+
: arr.slice(fromIndex, toIndex)
49+
50+
if (options.remove) {
8051
if (toIndex === -1) {
81-
/* 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` */
82-
deleteCount = arr.length
83-
} else if (toIndex === undefined) {
84-
/* When to is omitted, just pick the single value at the from index */
85-
/* This differs to arr.slice which slices to the end of the array if end is omitted */
86-
deleteCount = 1
52+
arr.splice(fromIndex)
8753
} else {
88-
if (inclusive) {
89-
deleteCount = toIndex - fromIndex
90-
} else {
91-
deleteCount = toIndex - fromIndex - 1
92-
}
54+
arr.splice(fromIndex, toIndex - fromIndex)
9355
}
94-
return arr.splice(fromIndex, deleteCount)
95-
/* deleteCount: An integer indicating the number of elements in the array to remove from start. */
96-
/* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice */
56+
}
57+
58+
return output
59+
}
60+
61+
function convertToFunction (fn) {
62+
if (typeof fn === 'string') {
63+
return function (val) { return val === fn }
9764
} else {
98-
if (toEnd) {
99-
toIndex = arr.length + 1
100-
}
101-
if (toIndex === -1) {
102-
return arr.slice(fromIndex)
103-
} else if (toIndex === undefined) {
104-
/* When to is omitted, just pick the single value at the from index */
105-
/* This differs to arr.slice which slices to the end of the array if end is omitted */
106-
return arr.slice(fromIndex, fromIndex + 1)
107-
} else {
108-
if (inclusive) {
109-
return arr.slice(fromIndex, toIndex + 1)
110-
} else {
111-
return arr.slice(fromIndex, toIndex)
112-
}
65+
return fn
66+
}
67+
}
68+
69+
function getFromIndex (arr, find) {
70+
const fromFns = arrayBack(find).map(convertToFunction)
71+
72+
if (fromFns.length === 0) {
73+
throw new Error('from required')
74+
}
75+
76+
let fromIndex
77+
for (const fn of fromFns) {
78+
fromIndex = arr.findIndex(fn)
79+
if (fromIndex > -1) {
80+
break
11381
}
114-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
115-
/* End: Zero-based index at which to end extraction. slice() extracts up to but not including end. */
11682
}
83+
84+
return fromIndex
85+
}
86+
87+
function single (arr, item, options = {}) {
88+
const fromIndex = getFromIndex(arr, item)
89+
90+
const output = arr.slice(fromIndex, fromIndex + 1)
91+
if (options.remove) {
92+
arr.splice(fromIndex, 1)
93+
}
94+
return output
11795
}
11896

119-
export default fromTo
97+
export { fromTo, single }

test/from-to.js

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,56 @@
11
import { strict as a } from 'assert'
2-
import fromTo from 'command-line-args/fromTo'
2+
import { fromTo, single } from 'command-line-args/fromTo'
33

44
const [test, only, skip] = [new Map(), new Map(), new Map()]
55

66
test.set('from and to: string inputs', async function () {
77
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
88
const result = fromTo(arr, {
99
from: 'here',
10-
to: ['rabbit', 'here', 'there'],
11-
inclusive: false
10+
to: ['rabbit', 'here', 'there'], // "to" implies one or more values expected. TODO: multiplicity config instread? E.g. `1..*` like UML.
1211
})
1312
a.deepEqual(result, ['here', '--', '--', '--'])
14-
})
15-
16-
test.set('from and to: string inputs, inclusive', async function () {
17-
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
18-
const result = fromTo(arr, {
19-
from: 'here',
20-
to: ['here', 'there'],
21-
inclusive: true
22-
})
23-
a.deepEqual(result, ['here', '--', '--', '--', 'here'])
13+
a.deepEqual(arr, ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
2414
})
2515

2616
test.set('from and to: function inputs', async function () {
2717
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
2818
const result = fromTo(arr, {
2919
from: (val) => val === 'here',
30-
to: (val) => val === 'here' || val === 'there',
31-
inclusive: false
20+
to: (val) => val === 'here' || val === 'there'
3221
})
3322
a.deepEqual(result, ['here', '--', '--', '--'])
23+
a.deepEqual(arr, ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
3424
})
3525

36-
test.set('from and to: function inputs, inclusive', async function () {
37-
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
38-
const result = fromTo(arr, {
39-
from: (val) => val === 'here',
40-
to: (val) => val === 'here' || val === 'there',
41-
inclusive: true
42-
})
43-
a.deepEqual(result, ['here', '--', '--', '--', 'here'])
44-
})
45-
46-
test.set('from, no to', async function () {
26+
test.set('no to, returns all items', async function () {
4727
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
4828
const result = fromTo(arr, {
4929
from: 'here'
5030
})
51-
a.deepEqual(result, ['here'])
52-
})
53-
54-
test.set('from, to end', async function () {
55-
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
56-
const result = fromTo(arr, {
57-
from: 'here',
58-
toEnd: true
59-
})
6031
a.deepEqual(result, ['here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
6132
})
6233

63-
skip.set('start point', async function () {
34+
skip.set('from second occurance', async function () {
6435
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
6536
const result = fromTo(arr, {
66-
// start: // second "here"
6737
from: 'here',
68-
toEnd: true
38+
fromOccurance: 2 // TODO: NOT IMPLEMENTED. DEPRECATED? Implement by passing in an array which already starts from the second occurance?
6939
})
7040
a.deepEqual(result, ['here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
7141
})
7242

73-
skip.set('from second occurance', async function () {
43+
/* Useful for parsing a --flag. Create new config called "once" and make "from" always imply `toEnd`. Remove toEnd. */
44+
test.set('single, no remove', async function () {
7445
const arr = ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there']
75-
const result = fromTo(arr, {
76-
from: 'here',
77-
fromOccurance: 2, // DEPRECATED
78-
toEnd: true
79-
})
80-
a.deepEqual(result, ['here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
46+
const result = single(arr, 'here')
47+
a.deepEqual(result, ['here'])
48+
a.deepEqual(arr, ['one', 'here', '--', '--', '--', 'here', '--', '--', '--', 'there'])
8149
})
8250

83-
test.set('flag', async function () {
51+
test.set('single, remove', async function () {
8452
const arr = ['one', 'here', '--flag', 'there']
85-
const result = fromTo(arr, {
86-
from: ['--flag', '-f'],
87-
remove: true
88-
})
53+
const result = single(arr, ['--flag', '-f'], { remove: true })
8954
a.deepEqual(result, ['--flag'])
9055
a.deepEqual(arr, ['one', 'here', 'there'])
9156
})
@@ -101,18 +66,18 @@ test.set('--option value', async function () {
10166
a.deepEqual(arr, ['one', 'here', 'more'])
10267
})
10368

104-
/* TODO: GET RID of noFurtherThan and inclusive - too confusing to use and code. Stick with "stop here", "no further than here" behaviour which is not inclusive. (default behaviour used by array.slice) */
105-
only.set('--option value without remove', async function () {
69+
test.set('--option value, no remove', async function () {
10670
const arr = ['one', 'here', '--option', 'there', 'more']
10771
const result = fromTo(arr, {
10872
from: '--option',
109-
noFurtherThan: (val, i, a, valueIndex) => valueIndex > 1 || val.startsWith('--'),
73+
to: (val, i, a, valueIndex) => valueIndex > 1 || val.startsWith('--'),
11074
remove: false
11175
})
11276
a.deepEqual(result, ['--option', 'there'])
11377
a.deepEqual(arr, ['one', 'here', '--option', 'there', 'more'])
11478
})
11579

80+
11681
test.set('--option value value ...', async function () {
11782
const arr = ['one', 'here', '--option', 'there', 'more']
11883
const result = fromTo(arr, {
@@ -124,5 +89,26 @@ test.set('--option value value ...', async function () {
12489
a.deepEqual(arr, ['one', 'here'])
12590
})
12691

92+
skip.set('from many, to many', async function () {
93+
const validCommands = [
94+
'/help',
95+
'/users',
96+
'/rooms',
97+
'/clientSessions',
98+
'/roomSessions',
99+
'/members',
100+
'/nick',
101+
'/join',
102+
]
103+
104+
const arr = [ '/join', 'r', '/nick', 'lloyd' ]
105+
const result = fromTo(arr, {
106+
from: validCommands,
107+
to: validCommands
108+
})
109+
/* Priority should be given to "first in the array", not "first in the from list". Is order in the argv more meaningful than order in the from list? */
110+
a.deepEqual(result, ['/join', 'r'])
111+
// a.deepEqual(arr, ['one', 'here'])
112+
})
127113

128114
export { test, only, skip }

0 commit comments

Comments
 (0)