@king011/flags
Version:
deno and nodeje command args parser
991 lines (990 loc) • 26.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = exports.FlagBooleans = exports.FlagBoolean = exports.FlagBigints = exports.FlagBigint = exports.FlagNumbers = exports.FlagNumber = exports.FlagStrings = exports.FlagString = exports.FlagBase = exports.Flags = exports.Command = exports.FlagsException = void 0;
class FlagsException extends Error {
constructor(message) {
super(message);
}
}
exports.FlagsException = FlagsException;
const minpad = 8;
const matchUse = new RegExp(/^[a-zA-Z][a-zA-Z0-9\-_\.]*$/u);
const matchFlagShort = new RegExp(/^[a-zA-Z0-9]$/u);
function isShortFlag(v) {
if (v.length != 1) {
return false;
}
const c = v.codePointAt(0);
return c !== undefined && c < 128 && c > 0;
}
function compareString(l, r) {
if (l == r) {
return 0;
}
return l < r ? -1 : 1;
}
/**
* Represents a command or subcommand
*/
class Command {
constructor(opts) {
this.opts = opts;
/**
* parameters not matched
*/
this.args = new Array();
if (!matchUse.test(opts.use)) {
throw new FlagsException(`use invalid: ${opts.use}`);
}
const short = opts.short;
if (short !== undefined && short.indexOf("\n") != -1) {
throw new FlagsException(`short invalid: ${short}`);
}
this.flags_ = Flags.make(this);
}
/**
* Add subcommands to commands
* @param cmds
*/
add(...cmds) {
if (cmds.length == 0) {
return;
}
let children = this.children_;
if (!children) {
children = new Map();
this.children_ = children;
}
for (const cmd of cmds) {
if (cmd.parent_) {
throw new FlagsException(`command "${cmd.opts.use}" already added to "${cmd.parent_.flags().use}"`);
}
const opts = cmd.opts;
if (opts.prepare) {
const run = opts.prepare(cmd.flags(), cmd);
if (run) {
opts.run = run;
}
}
const key = opts.use;
if (children.has(key)) {
throw new FlagsException(`command "${key}" already exists`);
}
else {
cmd.parent_ = this;
cmd.flags();
children.set(key, cmd);
}
}
}
/**
* Returns the {@link Flags} associated with the command
*/
flags() {
return this.flags_;
}
/**
* @internal
*/
parse(args, opts) {
if (opts === undefined) {
opts = {};
}
this._parse(args, 0, args.length, opts);
}
_parse(args, start, end, opts) {
// 重置解析狀態,以免多次解析緩存了上次的解析結果
this.args.splice(0);
const flags = this.flags();
flags.reset();
if (end - start < 1) {
const run = this.opts.run;
if (run) {
run(this.args, this);
}
return;
}
// 解析子命令和參數
const children = this.children_;
for (let i = start; i < end; i++) {
const arg = args[i];
if (arg == "-" || arg == "--") {
if (opts.unknowFlags) {
continue;
}
throw new FlagsException(`unknown flag in ${flags.use}: ${arg}`);
}
if (arg.startsWith("-")) { // 解析參數
// 解析內置特定參數
if (arg == "-h") {
this._print();
return;
}
// 解析用戶定義參數
const val = i + 1 < end ? args[i + 1] : undefined;
if (arg.startsWith("--")) {
// 解析長參數
if (arg == "--help") {
const h = this._parseHelp(flags, "--help", val);
if (h == -1) {
this._print();
return;
}
i += h;
continue;
}
i += this._parseLong(flags, arg.substring(2), val, opts);
}
else {
// 解析短參數
if (arg == "-h") {
const h = this._parseHelp(flags, "-h", val);
if (h == -1) {
this._print();
return;
}
i += h;
continue;
}
const h = this._parseShort(flags, arg.substring(1), val, opts);
if (h == -1) { //help
this._print();
return;
}
i += h;
}
}
else if (children) { // 解析子命令
const sub = children.get(arg);
if (sub) {
sub._parse(args, i + 1, end, opts);
return;
}
else {
if (opts.unknowCommand) {
return;
}
throw new FlagsException(`unknow commnad <${arg}>`);
}
}
else { // 沒有子命令才允許傳入 args
this.args.push(arg);
}
}
const run = this.opts.run;
if (run) {
run(this.args, this);
}
}
_throw(flags, flag, arg, val) {
if (val === undefined && !flag.isBool()) {
throw new FlagsException(`flag in ${flags.use} needs an argument: ${arg}`);
}
if (val === undefined) {
val = "";
}
else {
val = ` ${val}`;
}
throw new FlagsException(`invalid flag value in ${flags.use}: ${arg}${val}`);
}
_parseHelp(flags, arg, val) {
if (val === undefined || val === "true") {
return -1;
}
else if (val === "false") {
return 1;
}
if (val === undefined) {
throw new FlagsException(`flag in ${flags.use} needs an argument: ${arg}`);
}
if (val === undefined) {
val = "";
}
else {
val = ` ${val}`;
}
throw new FlagsException(`invalid flag value in ${flags.use}: ${arg}${val}`);
}
_parseShortOne(flags, arg, val, opts) {
if (arg == "h") {
return this._parseHelp(flags, `-${arg}`, val);
}
const flag = flags.find(arg, true);
if (!flag) {
if (opts.unknowFlags) {
return 1;
}
throw new FlagsException(`unknown flag in ${flags.use}: -${arg}`);
}
if (flag.isBool()) {
if (val !== "false" && val !== "true") {
val = undefined;
}
}
if (flag.add(val)) {
return val === undefined ? 0 : 1;
}
this._throw(flags, flag, `-${arg}`, val);
}
_parseShort2(flags, arg, val, opts) {
if (arg == "h") {
const v = this._parseHelp(flags, "-h", val);
return v == -1 ? v : 0;
}
const flag = flags.find(arg, true);
if (!flag) {
if (opts.unknowFlags) {
return 0;
}
throw new FlagsException(`unknown flag in ${flags.use}: -${arg}`);
}
if (flag.add(val)) {
return 0;
}
this._throw(flags, flag, `-${arg}`, val);
}
_parseShort(flags, arg, nextVal, opts) {
switch (arg.length) {
case 0:
if (opts.unknowFlags) {
return 0;
}
throw new FlagsException(`unknown flag in ${flags.use}: -${arg}`);
case 1:
return this._parseShortOne(flags, arg, nextVal, opts);
}
if (arg[1] == "=") {
return this._parseShort2(flags, arg[0], arg.substring(2), opts);
}
const name = arg[0];
const flag = flags.find(name, true);
if (!flag) {
if (opts.unknowFlags) {
return 0;
}
throw new FlagsException(`unknown flag in ${flags.use}: -${name}`);
}
else if (!flag.isBool()) {
return this._parseShort2(flags, arg[0], arg.substring(1), opts);
}
if (flag.add(undefined)) {
return this._parseShort(flags, arg.substring(1), nextVal, opts);
}
throw new FlagsException(`invalid flag value in ${flags.use}: ${name}`);
}
_parseLong(flags, arg, val, opts) {
const found = arg.indexOf("=");
let name;
let next = false;
if (found == -1) {
name = arg;
next = true;
}
else {
name = arg.substring(0, found);
val = arg.substring(found + 1);
}
const flag = flags.find(name);
if (!flag) {
if (opts.unknowFlags) {
return next ? 1 : 0;
}
throw new FlagsException(`unknown flag in ${flags.use}: --${name}`);
}
if (next && flag.isBool()) {
if (val !== "false" && val !== "true") {
next = false;
val = undefined;
}
}
if (flag.add(val)) {
return next ? 1 : 0;
}
this._throw(flags, flag, `--${name}`, val);
}
_print() {
console.log(this.toString());
}
/**
* Get the description string of command usage
*/
toString() {
var _a, _b, _c;
const opts = this.opts;
const use = this.flags().use;
const strs = new Array();
const long = (_a = opts.long) !== null && _a !== void 0 ? _a : "";
const short = (_b = opts.short) !== null && _b !== void 0 ? _b : "";
if (long == "") {
if (short != "") {
strs.push(short);
}
}
else {
strs.push(long);
}
if (strs.length == 0) {
strs.push("Usage:");
}
else {
strs.push("\nUsage:");
}
strs.push(` ${use} [flags]`);
const children = this.children_;
if (children) {
strs.push(` ${use} [command]
Available Commands:`);
const arrs = new Array();
let pad = 0;
for (const v of children.values()) {
const len = (_c = v.opts.use.length) !== null && _c !== void 0 ? _c : 0;
if (len > pad) {
pad = len;
}
arrs.push(v);
}
pad += 3;
if (pad < minpad) {
pad = minpad;
}
arrs.sort((l, r) => compareString(l.opts.use, r.opts.use));
for (const child of arrs) {
const opts = child.opts;
strs.push(` ${opts.use.padEnd(pad)}${opts.short}`);
}
}
const flags = this.flags();
let sp = 1;
let lp = 4;
for (const f of flags) {
if (sp < f.short.length) {
sp = f.short.length;
}
if (lp < f.name.length) {
lp = f.name.length;
}
}
if (lp < minpad) {
lp = minpad;
}
strs.push(`\nFlags:
-${"h".padEnd(sp)}, --${"help".padEnd(lp)} help for ${opts.use}`);
for (const f of flags) {
let s = "";
let str = f.defaultString();
if (str != "") {
s += " " + str;
}
str = f.valuesString();
if (str != "") {
s += " " + str;
}
if (f.short == "") {
strs.push(` ${"".padEnd(sp)} --${f.name.toString().padEnd(lp)} ${f.usage}${s}`);
}
else {
strs.push(` -${f.short.toString().padEnd(sp)}, --${f.name.toString().padEnd(lp)} ${f.usage}${s}`);
}
}
if (children) {
strs.push(`\nUse "${use} [command] --help" for more information about a command.`);
}
return strs.join("\n");
}
/**
* Use console.log output usage
*/
print() {
console.log(this.toString());
}
/**
* return parent command
*/
parent() {
return this.parent_;
}
}
exports.Command = Command;
/**
* Flags definition and parse
*/
class Flags {
/**
* @internal
*/
static make(cmd) {
return new Flags(cmd);
}
constructor(cmd) {
this.cmd = cmd;
}
/**
* @internal
*/
get use() {
const cmd = this.cmd;
let parent = cmd.parent();
let use = cmd.opts.use;
while (parent) {
use = `${parent.opts.use} ${use}`;
parent = parent.parent();
}
return use;
}
/**
* @internal
*/
find(name, short = false) {
var _a, _b;
return short ? (_a = this.short_) === null || _a === void 0 ? void 0 : _a.get(name) : (_b = this.long_) === null || _b === void 0 ? void 0 : _b.get(name);
}
_getArrs() {
const keys = this.long_;
if (!keys) {
return;
}
let arrs = this.arrs_;
if (!arrs || arrs.length != keys.size) {
arrs = [];
for (const f of keys.values()) {
arrs.push(f);
}
arrs.sort((l, r) => compareString(l.name, r.name));
}
return arrs;
}
/**
* @internal
*/
iterator() {
const arrs = this._getArrs();
let i = 0;
return {
next() {
if (arrs && i < arrs.length) {
return { value: arrs[i++] };
}
return { done: true };
},
};
}
/**
* @internal
*/
[Symbol.iterator]() {
return this.iterator();
}
/**
* @internal
*/
reset() {
var _a;
(_a = this.long_) === null || _a === void 0 ? void 0 : _a.forEach((f) => {
f.reset();
});
}
/**
* Define flags
*/
add(...flags) {
if (flags.length == 0) {
return;
}
let kl = this.long_;
if (!kl) {
kl = new Map();
this.long_ = kl;
}
let ks = this.short_;
if (!ks) {
ks = new Map();
this.short_ = ks;
}
for (const f of flags) {
const name = f.name;
if (kl.has(name)) {
throw new FlagsException(`${this.use} flag redefined: ${name}`);
}
const short = f.short;
if (short !== "") {
const found = ks.get(short);
if (found) {
throw new FlagsException(`unable to redefine '${short}' shorthand in "${this.use}" flagset: it's already used for "${found.name}" flag`);
}
if (!isShortFlag(short)) {
throw new FlagsException(`"${short}" shorthand in "${this.use} is more than one ASCII character`);
}
ks.set(short, f);
}
kl.set(name, f);
}
}
/**
* Define a flag of type string
*/
string(opts) {
const f = new FlagString(opts);
this.add(f);
return f;
}
/**
* Define a flag of type Array<string>
*/
strings(opts) {
const f = new FlagStrings(opts);
this.add(f);
return f;
}
/**
* Define a flag of type number
*/
number(opts) {
const f = new FlagNumber(opts);
this.add(f);
return f;
}
/**
* Define a flag of type Array<number>
*/
numbers(opts) {
const f = new FlagNumbers(opts);
this.add(f);
return f;
}
/**
* Define a flag of type bigint
*/
bigint(opts) {
const f = new FlagBigint(opts);
this.add(f);
return f;
}
/**
* Define a flag of type Array<bigint>
*/
bigints(opts) {
const f = new FlagBigints(opts);
this.add(f);
return f;
}
/**
* Define a flag of type boolean
*/
bool(opts) {
const f = new FlagBoolean(opts);
this.add(f);
return f;
}
/**
* Define a flag of type Array<boolean>
*/
bools(opts) {
const f = new FlagBooleans(opts);
this.add(f);
return f;
}
}
exports.Flags = Flags;
/**
* A base class provides some common methods for the class flag
*/
class FlagBase {
get value() {
return this.value_;
}
constructor(opts) {
this.opts = opts;
if (opts.short !== undefined &&
opts.short !== "" &&
!matchFlagShort.test(opts.short)) {
throw new FlagsException(`"${opts.short}" shorthand should match "^[a-zA-Z0-9]$"`);
}
if (!matchUse.test(opts.name)) {
throw new FlagsException(`"${opts.name}" flag should match "^[a-zA-Z][a-zA-Z0-9\\-_\\.]*$"`);
}
if (opts.usage !== undefined && opts.usage.indexOf("\n") != -1) {
throw new FlagsException(`flag usage invalid: ${opts.usage}`);
}
if (Array.isArray(opts.default)) {
const a = Array.from(opts.default);
this.value_ = a;
}
else {
this.value_ = opts.default;
}
}
get short() {
var _a;
return (_a = this.opts.short) !== null && _a !== void 0 ? _a : "";
}
get name() {
return this.opts.name;
}
get default() {
return this.opts.default;
}
get usage() {
var _a;
return (_a = this.opts.usage) !== null && _a !== void 0 ? _a : "";
}
get values() {
return this.opts.values;
}
isValid(v) {
if (typeof v === "number") {
if (!isFinite(v)) {
return false;
}
}
if (Array.isArray(v)) {
for (const i of v) {
if (!isFinite(i)) {
return false;
}
}
}
const opts = this.opts;
const values = opts.values;
if (values && values.length != 0) {
for (const val of values) {
if (this._equal(v, val)) {
return true;
}
}
const f = opts.isValid;
if (f) {
return f(v);
}
return false;
}
const f = opts.isValid;
if (f) {
return f(v);
}
return true;
}
_equal(l, r) {
if (Array.isArray(l) && Array.isArray(r)) {
if (l.length != r.length) {
return false;
}
for (let i = 0; i < l.length; i++) {
if (l[i] !== r[i]) {
return false;
}
}
}
return l === r;
}
reset() {
const def = this.opts.default;
if (Array.isArray(def)) {
const arrs = this.value_;
if (Array.isArray(arrs)) {
arrs.splice(0);
arrs.push(...def);
return;
}
}
this.value_ = this.opts.default;
}
defaultString() {
const val = this.opts.default;
if (Array.isArray(val)) {
if (val.length != 0) {
return `(default ${JSON.stringify(val)})`;
}
}
else if (typeof val === "string") {
if (val != "") {
return (`(default ${JSON.stringify(val)})`);
}
}
else if (typeof val === "boolean") {
if (val) {
return (`(default ${val})`);
}
}
else if (typeof val === "number") {
if (val != 0) {
return (`(default ${val})`);
}
}
else if (typeof val === "bigint") {
if (val != BigInt(0)) {
return (`(default ${val})`);
}
}
return "";
}
valuesString() {
const vals = this.opts.values;
if (vals && vals.length != 0) {
return `(values ${JSON.stringify(vals)})`;
}
return "";
}
add(_) {
return false;
}
isBool() {
return false;
}
}
exports.FlagBase = FlagBase;
function formatFlagOptions(opts, def) {
if (opts.default !== undefined) {
return opts;
}
return {
name: opts.name,
default: def,
short: opts.short,
usage: opts.usage,
values: opts.values,
isValid: opts.isValid,
};
}
/**
* A flag of type string
*/
class FlagString extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, ""));
}
/**
* @override
*/
add(v) {
if (v === undefined || !this.isValid(v)) {
return false;
}
this.value_ = v;
return true;
}
}
exports.FlagString = FlagString;
/**
* A flag of type Array<string>
*/
class FlagStrings extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, []));
}
/**
* @override
*/
add(v) {
if (v === undefined || !this.isValid([v])) {
return false;
}
this.value_.push(v);
return true;
}
}
exports.FlagStrings = FlagStrings;
/**
* A flag of type number
*/
class FlagNumber extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, 0));
}
/**
* @override
*/
add(v) {
if (v === undefined) {
return false;
}
const i = parseInt(v);
if (!this.isValid(i)) {
return false;
}
this.value_ = i;
return true;
}
}
exports.FlagNumber = FlagNumber;
/**
* A flag of type Array<number>
*/
class FlagNumbers extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, []));
}
/**
* @override
*/
add(v) {
if (v === undefined) {
return false;
}
const i = parseInt(v);
if (!this.isValid([i])) {
return false;
}
this.value_.push(i);
return true;
}
}
exports.FlagNumbers = FlagNumbers;
/**
* A flag of type bigint
*/
class FlagBigint extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, BigInt(0)));
}
/**
* @override
*/
add(v) {
if (v === undefined) {
return false;
}
try {
const i = BigInt(v);
if (!this.isValid(i)) {
return false;
}
this.value_ = i;
return true;
}
catch (_) {
return false;
}
}
}
exports.FlagBigint = FlagBigint;
/**
* A flag of type Array<bigint>
*/
class FlagBigints extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, []));
}
/**
* @override
*/
add(v) {
if (v === undefined) {
return false;
}
try {
const i = BigInt(v);
if (!this.isValid([i])) {
return false;
}
this.value_.push(i);
return true;
}
catch (_) {
return false;
}
}
}
exports.FlagBigints = FlagBigints;
function parseBool(v) {
if (v === undefined) {
return true;
}
else if (v === "true") {
return true;
}
else if (v === "false") {
return false;
}
return undefined;
}
/**
* A flag of type boolean
*/
class FlagBoolean extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, false));
}
/**
* @override
*/
isBool() {
return true;
}
/**
* @override
*/
add(v) {
const val = parseBool(v);
if (val === undefined || !this.isValid(val)) {
return false;
}
this.value_ = val;
return true;
}
}
exports.FlagBoolean = FlagBoolean;
/**
* A flag of type Array<boolean>
*/
class FlagBooleans extends FlagBase {
constructor(opts) {
super(formatFlagOptions(opts, []));
}
/**
* @override
*/
isBool() {
return true;
}
/**
* @override
*/
add(v) {
const val = parseBool(v);
if (val === undefined || !this.isValid([val])) {
return false;
}
this.value_.push(val);
return true;
}
}
exports.FlagBooleans = FlagBooleans;
/**
* command parser
*/
class Parser {
constructor(root) {
this.root = root;
const opts = root.opts;
const prepare = opts.prepare;
if (prepare) {
const run = prepare(root.flags(), root);
if (run) {
opts.run = run;
}
}
}
/**
* Parses command line arguments and invokes a handler callback for a matching command or its subcommands
* @param args command line parameters
* @param opts some optional behavior definitions
*
* @throws {@link FlagsException}
*
* @example deno
* ```
* new Parser(root).parse(Deno.args)
* ```
*
* @example nodejs
* ```
* new Parser(root).parse(process.argv.splice(2))
* ```
*/
parse(args, opts) {
this.root.parse(args, opts);
}
}
exports.Parser = Parser;
//# sourceMappingURL=mod.js.map