Skip to main content
Module

x/sade/deno/mod.js

Smooth (CLI) Operator 🎶
Latest
File
import { parse } from "https://deno.land/std@0.106.0/flags/mod.ts";import * as $ from "./utils.js";
const ALL = "__all__";const DEF = "__default__";
class Sade { constructor(name, isOne) { const [bin, ...rest] = name.split(/\s+/); isOne = isOne || rest.length > 0;
this.bin = bin; this.ver = "0.0.0"; this.default = ""; this.tree = {}; // set internal shapes; this.command(ALL); this.command([DEF].concat(isOne ? rest : "<command>").join(" ")); this.single = isOne; this.curr = ""; // reset }
command(str, desc, opts = {}) { if (this.single) { throw new Error('Disable "single" mode to add commands'); }
// All non-([|<) are commands let cmd = [], usage = []; const rgx = /(\[|<)/; str.split(/\s+/).forEach((x) => { (rgx.test(x.charAt(0)) ? usage : cmd).push(x); });
// Back to string~! cmd = cmd.join(" ");
if (cmd in this.tree) { throw new Error(`Command already exists: ${cmd}`); }
// re-include `cmd` for commands cmd.includes("__") || usage.unshift(cmd); usage = usage.join(" "); // to string
this.curr = cmd; if (opts.default) this.default = cmd;
this.tree[cmd] = { usage, alibi: [], options: [], alias: {}, default: {}, examples: [], }; if (opts.alias) this.alias(opts.alias); if (desc) this.describe(desc);
return this; }
describe(str) { this.tree[this.curr || DEF].describe = Array.isArray(str) ? str : $.sentences(str); return this; }
alias(...names) { if (this.single) throw new Error('Cannot call `alias()` in "single" mode'); if (!this.curr) { throw new Error("Cannot call `alias()` before defining a command"); } const arr = this.tree[this.curr].alibi = this.tree[this.curr].alibi.concat( ...names, ); arr.forEach((key) => this.tree[key] = this.curr); return this; }
option(str, desc, val) { const cmd = this.tree[this.curr || ALL];
let [flag, alias] = $.parse(str); if (alias && alias.length > 1) [flag, alias] = [alias, flag];
str = `--${flag}`; if (alias && alias.length > 0) { str = `-${alias}, ${str}`; const old = cmd.alias[alias]; cmd.alias[alias] = (old || []).concat(flag); }
const arr = [str, desc || ""];
if (val !== void 0) { arr.push(val); cmd.default[flag] = val; } else if (!alias) { cmd.default[flag] = void 0; }
cmd.options.push(arr); return this; }
action(handler) { this.tree[this.curr || DEF].handler = handler; return this; }
example(str) { this.tree[this.curr || DEF].examples.push(str); return this; }
version(str) { this.ver = str; return this; }
parse(arr, opts = {}) { arr = ["", ""].concat(arr); let offset = 2, tmp, idx, isVoid, cmd;
const alias = { h: "help", v: "version" }; const argv = parse(arr.slice(offset), { alias }); const isSingle = this.single;
let bin = this.bin; let name = "";
if (isSingle) { cmd = this.tree[DEF]; } else { // Loop thru possible command(s) let i = 1, xyz; const len = argv._.length + 1;
for (; i < len; i++) { tmp = argv._.slice(0, i).join(" "); xyz = this.tree[tmp]; if (typeof xyz === "string") { idx = (name = xyz).split(" "); arr.splice(arr.indexOf(argv._[0]), i, ...idx); i += (idx.length - i); } else if (xyz) { name = tmp; } else if (name) { break; } }
cmd = this.tree[name]; isVoid = (cmd === void 0);
if (isVoid) { if (this.default) { name = this.default; cmd = this.tree[name]; arr.unshift(name); offset++; } else if (tmp) { return $.error(bin, `Invalid command: ${tmp}`); } //=> else: cmd not specified, wait for now... } }
// show main help if relied on "default" for multi-cmd if (argv.help) return this.help(!isSingle && !isVoid && name); if (argv.version) return this._version();
if (!isSingle && cmd === void 0) { return $.error(bin, "No command specified."); }
const all = this.tree[ALL]; // merge all objects :: params > command > all opts.alias = Object.assign(all.alias, cmd.alias, opts.alias); opts.default = Object.assign(all.default, cmd.default, opts.default);
tmp = name.split(" "); idx = arr.indexOf(tmp[0], 2); if (~idx) arr.splice(idx, tmp.length);
const vals = parse(arr.slice(offset), opts); if (!vals || typeof vals === "string") { return $.error(bin, vals || "Parsed unknown option flag(s)!"); }
const segs = cmd.usage.split(/\s+/); const reqs = segs.filter((x) => x.charAt(0) === "<"); const args = vals._.splice(0, reqs.length);
if (args.length < reqs.length) { if (name) bin += ` ${name}`; // for help text return $.error(bin, "Insufficient arguments!"); }
segs.filter((x) => x.charAt(0) === "[").forEach((_) => { args.push(vals._.shift()); // adds `undefined` per [slot] if no more });
args.push(vals); // flags & co are last const handler = cmd.handler; return opts.lazy ? { args, name, handler } : handler.apply(null, args); }
help(str) { console.log( $.help(this.bin, this.tree, str || DEF, this.single), ); }
_version() { console.log(`${this.bin}, ${this.ver}`); }}
export default (str, isOne) => new Sade(str, isOne);