Skip to content

Commit f86728e

Browse files
committed
v3.2.3-beta :: fix some edge cases
1 parent 08f5b77 commit f86728e

File tree

6 files changed

+118
-36
lines changed

6 files changed

+118
-36
lines changed

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
LICENSE
44
examples
55
test.js
6+
poke.js
67
README.md

README.md

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function main() {
5858
const gitRes = {};
5959

6060
// First parse to handle global options and identify subcommand
61-
const ret = parse(process.argv, 2, gitReq, gitRes, errorHandler);
61+
const ret = parse(process.argv, 2, gitReq, gitRes, console.log);
6262

6363
// Handle help flag
6464
if (gitRes.help) {
@@ -79,7 +79,7 @@ function main() {
7979
case 'commit':
8080
// Parse commit-specific options starting from ret.i
8181
const commitRes = {};
82-
parse(process.argv, ret.i, commitReq, commitRes, errorHandler);
82+
parse(process.argv, ret.i, commitReq, commitRes, console.log);
8383

8484
// Use the results
8585
console.log(
@@ -97,12 +97,6 @@ function main() {
9797
}
9898
}
9999
}
100-
101-
// Error handler
102-
function errorHandler(err) {
103-
console.error(`Error at arg ${err.i}: ${err.msg}`);
104-
return false; // Continue parsing
105-
}
106100
```
107101

108102
## 🛠️ API
@@ -132,7 +126,7 @@ The function returns either:
132126
{
133127
// Regular variable with default value and option definitions
134128
variableName: {
135-
def: defaultValue, // Required: Boolean, string, or string[]
129+
def: defaultValue, // Boolean, string, or string[]
136130
set: optionDefinition, // Options that set this variable
137131
rst: optionDefinition // Options that reset this variable to default
138132
},
@@ -145,9 +139,7 @@ The function returns either:
145139
Option definitions can be:
146140
- A string: `'--option'`
147141
- An array: `['--option', '-o']`
148-
- `null` (refers to the variable name)
149-
150-
Note: `undefined` is not supported. While it may work like `null` in `OptKit`, this is unintended. At `ExitKit`, `undefined` behaves differently from `null`.
142+
- `null` refers to the variable name. `[null, ...]` is also supported.
151143

152144
## ⚡ Powerful Features
153145

@@ -222,7 +214,7 @@ parse(['node', 'app.js', '-abc'], 2, {
222214
a: { def: false, set: '-a' },
223215
b: { def: false, set: '-b' },
224216
c: { def: false, set: '-c' }
225-
}, res, errorHandler);
217+
}, res, console.log);
226218
// res = { a: true, b: true, c: true }
227219
```
228220
```javascript
@@ -232,7 +224,7 @@ parse(['node', 'app.js', '-abcd'], 2, {
232224
b: { def: [], set: '-b' },
233225
c: { def: false, set: '-c' },
234226
d: { def: false, set: '-d' }
235-
}, res, errorHandler);
227+
}, res, console.log);
236228
// { a: true, b: [ 'cd' ], c: false, d: false }
237229
```
238230

@@ -252,7 +244,7 @@ const mainReq = {
252244
};
253245

254246
const mainRes = {};
255-
const ret = parse(process.argv, 2, mainReq, mainRes, errorHandler);
247+
const ret = parse(process.argv, 2, mainReq, mainRes, console.log);
256248

257249
if (typeof ret === 'object') {
258250
// When a command is found via the exit mechanism:
@@ -264,7 +256,7 @@ if (typeof ret === 'object') {
264256
case 'build':
265257
const buildReq = { /* build options */ };
266258
const buildRes = {};
267-
parse(process.argv, ret.i, buildReq, buildRes, errorHandler);
259+
parse(process.argv, ret.i, buildReq, buildRes, console.log);
268260
break;
269261
}
270262
}

index.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
'use strict';
22
const isA = Array.isArray;
33
/**
4-
* @typedef {string} VarKey Variable name
4+
* @typedef {string} KeyStr Variable name
55
* @typedef {string | boolean | string[]} VarVal Variable value
66
* @typedef {string} OptStr Option string, e.g. '--', '-o', '--option'
77
* @typedef {OptStr | null} OptDef Option definitions, e.g. '--', '-o', '--option', or null to refer to the variable name
88
* @typedef {OptDef | OptDef[]} OptKit one or more option definitions
99
*
1010
* @typedef {object} VarKit Variable configuration object
11-
* @property {VarVal} def Variable **def**inition & **def**ault value (pun intended)
11+
* @property {VarVal} [def] Variable **def**inition & **def**ault value (pun intended)
1212
* @property {OptKit} [set] Array of options to set the variable value
1313
* @property {OptKit} [rst] Array of options to reset the variable value
1414
*
1515
* @typedef {OptDef | OptDef[]} ExitKit Exit options, identical to `OptKit` for now
16-
* @typedef {Record<VarKey, VarKit | ExitKit>} KeyKitMap
17-
* @typedef {Record<VarKey, VarVal>} KeyValMap
16+
* @typedef {Record<KeyStr, VarKit | ExitKit>} KeyKitMap
17+
* @typedef {Record<KeyStr, VarVal>} KeyValMap
1818
*
1919
* @callback IsFatal
20-
* @param {{msg: string, i: number, opt: OptStr, key?: VarKey, val?: VarVal }} err
20+
* @param {{msg: string, i: number, opt: OptStr, key?: KeyStr, val?: VarVal }} err
2121
* @returns {boolean} Whether the parsing should continue (false) or quit (true)
22-
* @typedef {Record<OptStr, VarKey>} OptKeyMap internal type
22+
* @typedef {Record<OptStr, KeyStr>} OptKeyMap internal type
2323
*/
2424
/** Get OD! @param {OptKit} ok */
2525
const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok];
@@ -30,13 +30,13 @@ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok];
3030
* @param {KeyKitMap} req Options structure definition
3131
* @param {KeyValMap} res Object to store parsed results
3232
* @param {IsFatal} err Error handler function, return true to quit parsing
33-
* @returns {number | { i: number, key: VarKey, opt: OptStr }}
33+
* @returns {number | { i: number, key: KeyStr, opt: OptStr }}
3434
* @example
3535
*/
3636
export default function parse(argv, i, req, res, err) {
3737
/** @type {OptStr} option */
3838
let opt = '';
39-
/** @type {VarKey | undefined} key */
39+
/** @type {KeyStr | undefined} key */
4040
let key;
4141
/** @param {VarVal} val */
4242
const set = val => {
@@ -51,30 +51,32 @@ export default function parse(argv, i, req, res, err) {
5151
res[key] = !def;
5252
return false;
5353
} return true;
54-
}, k = o => o == null ? key : o, // split undefined? hmm ugly
54+
}, k = o => o == null ? key : o,
5555
ask = (msg, val) => err({msg, i, opt, key, val}),
5656
exit = c => ({ i: i + c, key, opt });
5757
// prepare
5858
/** @type {OptKeyMap} */
5959
const set_ = {}, rst_ = {}, exit_ = {};
60-
/** @type {VarKey | undefined} */
60+
/** @type {KeyStr | undefined} */
6161
let _key, _exit = false;
6262
for (key in req) {
6363
const vk = req[key];
64-
X: { let xk; // stricter than god()
64+
X: { let xk;
6565
switch (typeof vk) {
6666
case 'object':
6767
if (vk === null) xk = [vk];
6868
else if (isA(vk)) xk = vk;
6969
else break X; break;
70-
case 'string': xk = [vk]; break;
71-
default: continue; }
72-
for (const o of xk) if (o!=='--') exit_[k(o)] = key; else _key = key, _exit = true;
70+
case 'undefined': continue;
71+
default: xk = [vk]; }
72+
for (let o of xk) if ((o=k(o))!=='--') exit_[o] = key;
73+
else _key = key, _exit = true;
7374
continue; }
74-
const def = vk.def;
75+
const def = vk.def; // if (def === undefined) continue;
7576
res[key] = isA(def) ? def.slice() : def;
76-
for (const o of god(vk.set)) if (o!=='--') set_[k(o)] = key; else _key = key, _exit = false;
77-
for (const o of god(vk.rst)) if (o!=='--') rst_[k(o)] = key; // do not reset around
77+
for (let o of god(vk.set)) if ((o=k(o))!=='--') set_[o] = key;
78+
else if (typeof def !== 'boolean') _key = key, _exit = false;
79+
for (let o of god(vk.rst)) if ((o=k(o))!=='--') rst_[o] = key;
7880
}
7981
// process
8082
let ext = false;
@@ -133,7 +135,8 @@ export default function parse(argv, i, req, res, err) {
133135
default: set(v); continue; }
134136
else if (key = rst_[opt]) t = 'reset';
135137
else if (key = exit_[opt]) t = 'exit';
136-
else if (ask('invalid option', v)) return i; else continue;
138+
else if (ask('invalid option', v)) return i;
139+
else continue;
137140
if (ask(`cannot assign value to ${t} option`, v)) return i;
138141
continue;
139142
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pargv-lite",
3-
"version": "3.2.2-beta",
3+
"version": "3.2.3-beta",
44
"description": "A lightning-fast, single-pass command line argument parser with structure validation",
55
"main": "index.js",
66
"type": "module",

poke.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use strict';
2+
import parse from './index.js';
3+
4+
const $ = console.log, e = a => $('💥', a), hr = '-'.repeat(42); $(hr);
5+
6+
const poke = (title, argv, req) => {
7+
$('🤓', title);
8+
$('⌨️ ', argv.length, argv);
9+
$('🔍', req);
10+
const res = {}, ret = parse(argv, 0, req, res, e);
11+
$('📖', res);
12+
$('😇', ret);
13+
$(hr);
14+
};
15+
16+
poke('give all to a bool', ['awawaw'], {
17+
bool: { def: false, set: ['--'] },
18+
}); // else if (typeof def !== 'boolean') _key = key, _exit = false;
19+
20+
poke('My key is two dashes but I dont know how to set', ['awawaw', 'sdfsdf'], {
21+
'--': { def: [], set: null },
22+
});
23+
24+
poke('super long dash', ['awawaw', '-----D', 'sdfsdf'], {
25+
'--': { def: [], set: null },
26+
'-----D': null
27+
});
28+
29+
poke('Set Bool Get Chain Bomb', ['-bawawaw', 'sdfsdf'], {
30+
bool: { def: false, set: ['--', '-b'] },
31+
});
32+
33+
poke('Assign Bool', ['--b=awawaw', 'sdfsdf'], {
34+
bool: { def: false, set: ['--', '--b'] },
35+
});
36+
37+
poke('Assign Bool + Pool', ['--b=awawaw', 'sdfsdf'], {
38+
bool: { def: false, set: ['--', '--b'] },
39+
pool: { def: [], set: ['--', '--b'] },
40+
}); // Overwritten all, which is expected behavior.
41+
42+
poke('Assign Pool + Bool', ['--b=awawaw', 'sdfsdf'], {
43+
pool: { def: [], set: ['--', '--b'] },
44+
bool: { def: false, set: ['--', '--b'] },
45+
}); // bool only overwrites '--b', which is expected
46+
47+
poke('My New Home is in Random Place After a b', ['--b=awawaw', 'sdfsdf'], {
48+
'--': null
49+
});
50+
51+
poke('My New Home is in Random Place', ['awawaw', 'sdfsdf'], {
52+
'--': null
53+
});
54+
55+
poke('My New Home is after two Dashes', ['--', 'awawaw', 'sdfsdf'], {
56+
'--': null
57+
});
58+
59+
poke('My New Home is An Empty Bad Guy', ['awawaw', 'sdfsdf'], {
60+
good: { def: [], set: '--' },
61+
home: { set: '--' }
62+
}); // if (def === undefined) continue;
63+
64+
poke('My set is undefined', ['awawaw', 'sdfsdf'], {
65+
'--': { def: [], set: undefined }
66+
});
67+
68+
poke('My set is null', ['awawaw', 'sdfsdf'], {
69+
'--': { def: [], set: null }
70+
});
71+
72+
poke('My exit is [null]', ['awawaw', 'sdfsdf'], {
73+
'--': [null]
74+
});
75+
76+
poke('My exit is [undefined]', ['awawaw', 'sdfsdf'], {
77+
'--': [undefined]
78+
}); // this is I said undefined acts like null in OptKit
79+
80+
poke('My Dashes are undefined', ['awawaw', 'sdfsdf'], {
81+
'--': undefined
82+
}); // Can't use undefined in ExitKit
83+
84+
poke('My Dashes are null', ['awawaw', 'sdfsdf'], {
85+
'--': null
86+
}); // But null is ok

test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ runTest('Exit on anonymous arguments', () => {
314314
if (typeof ret !== 'object') throw ret;
315315
assert.strictEqual(ret.key, 'files');
316316
assert.strictEqual(ret.opt, '1.txt');
317-
assert.strictEqual(ret.i, 4);
317+
assert.strictEqual(ret.i, 3);
318318
});
319319

320320
console.log('🎉 All tests completed!');

0 commit comments

Comments
 (0)