From 9a8e52d89db1fda53a1a7bab220a76579b2b631a Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 02:44:20 +0800 Subject: [PATCH 01/15] v4 init --- index.js | 182 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 44 deletions(-) diff --git a/index.js b/index.js index 29f3238..3387430 100644 --- a/index.js +++ b/index.js @@ -1,28 +1,46 @@ 'use strict'; const isA = Array.isArray; /** - * @typedef {string} KeyStr Variable name, in most cases, or kit name - * @typedef {string} OptStr Option string, e.g. '--', '-o', '--option' - * @typedef {OptStr | null} OptDef Option definition, e.g. '--', '-o', '--option', or `null` to refer to the variable name - * @typedef {OptDef | OptDef[]} OptKit one or more option definitions, for a shortest one + * @typedef {string} Key Variable name, in most cases, or kit name + * @typedef {string} Option Option string, e.g. '--', '-o', '--option' + * @typedef {Option | null} OptDef Option definition, e.g. '--', '-o', '--option' or `null` to refer to the variable name `Key` + * @typedef {OptDef | OptDef[]} OptKit one or more option definitions, for a shortest expression * - * @typedef {object} VarKit Variable configuration object - * @property {VarVal} [def] Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [set] Array of options to set the variable value - * @property {OptKit} [rst] Array of options to reset the variable value - * @typedef {string | boolean | string[]} VarVal Variable value + * @typedef {string | number | bigint | symbol} Text can be shown as text * - * @typedef {OptDef | OptDef[]} ExitKit Exit options - * @typedef {Record} KeyKitMap `req` - * @typedef {Record} KeyValMap `res` + * @typedef {object} TextKit + * @property {Text | Text[]} [def] Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [str] Options to set `arg: string` + * @property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` + * @property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw + * @property {OptKit} [sym] Options to set `Symbol(arg)`. + * @property {OptKit} [rst] Options to set `def` + * + * @typedef {object} BoolKit + * @property {boolean?} def Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [yes] Options to set `true` + * @property {OptKit} [no] Options to set `false` + * @property {OptKit} [on] Options to set `true` + * @property {OptKit} [off] Options to set `false` + * @property {OptKit} [not] Options to set `!def`. Call `err` with `null` + * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` + * @property {OptKit} [rst] Options to set `def` + * + * @typedef {OptDef | OptDef[]} ExitKit + * @typedef {TextKit | BoolKit} VarKit + * @typedef {Record} KeyKitMap The `req`. `ExitKit` has higher priority + * @typedef {Text | boolean?} VarVal + * @typedef {Record} KeyValMap The `res`. `undefined` is from `TextKit.def` * * @callback IsFatal - * @param {{msg: string, avi: number, opt: OptStr, key?: KeyStr, val?: VarVal }} err + * @param {{msg: string, avi: number, opt: Option, key?: Key, val?: VarVal }} err * @returns {boolean} Whether the parsing should continue (false) or quit (true) - * @typedef {Record} OptKeyMap internal type + * @typedef {(k: Key, v: VarVal) => void} Act + * @typedef {Record} OptReg internal type */ /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; + /** * Command line argument parser function * @param {string[]} argv Command line arguments array, e.g. `process.argv` @@ -30,56 +48,119 @@ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; * @param {KeyKitMap} req Options structure definition * @param {KeyValMap} res Object to store parsed results * @param {IsFatal} err Error handler function, return true to quit parsing - * @returns {number | { avi: number, key: KeyStr, opt: OptStr }} `ret` is object when an exit option applied, or just `avi` + * @returns {number | { avi: number, key: Key, opt: Option }} `ret` is object when an exit option applied, or just `avi` */ export default function parse(argv, i, req, res, err) { - /** @type {OptStr} option */ + /** @type {Option} */ let opt = ''; - /** @type {KeyStr | undefined} key */ + /** @type {Key} */ let key; + + /** @type {Key | undefined} */ + let _key, _exit = false; + /** @param {VarVal} val */ const set = val => { const cur = res[key]; if (isA(cur)) cur.push(val); else res[key] = val; - }, rst = () => { + }; + const rst = () => { const def = req[key].def; res[key] = isA(def) ? def.slice() : def; - }, noB = () => { + }; + const noB = () => { + const def = req[key].def; if (typeof def === 'boolean') { res[key] = !def; return false; } return true; - }, k = o => o == null ? key : o, // undefined is... well + + + }, k = o => o === null ? key : o, ask = (msg, val) => err({msg, avi: i, opt, key, val}), exit = c => ({ avi: i+c, key, opt }); + + + const a_b1 = k => { res[k] = true; } + const a_b0 = k => { res[k] = false; } + const a_bn = k => { res[k] = req[k].def; } + const a_bi = k => { + const def = res[k].def; + + res[k] = !res[k]; + } + + /** + * @param {OptKit} k + * @param {OptReg} r + * @param {Act} a + * @param {Key} k + * */ + const lo = (k, r, a) => { + if (k === undefined) return; + for (let o of isA(k) ? k : [k]) { + if (o === undefined) continue; + if (o === null) o = key; + if (o !== '--') r[o] = { a, k }; + + } + }, go = (k, r) => { + if (k === undefined) return; + for (let o of isA(k) ? k : [k]) { + if (o === undefined) continue; + if (o === null) o = key; + if (o !== '--') r[o] = key; + else _key = key, _exit = false; + } + }; + + // prepare - /** @type {OptKeyMap} */ - const set_ = {}, rst_ = {}, exit_ = {}; - /** @type {KeyStr | undefined} */ - let _key, _exit = false; + /** @type {OptReg} */ + const exit_ = {}, rst_ = {}, bk_ = {}, tk_ = {}, + str_ = {}, num_ = {}, int_ = {}, sym_ = {}, + b1_ = {}, b0_ = {}, bn_ = {}, bi_ = {}; + for (key in req) { const vk = req[key]; - X: { let xk; - switch (typeof vk) { - case 'object': - if (vk === null) xk = [vk]; - else if (isA(vk)) xk = vk; - else break X; break; - case 'undefined': continue; - default: xk = [vk]; } - for (let o of xk) if ((o=k(o))!=='--') exit_[o] = key; + if (vk === undefined) continue; + xk: { let xk; + if (typeof vk === 'object') { + if (vk === null) xk = [vk]; + else if (isA(vk)) xk = vk; + else break xk; + } else xk = [vk]; + for (let o of xk) { + if (o === undefined) continue; + if (o === null) o = key; + if (o !== '--') exit_[o] = key; else _key = key, _exit = true; - continue; } - const def = vk.def; // if (def === undefined) continue; + } continue; } + const def = vk.def; res[key] = isA(def) ? def.slice() : def; - for (let o of god(vk.set)) if ((o=k(o))!=='--') set_[o] = key; - else if (typeof def !== 'boolean') _key = key, _exit = false; - for (let o of god(vk.rst)) if ((o=k(o))!=='--') rst_[o] = key; + lo(vk.rst, rst_); + // BoolKit + switch (typeof def) { + case 'object': + if (def !== null) break; + case 'boolean': + lo(vk.yes, b1_); + lo(vk.no, b0_); + lo(vk.on, b1_); + lo(vk.off, b0_); + lo(vk.not, bn_); + lo(vk.inv, bi_); + continue; } + // TextKit + go(vk.str, str_); + go(vk.num, num_); + go(vk.int, int_); + go(vk.sym, sym_); } // process let ext = false; - S: for (; i < argv.length; ++i) { + s: for (; i < argv.length; ++i) { const s = argv[i]; // extension :: ASSERT key===set_[opt] if (ext) { ext = false; @@ -88,7 +169,20 @@ export default function parse(argv, i, req, res, err) { } // abc if (s.length < 2 || s[0] !== '-') { - if (key = set_[opt = s]) ext = noB(); + opt = s; + + const ak = exit_[opt]; + if (ak) { + const {a, k} = ak; + + } + + + if (key = exit_[opt]) return exit(1); + else if (key = rst_[opt]) rst(); + + + if (key = str_[opt = s]) ext = noB(); else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); else if (key = _key) if (_exit) return exit(0); else set(s); @@ -101,14 +195,14 @@ export default function parse(argv, i, req, res, err) { for (let j = 1; j < J; ++j) { // -ab :: no extension, no anonymous, no exit opt = '-' + s[j]; - if (key = set_[opt]) { if (noB()) { set(s.slice(j + 1)); continue S; } } + if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return i; } else if (ask('invalid option')) return i; } // -c :: no anonymous opt = '-' + s[J]; - if (key = set_[opt]) ext = noB(); + if (key = str_[opt]) ext = noB(); else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); else if (ask('invalid option')) return i; @@ -119,7 +213,7 @@ export default function parse(argv, i, req, res, err) { const J = s.indexOf('='); if (J < 0) { // --opt ... - if (key = set_[opt = s]) ext = noB(); + if (key = str_[opt = s]) ext = noB(); else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); else if (ask('invalid option')) return i; @@ -128,7 +222,7 @@ export default function parse(argv, i, req, res, err) { // --opt=val let t; opt = s.slice(0, J); const v = s.slice(J + 1); - if (key = set_[opt]) + if (key = str_[opt]) switch (t = typeof res[key]) { case 'boolean': break; default: set(v); continue; } From cc5d3a53a83804fdc284847b0e17305ce335d796 Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 15:07:08 +0800 Subject: [PATCH 02/15] drew some scratch --- index.js | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 3387430..711875a 100644 --- a/index.js +++ b/index.js @@ -6,18 +6,30 @@ const isA = Array.isArray; * @typedef {Option | null} OptDef Option definition, e.g. '--', '-o', '--option' or `null` to refer to the variable name `Key` * @typedef {OptDef | OptDef[]} OptKit one or more option definitions, for a shortest expression * - * @typedef {string | number | bigint | symbol} Text can be shown as text - * - * @typedef {object} TextKit - * @property {Text | Text[]} [def] Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [str] Options to set `arg: string` - * @property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` - * @property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw - * @property {OptKit} [sym] Options to set `Symbol(arg)`. + * @typedef {object} VarKitBase abstract base class + * @property {VarVal | VarVal[]} [def] Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [set] Options to set by referring the data type of `def` * @property {OptKit} [rst] Options to set `def` * - * @typedef {object} BoolKit + * @typedef {string | number} Text can be written as text IN JSON otherwise you can't configure default value in json but does it really matter? yes you do it's your project POSITIONING + * + * @typedef {object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) + * @property {Text} [def] Variable **def**inition & **def**ault value (pun intended) + * @property {number} [ext] Deprecated(it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature + * @property {OptKit} [set] Options to set by referring the data type of `def` + * @property {OptKit} [str] Options to set `arg: string` // which name is better? + * @property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` // dute hmmm.., + * @property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw // but since not available in JSON ... and this name confilct with parseInt + * @property {OptKit} [sym] Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages + * @property {OptKit} [rst] Options to set `def`. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` + * + * @typedef {object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one + * @property {Text[]} def + * @property {OptKit} [set] Options to set by referring the data type of `def` of the value in respective postion + * + * @typedef {object} BoolKit only appearance matters don't give some extensions to it * @property {boolean?} def Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [set] Options to set `!def`. Call `err` with `null`. Identical with `not` * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` * @property {OptKit} [on] Options to set `true` @@ -26,11 +38,11 @@ const isA = Array.isArray; * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` * @property {OptKit} [rst] Options to set `def` * - * @typedef {OptDef | OptDef[]} ExitKit + * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one * @typedef {TextKit | BoolKit} VarKit * @typedef {Record} KeyKitMap The `req`. `ExitKit` has higher priority * @typedef {Text | boolean?} VarVal - * @typedef {Record} KeyValMap The `res`. `undefined` is from `TextKit.def` + * @typedef {Record} KeyValMap The `res`. This `undefined` is from `TextKit.def` * * @callback IsFatal * @param {{msg: string, avi: number, opt: Option, key?: Key, val?: VarVal }} err @@ -159,7 +171,7 @@ export default function parse(argv, i, req, res, err) { go(vk.sym, sym_); } // process - let ext = false; + let ext = false; // this can be a number, so that a multiple extension is naturally available but how do I configure it s: for (; i < argv.length; ++i) { const s = argv[i]; // extension :: ASSERT key===set_[opt] From a3f18366de0936283bfa581b48da29995c7afb51 Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 15:33:01 +0800 Subject: [PATCH 03/15] planning a breaking change regarding the return value --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 711875a..1fac06c 100644 --- a/index.js +++ b/index.js @@ -60,7 +60,7 @@ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; * @param {KeyKitMap} req Options structure definition * @param {KeyValMap} res Object to store parsed results * @param {IsFatal} err Error handler function, return true to quit parsing - * @returns {number | { avi: number, key: Key, opt: Option }} `ret` is object when an exit option applied, or just `avi` + * @returns {number | { avi: number, key: Key, opt: Option }} `ret` is object when an exit option applied, or just `avi`. Maybe you don't need it to return a single `avi` because it seems always equal to `argv.length` */ export default function parse(argv, i, req, res, err) { /** @type {Option} */ From 7f4cb389b1fb220c982499360561d7c4c5a9c1b6 Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 17:31:45 +0800 Subject: [PATCH 04/15] fine doc --- index.js | 87 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/index.js b/index.js index 1fac06c..aca24f6 100644 --- a/index.js +++ b/index.js @@ -6,29 +6,12 @@ const isA = Array.isArray; * @typedef {Option | null} OptDef Option definition, e.g. '--', '-o', '--option' or `null` to refer to the variable name `Key` * @typedef {OptDef | OptDef[]} OptKit one or more option definitions, for a shortest expression * - * @typedef {object} VarKitBase abstract base class - * @property {VarVal | VarVal[]} [def] Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [set] Options to set by referring the data type of `def` - * @property {OptKit} [rst] Options to set `def` - * - * @typedef {string | number} Text can be written as text IN JSON otherwise you can't configure default value in json but does it really matter? yes you do it's your project POSITIONING - * - * @typedef {object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) - * @property {Text} [def] Variable **def**inition & **def**ault value (pun intended) - * @property {number} [ext] Deprecated(it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature - * @property {OptKit} [set] Options to set by referring the data type of `def` - * @property {OptKit} [str] Options to set `arg: string` // which name is better? - * @property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` // dute hmmm.., - * @property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw // but since not available in JSON ... and this name confilct with parseInt - * @property {OptKit} [sym] Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages - * @property {OptKit} [rst] Options to set `def`. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` - * - * @typedef {object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one - * @property {Text[]} def - * @property {OptKit} [set] Options to set by referring the data type of `def` of the value in respective postion + * @typedef {boolean | null} Bool actually `Optional` + * @typedef {string | number} Text Values that can be written as text IN JSON. it's the project POSITIONING + * @typedef {Text[]} List * - * @typedef {object} BoolKit only appearance matters don't give some extensions to it - * @property {boolean?} def Variable **def**inition & **def**ault value (pun intended) + * @typedef {Object} BoolKit only appearance matters don't give some extensions to it + * @property {Bool} def Variable **def**inition & **def**ault value (pun intended) * @property {OptKit} [set] Options to set `!def`. Call `err` with `null`. Identical with `not` * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` @@ -38,17 +21,34 @@ const isA = Array.isArray; * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` * @property {OptKit} [rst] Options to set `def` * + * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) + * @property {Text} def Variable **def**inition & **def**ault value (pun intended) + * property {number} [ext] Deprecated(it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature + * @property {OptKit} [set] Options to set by referring the data type of `def` + * property {OptKit} [str] Options to set `arg: string` // which name is better? + * property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` // dute hmmm.., + * property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw // but since not available in JSON ... and this name confilct with parseInt + * property {OptKit} [sym] Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages + * @property {OptKit} [rst] Options to set `def`. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` + * + * @typedef {Object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one + * @property {List} def + * @property {OptKit} [set] Options to set by referring the data type of `def` of the value in respective postion + * @property {OptKit} [rst] + * * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one - * @typedef {TextKit | BoolKit} VarKit - * @typedef {Record} KeyKitMap The `req`. `ExitKit` has higher priority - * @typedef {Text | boolean?} VarVal - * @typedef {Record} KeyValMap The `res`. This `undefined` is from `TextKit.def` + * @typedef {BoolKit | TextKit | ListKit} VarKit + * @typedef {{ [key: Key]: ExitKit | VarKit }} KitMap The `req` arg of `parse`. `ExitKit` has higher priority (extension is even higher. '--' is highest) + * + * @typedef {Bool | Text | List} VarVal + * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. + * @typedef {{ avi: number, key: Key, opt: Option }} ExitVal * * @callback IsFatal - * @param {{msg: string, avi: number, opt: Option, key?: Key, val?: VarVal }} err + * @param {{ msg: string, avi: number, opt: Option, key?: Key, val?: VarVal }} err * @returns {boolean} Whether the parsing should continue (false) or quit (true) * @typedef {(k: Key, v: VarVal) => void} Act - * @typedef {Record} OptReg internal type + * @typedef {{ [opt: Option]: { a: Act, k: Key } }} OptReg internal type */ /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; @@ -57,10 +57,10 @@ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; * Command line argument parser function * @param {string[]} argv Command line arguments array, e.g. `process.argv` * @param {number} i Index of current argument being processed, e.g. `2` - * @param {KeyKitMap} req Options structure definition - * @param {KeyValMap} res Object to store parsed results + * @param {KitMap} req Options structure definition + * @param {VarValMap} res Object to store parsed results * @param {IsFatal} err Error handler function, return true to quit parsing - * @returns {number | { avi: number, key: Key, opt: Option }} `ret` is object when an exit option applied, or just `avi`. Maybe you don't need it to return a single `avi` because it seems always equal to `argv.length` + * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { avi: number, key: Key, opt: Option }` */ export default function parse(argv, i, req, res, err) { /** @type {Option} */ @@ -177,7 +177,7 @@ export default function parse(argv, i, req, res, err) { // extension :: ASSERT key===set_[opt] if (ext) { ext = false; if (key) { set(s); continue; } - if (ask('invalid option', s)) return i; + if (ask('invalid option', s)) return null; } // abc if (s.length < 2 || s[0] !== '-') { @@ -198,26 +198,27 @@ export default function parse(argv, i, req, res, err) { else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); else if (key = _key) if (_exit) return exit(0); else set(s); - else if (ask('invalid option')) return i; + else if (ask('invalid option')) return null; continue; } // -abc - if (s[1] !== '-') { + if (s[1] !== '-') { + // maybe support '-opt' ? wtf const J = s.length - 1; for (let j = 1; j < J; ++j) { // -ab :: no extension, no anonymous, no exit opt = '-' + s[j]; if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } else if (key = rst_[opt]) rst(); - else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return i; } - else if (ask('invalid option')) return i; + else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return null; } + else if (ask('invalid option')) return null; } // -c :: no anonymous opt = '-' + s[J]; if (key = str_[opt]) ext = noB(); else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); - else if (ask('invalid option')) return i; + else if (ask('invalid option')) return null; continue; } // --opt @@ -228,7 +229,7 @@ export default function parse(argv, i, req, res, err) { if (key = str_[opt = s]) ext = noB(); else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) return exit(1); - else if (ask('invalid option')) return i; + else if (ask('invalid option')) return null; continue; } // --opt=val @@ -240,9 +241,9 @@ export default function parse(argv, i, req, res, err) { default: set(v); continue; } else if (key = rst_[opt]) t = 'reset'; else if (key = exit_[opt]) t = 'exit'; - else if (ask('invalid option', v)) return i; + else if (ask('invalid option', v)) return null; else continue; - if (ask(`cannot assign value to ${t} option`, v)) return i; + if (ask(`cannot assign value to ${t} option`, v)) return null; continue; } opt = '--'; @@ -251,10 +252,10 @@ export default function parse(argv, i, req, res, err) { const a = res[key], l = argv.length; ++i; if (isA(a)) while (i < l) a.push(argv[i++]); else if (i < l) res[key] = argv[(i = l) - 1]; - return i; + return null; } - if (ask('anonymous arguments are not allowed')) return i; + if (ask('anonymous arguments are not allowed')) return null; } if (ext) ask('this option requires an argument'); - return i; + return null; }; \ No newline at end of file From 9f0abeaec5e3a96d89b27da607d0175cbbaafecf Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 18:33:30 +0800 Subject: [PATCH 05/15] add feature feature --- index.js | 82 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index aca24f6..877d997 100644 --- a/index.js +++ b/index.js @@ -52,6 +52,18 @@ const isA = Array.isArray; */ /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; +export const defaults = { + /** `'--'` => Consume **all** the rest arguments */ + dd_all: true, // modify the compile time behavior + /** `'-opt'` => `{ opt: '-opt' }` */ + d_opt: false, + /** `'-abc'` => `'-a', '-b', '-c'` */ + d_abc: true, + /** `'-oval'` => `{ opt: '-o', val: 'val' }` */ + d_o_val: true, + /** `'--opt=val'` => `{ opt: '--opt', val: 'val' }` */ + dd_opt_eq_val: true, +}; /** * Command line argument parser function @@ -62,7 +74,7 @@ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; * @param {IsFatal} err Error handler function, return true to quit parsing * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { avi: number, key: Key, opt: Option }` */ -export default function parse(argv, i, req, res, err) { +export default function parse(argv, i, req, res, err, feature = defaults) { /** @type {Option} */ let opt = ''; /** @type {Key} */ @@ -201,19 +213,30 @@ export default function parse(argv, i, req, res, err) { else if (ask('invalid option')) return null; continue; } - // -abc + // -abc or -opt if (s[1] !== '-') { - // maybe support '-opt' ? wtf + // -opt + if (feature.d_opt) { + // WTF maybe support '-opt' ? wtf // maybe should record the max length of '-opt's + } const J = s.length - 1; - for (let j = 1; j < J; ++j) { + // -abc + if (feature.d_abc) { // -ab :: no extension, no anonymous, no exit - opt = '-' + s[j]; - if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } - else if (key = rst_[opt]) rst(); - else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return null; } - else if (ask('invalid option')) return null; + for (let j = 1; j < J; ++j) { + opt = '-' + s[j]; + if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } // d_o_val + else if (key = rst_[opt]) rst(); + else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return null; } + else if (ask('invalid option')) return null; + } + } else if (J !== 1) { + // opt? key? + opt = s; + if (ask('invalid option')) return null; + continue; } - // -c :: no anonymous + // -c or -o :: no anonymous opt = '-' + s[J]; if (key = str_[opt]) ext = noB(); else if (key = rst_[opt]) rst(); @@ -223,27 +246,28 @@ export default function parse(argv, i, req, res, err) { } // --opt if (s.length > 2) { - const J = s.indexOf('='); - if (J < 0) { - // --opt ... - if (key = str_[opt = s]) ext = noB(); - else if (key = rst_[opt]) rst(); - else if (key = exit_[opt]) return exit(1); - else if (ask('invalid option')) return null; + // --opt=val + if (feature.dd_opt_eq_val) eq: { + const J = s.indexOf('='); + if (J < 0) break eq; + let t; opt = s.slice(0, J); + const v = s.slice(J + 1); + if (key = str_[opt]) + switch (t = typeof res[key]) { + case 'boolean': break; + default: set(v); continue; } + else if (key = rst_[opt]) t = 'reset'; + else if (key = exit_[opt]) t = 'exit'; + else if (ask('invalid option', v)) return null; + else continue; + if (ask(`cannot assign value to ${t} option`, v)) return null; continue; } - // --opt=val - let t; opt = s.slice(0, J); - const v = s.slice(J + 1); - if (key = str_[opt]) - switch (t = typeof res[key]) { - case 'boolean': break; - default: set(v); continue; } - else if (key = rst_[opt]) t = 'reset'; - else if (key = exit_[opt]) t = 'exit'; - else if (ask('invalid option', v)) return null; - else continue; - if (ask(`cannot assign value to ${t} option`, v)) return null; + // --opt ... + if (key = str_[opt = s]) ext = noB(); + else if (key = rst_[opt]) rst(); + else if (key = exit_[opt]) return exit(1); + else if (ask('invalid option')) return null; continue; } opt = '--'; From c496b47af0bd731bc62b285939b4d11b4a51634b Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 20:25:27 +0800 Subject: [PATCH 06/15] add BoolKit.nul (what?) --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 877d997..ccc1bea 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ const isA = Array.isArray; * @property {OptKit} [off] Options to set `false` * @property {OptKit} [not] Options to set `!def`. Call `err` with `null` * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` + * @property {OptKit} [nul] Options to set `null`. (what??) * @property {OptKit} [rst] Options to set `def` * * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) From eb369c7f54586514043196a964b266674a7eadad Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 21:08:45 +0800 Subject: [PATCH 07/15] fix: add -a branch; add: TODO; refine cfg --- index.js | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index ccc1bea..414044e 100644 --- a/index.js +++ b/index.js @@ -54,15 +54,20 @@ const isA = Array.isArray; /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; export const defaults = { - /** `'--'` => Consume **all** the rest arguments */ - dd_all: true, // modify the compile time behavior - /** `'-opt'` => `{ opt: '-opt' }` */ + // WTF feature + /** dash sign @type {'-'} */ + d: '-', + /** equal sign @type {'='} */ + eq: '=', + /** `'-opt'` => `{ opt: '-opt' }` @type {false} */ d_opt: false, - /** `'-abc'` => `'-a', '-b', '-c'` */ + /** `'-abc'` => `'-a', '-b', '-c'` @type {true} */ d_abc: true, - /** `'-oval'` => `{ opt: '-o', val: 'val' }` */ + /** `'-oval'` => `{ opt: '-o', val: 'val' }` @type {true} */ d_o_val: true, - /** `'--opt=val'` => `{ opt: '--opt', val: 'val' }` */ + /** `'--'` => Consume **all** the rest arguments @type {true} */ // modify the compile time behavior + dd_all: true, + /** `'--opt=val'` => `{ opt: '--opt', val: 'val' }` @type {true} */ dd_opt_eq_val: true, }; @@ -75,7 +80,7 @@ export const defaults = { * @param {IsFatal} err Error handler function, return true to quit parsing * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { avi: number, key: Key, opt: Option }` */ -export default function parse(argv, i, req, res, err, feature = defaults) { +export default function parse(argv, i, req, res, err = console.error, cfg = defaults) { /** @type {Option} */ let opt = ''; /** @type {Key} */ @@ -140,7 +145,7 @@ export default function parse(argv, i, req, res, err, feature = defaults) { } }; - + // TODO: const d0 = {}, d1 = {}, d2 = {} // prepare /** @type {OptReg} */ const exit_ = {}, rst_ = {}, bk_ = {}, tk_ = {}, @@ -217,23 +222,26 @@ export default function parse(argv, i, req, res, err, feature = defaults) { // -abc or -opt if (s[1] !== '-') { // -opt - if (feature.d_opt) { + if (cfg.d_opt) { // WTF maybe support '-opt' ? wtf // maybe should record the max length of '-opt's } - const J = s.length - 1; + const J = s.length - 1; // this can't be negative // -abc - if (feature.d_abc) { - // -ab :: no extension, no anonymous, no exit - for (let j = 1; j < J; ++j) { + + // -a :: yes new branch TODO TODO TODO + + if (cfg.d_abc) { + // -b :: no extension, no anonymous, no exit + for (let j = 2; j < J; ++j) { opt = '-' + s[j]; - if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } // d_o_val + if (key = str_[opt]) { if (noB()) { set(s.slice(j + 1)); continue s; } } // TODO feature.d_o_val else if (key = rst_[opt]) rst(); else if (key = exit_[opt]) { if (ask('cannot exit within an argument')) return null; } else if (ask('invalid option')) return null; } - } else if (J !== 1) { + } else if (J > 1) { // opt? key? - opt = s; + opt = s[2]; // check it if (ask('invalid option')) return null; continue; } @@ -248,9 +256,9 @@ export default function parse(argv, i, req, res, err, feature = defaults) { // --opt if (s.length > 2) { // --opt=val - if (feature.dd_opt_eq_val) eq: { - const J = s.indexOf('='); - if (J < 0) break eq; + if (cfg.dd_opt_eq_val) eq: { + let J = s.indexOf('='); // TODO s.slice(2, maxOptLen).indexOf('=') + if (J < 0) break eq; // TODO else J += 2; let t; opt = s.slice(0, J); const v = s.slice(J + 1); if (key = str_[opt]) From 7ce57d95a7be492e4b77940318b9aed25f091cc1 Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 21:17:23 +0800 Subject: [PATCH 08/15] no more avi --- index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 414044e..eb16309 100644 --- a/index.js +++ b/index.js @@ -43,10 +43,10 @@ const isA = Array.isArray; * * @typedef {Bool | Text | List} VarVal * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. - * @typedef {{ avi: number, key: Key, opt: Option }} ExitVal + * @typedef {{ i: number, key: Key, opt: Option }} ExitVal * * @callback IsFatal - * @param {{ msg: string, avi: number, opt: Option, key?: Key, val?: VarVal }} err + * @param {{ i: number, msg: string, opt: Option, key?: Key, val?: VarVal }} err * @returns {boolean} Whether the parsing should continue (false) or quit (true) * @typedef {(k: Key, v: VarVal) => void} Act * @typedef {{ [opt: Option]: { a: Act, k: Key } }} OptReg internal type @@ -77,8 +77,8 @@ export const defaults = { * @param {number} i Index of current argument being processed, e.g. `2` * @param {KitMap} req Options structure definition * @param {VarValMap} res Object to store parsed results - * @param {IsFatal} err Error handler function, return true to quit parsing - * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { avi: number, key: Key, opt: Option }` + * @param {IsFatal} err Error handler function, return `true` to quit parsing. It'a clean quit, no bad data in `res` + * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { i: number, key: Key, opt: Option }` */ export default function parse(argv, i, req, res, err = console.error, cfg = defaults) { /** @type {Option} */ @@ -108,8 +108,8 @@ export default function parse(argv, i, req, res, err = console.error, cfg = defa }, k = o => o === null ? key : o, - ask = (msg, val) => err({msg, avi: i, opt, key, val}), - exit = c => ({ avi: i+c, key, opt }); + ask = (msg, val) => err({ i, msg, opt, key, val }), + exit = c => ({ i: i+c, key, opt }); const a_b1 = k => { res[k] = true; } From 958cb9364331cc396416d3a0432dfa61b0a92266 Mon Sep 17 00:00:00 2001 From: Chinory Date: Mon, 7 Apr 2025 22:09:47 +0800 Subject: [PATCH 09/15] todo --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index eb16309..86bcd0e 100644 --- a/index.js +++ b/index.js @@ -19,7 +19,7 @@ const isA = Array.isArray; * @property {OptKit} [off] Options to set `false` * @property {OptKit} [not] Options to set `!def`. Call `err` with `null` * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` - * @property {OptKit} [nul] Options to set `null`. (what??) + * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [rst] Options to set `def` * * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) @@ -189,9 +189,11 @@ export default function parse(argv, i, req, res, err = console.error, cfg = defa go(vk.sym, sym_); } // process + // maxOptLen += 1; // TODO // cfg.eq.length; // wtf is this let ext = false; // this can be a number, so that a multiple extension is naturally available but how do I configure it s: for (; i < argv.length; ++i) { const s = argv[i]; + // const h = s.length > maxOptLen ? s.slice(0, maxOptLen) : s; // TODO // extension :: ASSERT key===set_[opt] if (ext) { ext = false; if (key) { set(s); continue; } From 151852ffdd09f6895cd152e10ebad510464d294f Mon Sep 17 00:00:00 2001 From: Chinory Date: Tue, 8 Apr 2025 00:39:04 +0800 Subject: [PATCH 10/15] Bool can be undefined and Text can be null. Kit.set is to set the raw string sliced from argv, without doing any format check --- index.js | 70 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index 86bcd0e..222fd50 100644 --- a/index.js +++ b/index.js @@ -2,58 +2,60 @@ const isA = Array.isArray; /** * @typedef {string} Key Variable name, in most cases, or kit name - * @typedef {string} Option Option string, e.g. '--', '-o', '--option' - * @typedef {Option | null} OptDef Option definition, e.g. '--', '-o', '--option' or `null` to refer to the variable name `Key` + * @typedef {string} Opt Option string, e.g. '--', '-o', '--option' + * @typedef {Opt | null} OptDef Option definition(or default option(pun again?)), e.g. '--', '-o', '--option' or `null` to refer to the variable name `Key` * @typedef {OptDef | OptDef[]} OptKit one or more option definitions, for a shortest expression * - * @typedef {boolean | null} Bool actually `Optional` - * @typedef {string | number} Text Values that can be written as text IN JSON. it's the project POSITIONING - * @typedef {Text[]} List + * @typedef {boolean | undefined} Bool actually `Optional`. yes since a bool can't be inside an array in this project, this design is good, very good, PERFECT + * @typedef {string | number | null} Text Values that can be written as text IN JSON. it's the project POSITIONING + * @typedef {Text[]} List only adding or resetting for now. oh this is enough as this is just a configurer not a programming language * * @typedef {Object} BoolKit only appearance matters don't give some extensions to it - * @property {Bool} def Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [set] Options to set `!def`. Call `err` with `null`. Identical with `not` + * @property {Bool} [def] Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [set] Options to set `!def`. Raise `err` with `undefined`. Identical with `not`. // maybe not like this?? any other behavior? * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` * @property {OptKit} [on] Options to set `true` * @property {OptKit} [off] Options to set `false` - * @property {OptKit} [not] Options to set `!def`. Call `err` with `null` - * @property {OptKit} [inv] Options to set `!cur`. Call `err` with `null` - * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions + * @property {OptKit} [not] Options to set `!def`. Raise `err` with `undefined` + * @property {OptKit} [inv] Options to set `!cur`. Raise `err` with `undefined` + * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [rst] Options to set `def` * * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) - * @property {Text} def Variable **def**inition & **def**ault value (pun intended) - * property {number} [ext] Deprecated(it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature - * @property {OptKit} [set] Options to set by referring the data type of `def` - * property {OptKit} [str] Options to set `arg: string` // which name is better? - * property {OptKit} [num] Options to set `Number(arg)`. Call `err` with `NaN` // dute hmmm.., - * property {OptKit} [int] Options to set `BigInt(arg)`. Call `err` when throw // but since not available in JSON ... and this name confilct with parseInt - * property {OptKit} [sym] Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages + * @property {Text?} def Variable **def**inition & **def**ault value (pun intended) can be `null`, maybe this is the only way to provide `Optional` + * @property {OptKit} [set] Options to set the raw string slice from the argv + * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [rst] Options to set `def`. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` + * @property {number} [ext] Deprecated (it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature + * @property {OptKit} [str] External Options to set a string, can escape the quotes + * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally + * @property {OptKit} [int] Deprecated External Options to set `BigInt(arg)`. Raise `err` when throw // but since not available in JSON ... and this name confilct with parseInt + * @property {OptKit} [sym] Deprecated External Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages * * @typedef {Object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one - * @property {List} def - * @property {OptKit} [set] Options to set by referring the data type of `def` of the value in respective postion - * @property {OptKit} [rst] + * @property {List} def there is no `Optional` for sure because it's stupid for an emptiable container. All set option in `TextKit` is available to push the value into the array + * @property {OptKit} [set] Options to set the raw string slice from the argv + * @property {OptKit} [rst] Options to set the array back to `def` * * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one * @typedef {BoolKit | TextKit | ListKit} VarKit * @typedef {{ [key: Key]: ExitKit | VarKit }} KitMap The `req` arg of `parse`. `ExitKit` has higher priority (extension is even higher. '--' is highest) * - * @typedef {Bool | Text | List} VarVal + * @typedef {Bool | Text | List} VarVal // `undefined` is from `Bool`, `null` is from `Text` * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. - * @typedef {{ i: number, key: Key, opt: Option }} ExitVal + * @typedef {{ i: number, key: Key, opt: Opt }} ExitVal * * @callback IsFatal - * @param {{ i: number, msg: string, opt: Option, key?: Key, val?: VarVal }} err + * @param {{ i: number, msg: string, opt: Opt, key?: Key, val?: VarVal }} err * @returns {boolean} Whether the parsing should continue (false) or quit (true) * @typedef {(k: Key, v: VarVal) => void} Act - * @typedef {{ [opt: Option]: { a: Act, k: Key } }} OptReg internal type + * @typedef {{ [cmd: string]: Act }} KitImpl + * @typedef {{ [opt: Opt]: { a: Act, k: Key } }} OptReg internal type */ /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; -export const defaults = { +export const Config = { // WTF feature /** dash sign @type {'-'} */ d: '-', @@ -71,6 +73,18 @@ export const defaults = { dd_opt_eq_val: true, }; + +/** @type {KitImpl} */ +const bki = { + set: function(k, v) { + + } +}; + + + +export { bki as BoolKitImpl }; + /** * Command line argument parser function * @param {string[]} argv Command line arguments array, e.g. `process.argv` @@ -78,10 +92,10 @@ export const defaults = { * @param {KitMap} req Options structure definition * @param {VarValMap} res Object to store parsed results * @param {IsFatal} err Error handler function, return `true` to quit parsing. It'a clean quit, no bad data in `res` - * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { i: number, key: Key, opt: Option }` + * @returns {ExitVal?} `ret` provided when an Exit option applied `@type { i: number, key: Key, opt: Opt }` */ -export default function parse(argv, i, req, res, err = console.error, cfg = defaults) { - /** @type {Option} */ +export default function parse(argv, i, req, res, err = console.error, cfg = Config) { + /** @type {Opt} */ let opt = ''; /** @type {Key} */ let key; From bbabedb4b83ae3cb3fa6cdf5ede0ec1da447a14e Mon Sep 17 00:00:00 2001 From: Chinory Date: Wed, 9 Apr 2025 00:16:40 +0800 Subject: [PATCH 11/15] add IVarKit --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 222fd50..dd392d3 100644 --- a/index.js +++ b/index.js @@ -41,6 +41,7 @@ const isA = Array.isArray; * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one * @typedef {BoolKit | TextKit | ListKit} VarKit * @typedef {{ [key: Key]: ExitKit | VarKit }} KitMap The `req` arg of `parse`. `ExitKit` has higher priority (extension is even higher. '--' is highest) + * @typedef {{ def?: VarVal, [cmd: string]: OptKit }} IVarKit * * @typedef {Bool | Text | List} VarVal // `undefined` is from `Bool`, `null` is from `Text` * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. From 3e5cb2f901cccdaa204820c14f5c0db8a163a8b0 Mon Sep 17 00:00:00 2001 From: Chinory Date: Wed, 9 Apr 2025 02:08:44 +0800 Subject: [PATCH 12/15] since `def` is a keyword in Python... byebye `def` qwq --- index.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index dd392d3..cf672eb 100644 --- a/index.js +++ b/index.js @@ -11,22 +11,22 @@ const isA = Array.isArray; * @typedef {Text[]} List only adding or resetting for now. oh this is enough as this is just a configurer not a programming language * * @typedef {Object} BoolKit only appearance matters don't give some extensions to it - * @property {Bool} [def] Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [set] Options to set `!def`. Raise `err` with `undefined`. Identical with `not`. // maybe not like this?? any other behavior? + * @property {Bool} [_] ***def***: Variable **def**inition & **def**ault value (pun intended) + * @property {OptKit} [set] Options to set !***def***. Raise `err` if is not a `boolean`. Identical with `not`. // maybe not like this?? any other behavior? + * @property {OptKit} [rst] Options to set ***def*** + * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` * @property {OptKit} [on] Options to set `true` * @property {OptKit} [off] Options to set `false` - * @property {OptKit} [not] Options to set `!def`. Raise `err` with `undefined` - * @property {OptKit} [inv] Options to set `!cur`. Raise `err` with `undefined` - * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions - * @property {OptKit} [rst] Options to set `def` + * @property {OptKit} [not] Options to set !***def***. Raise `err` if is not a `boolean` + * @property {OptKit} [inv] Options to set `!cur`. Raise `err` if is not a `boolean` * * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) - * @property {Text?} def Variable **def**inition & **def**ault value (pun intended) can be `null`, maybe this is the only way to provide `Optional` + * @property {Text?} _ ***def***: Variable **def**inition & **def**ault value (pun intended) can be `null`, maybe this is the only way to provide `Optional` * @property {OptKit} [set] Options to set the raw string slice from the argv + * @property {OptKit} [rst] Options to set ***def***. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions - * @property {OptKit} [rst] Options to set `def`. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` * @property {number} [ext] Deprecated (it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature * @property {OptKit} [str] External Options to set a string, can escape the quotes * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally @@ -34,14 +34,16 @@ const isA = Array.isArray; * @property {OptKit} [sym] Deprecated External Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages * * @typedef {Object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one - * @property {List} def there is no `Optional` for sure because it's stupid for an emptiable container. All set option in `TextKit` is available to push the value into the array + * @property {List} _ ***def***: there is no `Optional` for sure because it's stupid for an emptiable container. All set option in `TextKit` is available to push the value into the array * @property {OptKit} [set] Options to set the raw string slice from the argv - * @property {OptKit} [rst] Options to set the array back to `def` - * + * @property {OptKit} [rst] Options to set the array back to ***def***: `_` + * @property {OptKit} [str] External Options to set a string, can escape the quotes + * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally + * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one * @typedef {BoolKit | TextKit | ListKit} VarKit * @typedef {{ [key: Key]: ExitKit | VarKit }} KitMap The `req` arg of `parse`. `ExitKit` has higher priority (extension is even higher. '--' is highest) - * @typedef {{ def?: VarVal, [cmd: string]: OptKit }} IVarKit + * @typedef {{ _?: VarVal, [cmd: string]: OptKit }} IVarKit * * @typedef {Bool | Text | List} VarVal // `undefined` is from `Bool`, `null` is from `Text` * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. @@ -110,12 +112,12 @@ export default function parse(argv, i, req, res, err = console.error, cfg = Conf if (isA(cur)) cur.push(val); else res[key] = val; }; const rst = () => { - const def = req[key].def; + const def = req[key]._; res[key] = isA(def) ? def.slice() : def; }; const noB = () => { - const def = req[key].def; + const def = req[key]._; if (typeof def === 'boolean') { res[key] = !def; return false; @@ -129,9 +131,9 @@ export default function parse(argv, i, req, res, err = console.error, cfg = Conf const a_b1 = k => { res[k] = true; } const a_b0 = k => { res[k] = false; } - const a_bn = k => { res[k] = req[k].def; } + const a_bn = k => { res[k] = req[k]._; } const a_bi = k => { - const def = res[k].def; + const def = res[k]._; res[k] = !res[k]; } From 4bc18cf11d46b38ec3de51c3cf58a38416ac308a Mon Sep 17 00:00:00 2001 From: Chinory Date: Wed, 9 Apr 2025 15:18:54 +0800 Subject: [PATCH 13/15] add jsdoc PropKit hell many features --- index.js | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/index.js b/index.js index cf672eb..10e8bd2 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,4 @@ 'use strict'; -const isA = Array.isArray; /** * @typedef {string} Key Variable name, in most cases, or kit name * @typedef {string} Opt Option string, e.g. '--', '-o', '--option' @@ -9,53 +8,67 @@ const isA = Array.isArray; * @typedef {boolean | undefined} Bool actually `Optional`. yes since a bool can't be inside an array in this project, this design is good, very good, PERFECT * @typedef {string | number | null} Text Values that can be written as text IN JSON. it's the project POSITIONING * @typedef {Text[]} List only adding or resetting for now. oh this is enough as this is just a configurer not a programming language + * @typedef {{ [nestable: string]: { bool: boolean, str: string, num: number, list: Text[] } }} Prop supprt `-Xo1.o2.o3 val`, `-Xo1.o2=val` `--opt=o1=val` * * @typedef {Object} BoolKit only appearance matters don't give some extensions to it * @property {Bool} [_] ***def***: Variable **def**inition & **def**ault value (pun intended) * @property {OptKit} [set] Options to set !***def***. Raise `err` if is not a `boolean`. Identical with `not`. // maybe not like this?? any other behavior? - * @property {OptKit} [rst] Options to set ***def*** - * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` * @property {OptKit} [on] Options to set `true` * @property {OptKit} [off] Options to set `false` * @property {OptKit} [not] Options to set !***def***. Raise `err` if is not a `boolean` * @property {OptKit} [inv] Options to set `!cur`. Raise `err` if is not a `boolean` + * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions + * @property {OptKit} [rst] Options to set ***def*** * * @typedef {Object} TextKit you have to use something. but if i say, when ext=0, it is just Bool..hmmmm BUT you can't name a zero size setter, it must be 'set', 'set_set', 'str_num', but not ''(???) * @property {Text?} _ ***def***: Variable **def**inition & **def**ault value (pun intended) can be `null`, maybe this is the only way to provide `Optional` * @property {OptKit} [set] Options to set the raw string slice from the argv - * @property {OptKit} [rst] Options to set ***def***. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` - * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions - * @property {number} [ext] Deprecated (it's in setter name now) consume such number of arguments as extension at once. only available when def is an array. so we shall ban the -- for '' the ambiguous feature * @property {OptKit} [str] External Options to set a string, can escape the quotes * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally - * @property {OptKit} [int] Deprecated External Options to set `BigInt(arg)`. Raise `err` when throw // but since not available in JSON ... and this name confilct with parseInt - * @property {OptKit} [sym] Deprecated External Options to set `Symbol(arg)`. // but since not available in JSON ... // omg it doesn't even available in most of other languages + * @property {OptKit} [nul] Options to set `null`. (what??) maybe the user wants to cause some NullExceptions + * @property {OptKit} [rst] Options to set ***def***. The short name is reasonable, so you don't match `reset` when you search `set`, it IS `rst` * * @typedef {Object} ListKit now you can put 'set', 'set_set', 'str_num', here. OR add another param to parse() to define how to initialize a var from raw string. YES! It's not my duty! Something like `{ num: v => { if (isNaN(v = Number(v)) throw 'not a number'; return v; } }`, ` { i64: v => { throw 'This is in JAVASCRIPT you Rust guy' } }`, ` { bigint: BigInt } // it throws`. AND throw directly if there's a mistake in `req` as it's a compile time error not a runtime one * @property {List} _ ***def***: there is no `Optional` for sure because it's stupid for an emptiable container. All set option in `TextKit` is available to push the value into the array * @property {OptKit} [set] Options to set the raw string slice from the argv - * @property {OptKit} [rst] Options to set the array back to ***def***: `_` * @property {OptKit} [str] External Options to set a string, can escape the quotes * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally - + * @property {OptKit} [nul] Options to set `null`, that is, add a `null` to the array. (what??) maybe the user wants to cause some NullExceptions + * @property {OptKit} [rst] Options to set the array back to ***def***: `_` + * + * @typedef {Object} PropKit + * @property {Prop} _ + * @property {OptKit} [set] Options to set the raw string slice from the argv. Raise `err` if that is not a `string` + * @property {OptKit} [str] External Options to set a string, can escape the quotes. Raise `err` if is not a `string` + * @property {OptKit} [num] External Options to set `Number(arg)`. Raise `err` if is not a `number`. Raise `err` with `NaN`. Provide a JSON Number Type in other language, naturally + * @property {OptKit} [not] Options to set !***def***. Raise `err` if is not a `boolean` + * @property {OptKit} [inv] Options to set `!cur`. Raise `err` if is not a `boolean` + * @property {OptKit} [yes] Options to set `true`. Raise `err` if is not a `boolean` + * @property {OptKit} [no] Options to set `false`. Raise `err` if is not a `boolean` + * @property {OptKit} [on] Options to set `true`. Raise `err` if is not a `boolean` + * @property {OptKit} [off] Options to set `false`. Raise `err` if is not a `boolean` + * @property {OptKit} [nul] Options to set `null`, or add a `null` to the array. + * @property {OptKit} [rst] Options to set the value/array in that postion back to ***def***: `_` + * * @typedef {OptDef | OptDef[]} ExitKit there are only two types of container in JSON: list, dict. And this the list one - * @typedef {BoolKit | TextKit | ListKit} VarKit + * @typedef {BoolKit | TextKit | ListKit | PropKit} VarKit + * @typedef {{ _?: VarVal, [act: string]: OptKit }} VarKitLike * @typedef {{ [key: Key]: ExitKit | VarKit }} KitMap The `req` arg of `parse`. `ExitKit` has higher priority (extension is even higher. '--' is highest) - * @typedef {{ _?: VarVal, [cmd: string]: OptKit }} IVarKit * - * @typedef {Bool | Text | List} VarVal // `undefined` is from `Bool`, `null` is from `Text` + * @typedef {Bool | Text | List | Prop} VarVal // `undefined` is from `Bool`, `null` is from `Text` * @typedef {{ [key: Key]: VarVal }} VarValMap The `req` arg of `parse`. * @typedef {{ i: number, key: Key, opt: Opt }} ExitVal * * @callback IsFatal - * @param {{ i: number, msg: string, opt: Opt, key?: Key, val?: VarVal }} err + * @param {{ i: number, msg: string, opt: Opt, key?: Key, val?: VarVal, prop?: Prop }} err * @returns {boolean} Whether the parsing should continue (false) or quit (true) * @typedef {(k: Key, v: VarVal) => void} Act - * @typedef {{ [cmd: string]: Act }} KitImpl + * @typedef {{ [act: string]: Act }} KitImpl * @typedef {{ [opt: Opt]: { a: Act, k: Key } }} OptReg internal type */ +const isA = Array.isArray; /** Get OD! @param {OptKit} ok */ const god = ok => ok === undefined ? [] : isA(ok) ? ok : [ok]; export const Config = { From 5fc7d4a3fb6f1c7012d414160d41c996c0ad6112 Mon Sep 17 00:00:00 2001 From: Chinory Date: Wed, 9 Apr 2025 15:27:28 +0800 Subject: [PATCH 14/15] remove BoolKit.set for coherence --- index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 10e8bd2..e80ff0e 100644 --- a/index.js +++ b/index.js @@ -12,13 +12,12 @@ * * @typedef {Object} BoolKit only appearance matters don't give some extensions to it * @property {Bool} [_] ***def***: Variable **def**inition & **def**ault value (pun intended) - * @property {OptKit} [set] Options to set !***def***. Raise `err` if is not a `boolean`. Identical with `not`. // maybe not like this?? any other behavior? + * @property {OptKit} [not] Options to set !***def***. Raise `err` if is not a `boolean` + * @property {OptKit} [inv] Options to set `!cur`. Raise `err` if is not a `boolean` * @property {OptKit} [yes] Options to set `true` * @property {OptKit} [no] Options to set `false` * @property {OptKit} [on] Options to set `true` * @property {OptKit} [off] Options to set `false` - * @property {OptKit} [not] Options to set !***def***. Raise `err` if is not a `boolean` - * @property {OptKit} [inv] Options to set `!cur`. Raise `err` if is not a `boolean` * @property {OptKit} [nul] Options to set `undefined`. (what??) maybe the user wants to cause some NullExceptions * @property {OptKit} [rst] Options to set ***def*** * From 427d2e58616ed666e50dffd97325b362d86dec49 Mon Sep 17 00:00:00 2001 From: Chinory Date: Wed, 9 Apr 2025 15:47:33 +0800 Subject: [PATCH 15/15] give me some time to design it --- LICENSE | 21 ----- README.md | 254 +----------------------------------------------------- 2 files changed, 3 insertions(+), 272 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 4807483..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Chinory - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index a0fdcff..9000a3f 100644 --- a/README.md +++ b/README.md @@ -12,256 +12,8 @@ A lightning-fast, single-pass command line argument parser with structural valid - **Zero Dependencies**: Pure JavaScript with no external libraries - **Predictable Results**: No type errors or undefined values (all variables are initialized) -## 📦 Installation - -```bash -npm install pargv-lite -``` - -## 🔍 Elegant Usage Example - -```javascript -import parse from 'pargv-lite'; - -// Define your application's command structure -const gitReq = { - // Global options - help: { - def: false, - set: ['-h', '--help'] - }, - verbose: { - def: false, - set: ['-v', '--verbose'] - }, - // Subcommands (direct string array format for exit options) - command: ['clone', 'push', 'pull', 'commit', 'checkout'] -}; - -// Commit subcommand config -const commitReq = { - message: { - def: '', - set: ['-m', '--message'] - }, - all: { - def: false, - set: ['-a', '--all'] - }, - amend: { - def: false, - set: ['--amend'] - } -}; - -function main() { - const gitRes = {}; - - // First parse to handle global options and identify subcommand - const ret = parse(process.argv, 2, gitReq, gitRes, console.error); - - // Handle help flag - if (gitRes.help) { - showHelp(); - return; - } - - // Check if a subcommand was encountered (returns object with next position) - if (typeof ret === 'object') { - // ret contains { avi, key, opt }: - // - ret.avi is the argv index to continue parsing from - // - ret.key is the variable name in gitReq that triggered the exit ('command') - // - ret.opt is the option string that triggered the exit (subcommand name) - - console.error(`Executing ${ret.opt} command...`); - - switch (ret.opt) { - case 'commit': - // Parse commit-specific options starting from ret.avi - const commitRes = {}; - parse(process.argv, ret.avi, commitReq, commitRes, console.error); - - // Use the results - console.error( - `Committing with message: ${commitRes.message}`, - commitRes.all ? '(all files)' : '', - commitRes.amend ? '(amending)' : '' - ); - break; - - case 'push': - // Handle push command... - break; - - // Handle other commands... - } - } -} -``` - -## 🛠️ API - -```javascript -parse(argv, i, req, res, err) -``` - -- **argv**: Array of command line arguments (usually `process.argv`) -- **i**: Starting index for parsing (usually `2`) -- **req**: Configuration object defining your command structure -- **res**: Object that will be populated with parsed results -- **err**: Error handler function, receives `{msg, avi, opt, key, val}` and returns boolean - -### Return Value - -The function returns either: -- A number (the next index after parsing completed normally) -- An object `{ avi, key, opt }` when exiting early due to an exit option, where: - - `avi`: The next index to resume parsing from (**A**rg**V** **I**ndex) - - `key`: The variable name in the req object that triggered the exit - - `opt`: The option string that triggered the exit (e.g., the subcommand name) - -### Configuration Format - -```javascript -{ - // Regular variable with default value and option definitions - variableName: { - def: defaultValue, // Boolean, string, or string[] - set: optionDefinition, // Options that set this variable - rst: optionDefinition // Options that reset this variable to default - }, - - // Exit option (shorthand format) - exits parsing when encountered - exitOptionName: optionDefinition -} -``` - -Option definitions can be: -- A string: `'--option'` -- An array: `['--option', '-o']` -- `null` refers to the variable name. `[null, ...]` is also supported. - -## ⚡ Powerful Features - -### Boolean Options - -```javascript -// Simple flags (no value needed) -verbose: { - def: false, - set: ['-v', '--verbose'] -} -``` - -### String Options - -```javascript -// String option with default value -output: { - def: 'stdout', - set: ['-o', '--output'] -} -``` - -### Array Collection - -```javascript -// Collect multiple values in an array -files: { - def: [], - set: ['-i', '--input'] // -i 1.txt --input 2.txt -i3.txt --input=4.txt -} - -// Special option '--' collects all the anonymous arguments. -allFiles: { - def: [], - set: ['--', '-i'] // -i 1.txt 2.txt -- --this-will-be-collected-too -} -``` - -### Reset Options - -```javascript -// Option to reset value back to default -// For boolean values: -color: { - def: false, - set: ['--color'], // Sets to true (!def) - rst: ['--no-color'] // Resets to false (def) -} - -big: { - def: true, - set: ['--small'], // Sets to false (!def) - rst: ['--big'] // Resets to true (def) -} - -// For strings or arrays, reset restores the original default: -files: { - def: ['default.txt'], - set: ['--files'], // Adds files to array - rst: ['--no-files'] // Resets back to ['default.txt'] -} -``` - -Note: Inverting a boolean value is not supported. - -### Combined Short Options - -```javascript -const res = {}; -parse(['node', 'app.js', '-abc'], 2, { - a: { def: false, set: '-a' }, - b: { def: false, set: '-b' }, - c: { def: false, set: '-c' } -}, res, console.error); -// res = { a: true, b: true, c: true } -``` -```javascript -const res = {}; -parse(['node', 'app.js', '-abcd'], 2, { - a: { def: false, set: '-a' }, - b: { def: [], set: '-b' }, - c: { def: false, set: '-c' }, - d: { def: false, set: '-d' } -}, res, console.error); -// { a: true, b: [ 'cd' ], c: false, d: false } -``` - -## 🔄 Subcommand Handling - -The exit feature enables elegant subcommand handling: - -```javascript -// Main CLI configuration -const mainReq = { - help: { - def: false, - set: ['-h', '--help'] - }, - // Direct array format for exit options - command: ['build', 'serve', 'test'] -}; - -const mainRes = {}; -const ret = parse(process.argv, 2, mainReq, mainRes, console.error); - -if (typeof ret === 'object') { - // When a command is found via the exit mechanism: - // - ret.avi is already positioned after the subcommand - // - ret.key contains the variable name in req ('command' in this case) - // - ret.opt contains the matched option (the subcommand name) - - switch(ret.opt) { - case 'build': - const buildReq = { /* build options */ }; - const buildRes = {}; - parse(process.argv, ret.avi, buildReq, buildRes, console.error); - break; - } -} -``` - ## 📜 License -MIT © Chinory \ No newline at end of file +All rights reserved. + +Give me some time to design it. \ No newline at end of file