Skip to content

Commit b30fb9c

Browse files
committed
refactor index
1 parent d4b216b commit b30fb9c

File tree

4 files changed

+267
-351
lines changed

4 files changed

+267
-351
lines changed

dist/index.cjs

Lines changed: 65 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -88,93 +88,82 @@ function getFromIndex (arr, find) {
8888
return fromIndex
8989
}
9090

91-
class CommandLineArgs {
92-
constructor (args, optionDefinitions) {
93-
this.origArgv = args.slice();
94-
this.args = args.slice();
95-
this.optionDefinitions = optionDefinitions;
91+
/**
92+
* Return a single item from an array, optionally removing it from the input.
93+
*/
94+
function single (arr, item, options = {}) {
95+
const fromIndex = getFromIndex(arr, item);
96+
97+
const output = arr.slice(fromIndex, fromIndex + 1);
98+
if (options.remove && fromIndex > -1) {
99+
arr.splice(fromIndex, 1);
96100
}
101+
return output
102+
}
97103

98-
parse () {
99-
const extractions = this.getExtractions();
100-
const matches = this.getMatches(extractions);
101-
return this.buildOutput(matches)
104+
/**
105+
* Return a single item by index, optionally removing it from the input.
106+
*/
107+
function positional (arr, fromIndex, options = {}) {
108+
const output = arr.slice(fromIndex, fromIndex + 1);
109+
if (options.remove) {
110+
arr.splice(fromIndex, 1);
102111
}
112+
return output
113+
}
103114

104-
/**
105-
* Loop through the defs using `def.from` and `def.to` to compute `args` start and end indices for extraction.
106-
* Output:
107-
* [<from-arg>, <...arg>, <to-arg>]
108-
*/
109-
getExtractions () {
110-
const result = [];
111-
for (const def of this.optionDefinitions) {
112-
let scanning = true;
113-
while (scanning) {
114-
const fromIndex = this.args.findIndex(def.from);
115-
if (fromIndex === -1) {
116-
scanning = false;
117-
} else {
118-
let dynamicDef;
119-
if (def.def) {
120-
dynamicDef = def.def(this.args[fromIndex]);
121-
Object.assign(dynamicDef, def);
122-
} else {
123-
dynamicDef = def;
124-
}
125-
result.push(fromTo(this.args, {
126-
from: dynamicDef.from,
127-
to: dynamicDef.to,
128-
noFurtherThan: dynamicDef.noFurtherThan,
129-
remove: true
130-
}));
115+
const toPresets = {
116+
singleOptionValue (arg, index, argv, valueIndex) {
117+
return valueIndex > 1 || arg.startsWith('--')
118+
},
119+
120+
multipleOptionValue (arg, index, argv, valueIndex) {
121+
return arg.startsWith('--')
122+
}
123+
};
124+
125+
class CommandLineArgs {
126+
constructor (argv = process.argv) {
127+
this.origArgv = argv.slice();
128+
this.argv = argv.slice();
129+
}
130+
131+
parse (optionDefinitions) {
132+
const result = {};
133+
134+
/* Do the positionals backwards, so removing them doesn't mess up the position config */
135+
const positionals = optionDefinitions.filter(d => d.extractor === 'positional');
136+
positionals.sort((a, b) => b.position - a.position);
137+
138+
if (positionals.length) {
139+
for (const def of positionals) {
140+
const extraction = positional(this.argv, def.position - 1, { remove: true });
141+
if (extraction.length) {
142+
result[def.name] = def.output(extraction);
131143
}
132144
}
133145
}
134-
return result
135-
}
136146

137-
/**
138-
* Operates on the extractions, input args not touched. Map an option to one or more values. Currently does some processing if `name` and/or `type` are provided. What if they are not, what should the defaults be?
139-
* Uses `def.from` to match with the first arg of the extraction (as the same def.from was originally used to create the extraction)
140-
* Uses def.def, def.name, def.type. Not def.to.
141-
* Output:
142-
[
143-
[<name>, [...typeResults]]
144-
[<name>, [...typeResults]]
145-
]
146-
*/
147-
getMatches (extractions) {
148-
const result = [];
149-
for (const extraction of extractions) {
150-
/* the from arg is the one matched by def.from() */
151-
const fromArg = extraction[0];
152-
const def = this.optionDefinitions.find(def => def.from(fromArg));
153-
let dynamicDef = def;
154-
if (def.def) {
155-
dynamicDef = def.def(fromArg);
156-
Object.assign(dynamicDef, def);
147+
const notPositionals = optionDefinitions.filter(d => d.extractor !== 'positional');
148+
for (const def of notPositionals) {
149+
let extraction;
150+
if (def.extractor === 'fromTo') {
151+
extraction = fromTo(this.argv, {
152+
from: def.from,
153+
to: toPresets[def.to],
154+
remove: true
155+
});
156+
} else if (def.extractor === 'single') {
157+
extraction = single(this.argv, def.single, { remove: true });
158+
} else {
159+
throw new Error('Extractor not found: ' + def.extractor)
160+
}
161+
if (extraction.length) {
162+
result[def.name] = def.output(extraction);
157163
}
158-
const name = dynamicDef.name === undefined
159-
? fromArg
160-
: typeof dynamicDef.name === 'string'
161-
? dynamicDef.name
162-
: dynamicDef.name(extraction);
163-
const typeResult = dynamicDef.type ? dynamicDef.type(extraction.slice(1)) : extraction.slice(1);
164-
result.push([name, typeResult]);
165164
}
166-
return result
167-
}
168165

169-
/**
170-
* No hints or config in the def. User decides, hand-rolled preference.
171-
*/
172-
buildOutput (matches) {
173-
const output = {};
174-
for (const [name, values] of matches) {
175-
output[name] = values;
176-
}
177-
return output
166+
return result
178167
}
179168
}
180169

index.js

Lines changed: 42 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,57 @@
1-
import { fromTo } from './lib/from-to.js'
1+
import { fromTo, single, positional } from './lib/from-to.js'
22

3-
class CommandLineArgs {
4-
constructor (args, optionDefinitions) {
5-
this.origArgv = args.slice()
6-
this.args = args.slice()
7-
this.optionDefinitions = optionDefinitions
3+
const toPresets = {
4+
singleOptionValue (arg, index, argv, valueIndex) {
5+
return valueIndex > 1 || arg.startsWith('--')
6+
},
7+
8+
multipleOptionValue (arg, index, argv, valueIndex) {
9+
return arg.startsWith('--')
810
}
11+
}
912

10-
parse () {
11-
const extractions = this.getExtractions()
12-
const matches = this.getMatches(extractions)
13-
return this.buildOutput(matches)
13+
class CommandLineArgs {
14+
constructor (argv = process.argv) {
15+
this.origArgv = argv.slice()
16+
this.argv = argv.slice()
1417
}
1518

16-
/**
17-
* Loop through the defs using `def.from` and `def.to` to compute `args` start and end indices for extraction.
18-
* Output:
19-
* [<from-arg>, <...arg>, <to-arg>]
20-
*/
21-
getExtractions () {
22-
const result = []
23-
for (const def of this.optionDefinitions) {
24-
let scanning = true
25-
while (scanning) {
26-
const fromIndex = this.args.findIndex(def.from)
27-
if (fromIndex === -1) {
28-
scanning = false
29-
} else {
30-
let dynamicDef
31-
if (def.def) {
32-
dynamicDef = def.def(this.args[fromIndex])
33-
Object.assign(dynamicDef, def)
34-
} else {
35-
dynamicDef = def
36-
}
37-
result.push(fromTo(this.args, {
38-
from: dynamicDef.from,
39-
to: dynamicDef.to,
40-
noFurtherThan: dynamicDef.noFurtherThan,
41-
remove: true
42-
}))
19+
parse (optionDefinitions) {
20+
const result = {}
21+
22+
/* Do the positionals backwards, so removing them doesn't mess up the position config */
23+
const positionals = optionDefinitions.filter(d => d.extractor === 'positional')
24+
positionals.sort((a, b) => b.position - a.position)
25+
26+
if (positionals.length) {
27+
for (const def of positionals) {
28+
const extraction = positional(this.argv, def.position - 1, { remove: true })
29+
if (extraction.length) {
30+
result[def.name] = def.output(extraction)
4331
}
4432
}
4533
}
46-
return result
47-
}
4834

49-
/**
50-
* Operates on the extractions, input args not touched. Map an option to one or more values. Currently does some processing if `name` and/or `type` are provided. What if they are not, what should the defaults be?
51-
* Uses `def.from` to match with the first arg of the extraction (as the same def.from was originally used to create the extraction)
52-
* Uses def.def, def.name, def.type. Not def.to.
53-
* Output:
54-
[
55-
[<name>, [...typeResults]]
56-
[<name>, [...typeResults]]
57-
]
58-
*/
59-
getMatches (extractions) {
60-
const result = []
61-
for (const extraction of extractions) {
62-
/* the from arg is the one matched by def.from() */
63-
const fromArg = extraction[0]
64-
const def = this.optionDefinitions.find(def => def.from(fromArg))
65-
let dynamicDef = def
66-
if (def.def) {
67-
dynamicDef = def.def(fromArg)
68-
Object.assign(dynamicDef, def)
35+
const notPositionals = optionDefinitions.filter(d => d.extractor !== 'positional')
36+
for (const def of notPositionals) {
37+
let extraction
38+
if (def.extractor === 'fromTo') {
39+
extraction = fromTo(this.argv, {
40+
from: def.from,
41+
to: toPresets[def.to],
42+
remove: true
43+
})
44+
} else if (def.extractor === 'single') {
45+
extraction = single(this.argv, def.single, { remove: true })
46+
} else {
47+
throw new Error('Extractor not found: ' + def.extractor)
48+
}
49+
if (extraction.length) {
50+
result[def.name] = def.output(extraction)
6951
}
70-
const name = dynamicDef.name === undefined
71-
? fromArg
72-
: typeof dynamicDef.name === 'string'
73-
? dynamicDef.name
74-
: dynamicDef.name(extraction)
75-
const typeResult = dynamicDef.type ? dynamicDef.type(extraction.slice(1)) : extraction.slice(1)
76-
result.push([name, typeResult])
7752
}
78-
return result
79-
}
8053

81-
/**
82-
* No hints or config in the def. User decides, hand-rolled preference.
83-
*/
84-
buildOutput (matches) {
85-
const output = {}
86-
for (const [name, values] of matches) {
87-
output[name] = values
88-
}
89-
return output
54+
return result
9055
}
9156
}
9257

test/from-to.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { strict as a } from 'assert'
2-
import { fromTo, single } from 'command-line-args/fromTo'
2+
import { fromTo, single, positional } from '../lib/from-to.js'
33

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

0 commit comments

Comments
 (0)