v1-minimal
This commit is contained in:
parent
f5a64b51ff
commit
5231e7375b
284
barg.js
284
barg.js
@ -1,22 +1,87 @@
|
||||
const ELEMENTS_SEP = ";";
|
||||
const ALIAS_SEP = ",";
|
||||
const ATTR_SEP = ":";
|
||||
function log(text) {
|
||||
console.log(JSON.stringify(text, null, 2));
|
||||
}
|
||||
|
||||
let var_values = {};
|
||||
let required_values = [];
|
||||
function join(...rest) {
|
||||
return rest.filter((v) => !!v).join(".");
|
||||
}
|
||||
|
||||
let schema = {};
|
||||
let alias_map = {};
|
||||
function take_one(list) {
|
||||
return list.shift();
|
||||
}
|
||||
|
||||
let position = 0;
|
||||
let level = 1;
|
||||
let commands = ["root"];
|
||||
function take_all(list) {
|
||||
const all = [...list];
|
||||
list.length = 0;
|
||||
return all;
|
||||
}
|
||||
|
||||
function register_command(raw_entry) {
|
||||
const header = raw_entry.split(ELEMENTS_SEP).slice(1);
|
||||
const aliases = header.shift().split(ALIAS_SEP);
|
||||
function tokenize(input) {
|
||||
const raw_tokens = input.trim().split(/\s+/).slice(1); // skip command
|
||||
const tokens = [];
|
||||
|
||||
while (raw_tokens.length) {
|
||||
const raw_token = take_one(raw_tokens);
|
||||
|
||||
if (raw_token === "--") {
|
||||
tokens.push(`rest::${take_all(raw_tokens).join(" ")}`);
|
||||
break;
|
||||
}
|
||||
|
||||
let key;
|
||||
let value;
|
||||
|
||||
if (raw_token.startsWith("--")) {
|
||||
[key, value] = raw_token.slice(2).split("=");
|
||||
tokens.push(`narg::${key}`);
|
||||
} else if (raw_token.startsWith("-")) {
|
||||
[key, value] = raw_token.slice(1).split("=");
|
||||
tokens.push(...key.split("").map((k) => `narg::${k}`));
|
||||
} else {
|
||||
tokens.push(`unk::${raw_token}`);
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
tokens.push(`value::${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function parse_input(spec, input) {
|
||||
const tokens = tokenize(input);
|
||||
const values = {};
|
||||
|
||||
const schema = {};
|
||||
const alias_map = {};
|
||||
|
||||
let position = 0;
|
||||
let level = 1;
|
||||
let commands = ["root"];
|
||||
|
||||
function ref(...rest) {
|
||||
return commands.slice(0, level).concat(rest).join(".");
|
||||
}
|
||||
|
||||
function get_entry_ref(key) {
|
||||
return alias_map[ref(key)];
|
||||
}
|
||||
|
||||
function get_entry_item(...keys) {
|
||||
return schema[join(...keys)];
|
||||
}
|
||||
|
||||
function generate_schema() {
|
||||
for (const entry of spec) {
|
||||
const elements = entry.split(";");
|
||||
const type = elements.shift();
|
||||
|
||||
switch (type) {
|
||||
case "command": {
|
||||
const aliases = elements.shift().split(",");
|
||||
const name = aliases[0];
|
||||
const help = header.shift();
|
||||
const help = elements.shift();
|
||||
|
||||
// Schema
|
||||
schema[ref(name, "type")] = "command";
|
||||
@ -31,23 +96,20 @@ function register_command(raw_entry) {
|
||||
commands.push(name);
|
||||
level += 1;
|
||||
position = 0;
|
||||
}
|
||||
|
||||
function end_command() {
|
||||
break;
|
||||
}
|
||||
case "end": {
|
||||
level -= 1;
|
||||
commands.pop();
|
||||
}
|
||||
|
||||
function register_argument(raw_entry) {
|
||||
const elements = raw_entry.split(ELEMENTS_SEP).slice(1);
|
||||
let aliases = elements.shift().split(ALIAS_SEP);
|
||||
break;
|
||||
}
|
||||
case "argument": {
|
||||
let aliases = elements.shift().split(",");
|
||||
let name = aliases[0];
|
||||
const entry = {
|
||||
required: false,
|
||||
kind: "positional",
|
||||
};
|
||||
|
||||
const entry = { required: false, kind: "positional" };
|
||||
for (const element of elements) {
|
||||
const [attribute, attr_value] = element.split(ATTR_SEP);
|
||||
const [attribute, attr_value] = element.split(":");
|
||||
|
||||
if (attribute === "required") entry.required = true;
|
||||
else if (attribute === "repeatable") entry.repeatable = true;
|
||||
@ -70,9 +132,11 @@ function register_argument(raw_entry) {
|
||||
}
|
||||
|
||||
for (const alias of aliases) {
|
||||
if (entry.kind === "option" || entry.kind === "flag")
|
||||
if (entry.kind === "option" || entry.kind === "flag") {
|
||||
alias_map[ref(`narg::${alias}`)] = ref(name);
|
||||
else alias_map[ref(alias)] = ref(name);
|
||||
} else {
|
||||
alias_map[ref(alias)] = ref(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Schema
|
||||
@ -80,103 +144,70 @@ function register_argument(raw_entry) {
|
||||
for (const [key, value] of Object.entries(entry)) {
|
||||
schema[ref(name, key)] = value;
|
||||
}
|
||||
}
|
||||
|
||||
function generate_schema(spec) {
|
||||
for (const entry of spec) {
|
||||
const elements = entry.split(ELEMENTS_SEP);
|
||||
const type = elements.shift();
|
||||
|
||||
switch (type) {
|
||||
case "command":
|
||||
register_command(entry);
|
||||
break;
|
||||
case "end":
|
||||
end_command();
|
||||
break;
|
||||
case "argument":
|
||||
register_argument(entry);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Invalid entry type: '${type}'`);
|
||||
}
|
||||
}
|
||||
|
||||
position = 0;
|
||||
level = 1;
|
||||
commands = ["root"];
|
||||
}
|
||||
|
||||
function parse_input(spec, input) {
|
||||
// rest
|
||||
var_values = {};
|
||||
required_values = [];
|
||||
position = 0;
|
||||
level = 1;
|
||||
commands = ["root"];
|
||||
schema = {};
|
||||
alias_map = {};
|
||||
|
||||
// code
|
||||
generate_schema(spec);
|
||||
const tokens = tokenize(input);
|
||||
|
||||
// log({ schema, alias_map });
|
||||
|
||||
function next_token() {
|
||||
const tagged_token = tokens.shift();
|
||||
const [type, token] = tagged_token.split("::");
|
||||
return { original: tagged_token, type, token };
|
||||
position = 0;
|
||||
}
|
||||
|
||||
function next_value() {
|
||||
const tagged_token = next_token();
|
||||
if (tagged_token.type !== "unk") {
|
||||
throw new Error(`Expected option value, but got: ${tagged_token.token}`);
|
||||
function next_token_value() {
|
||||
const token = take_one(tokens);
|
||||
const [token_type, token_key] = token.split("::");
|
||||
if (token_type !== "unk") {
|
||||
throw new Error(`Expected option value, but got: ${token_type}. Token: '${token_key}'`);
|
||||
}
|
||||
return tagged_token.token;
|
||||
return token_key;
|
||||
}
|
||||
|
||||
generate_schema();
|
||||
|
||||
while (tokens.length > 0) {
|
||||
const {
|
||||
original: token,
|
||||
type: token_type,
|
||||
token: token_value,
|
||||
} = next_token();
|
||||
const token = take_one(tokens);
|
||||
const [token_type, token_key] = token.split("::");
|
||||
if (token_type === "rest") {
|
||||
var_values["__rest"] = token_value;
|
||||
values["__rest"] = token_key;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bubble up arguments -> Loop over all command levels starting by the latest
|
||||
let found = false;
|
||||
for (let i = commands.length; i >= 1; i--) {
|
||||
level = i;
|
||||
let entry_ref = alias_map[ref(token)];
|
||||
for (level = commands.length; level >= 1; level--) {
|
||||
let entry_ref = get_entry_ref(token);
|
||||
|
||||
// Positional arguments are valid only for the latest command
|
||||
if (!entry_ref && level === commands.length && token_type === "unk") {
|
||||
entry_ref = alias_map[ref(`pos::${position++}`)];
|
||||
entry_ref = get_entry_ref(`pos::${position}`);
|
||||
if (entry_ref) {
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Try with parent command
|
||||
if (!entry_ref) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [_token_type, token_value] = token.split("::");
|
||||
const type = schema[join(entry_ref, "type")];
|
||||
if (type === "command") {
|
||||
commands.push(token_value);
|
||||
level += commands.length;
|
||||
} else if (type === "argument") {
|
||||
const kind = schema[join(entry_ref, "kind")];
|
||||
const dest = schema[join(entry_ref, "dest")];
|
||||
const value = schema[join(entry_ref, "value")];
|
||||
const entry_type = get_entry_item(entry_ref, "type");
|
||||
if (entry_type === "command") {
|
||||
commands.push(token_key);
|
||||
} else if (entry_type === "argument") {
|
||||
const kind = get_entry_item(entry_ref, "kind");
|
||||
const dest = get_entry_item(entry_ref, "dest");
|
||||
const value = get_entry_item(entry_ref, "value");
|
||||
|
||||
var_values[dest] = value; // default one?
|
||||
values[dest] = value; // default one
|
||||
|
||||
if (kind === "flag") var_values[dest] = true;
|
||||
else if (kind === "option") var_values[dest] = next_value();
|
||||
else if (kind === "positional") var_values[dest] = token_value;
|
||||
else if (kind === "rest") var_values[dest] = token_value;
|
||||
if (kind === "flag") values[dest] = true;
|
||||
else if (kind === "option") values[dest] = next_token_value();
|
||||
else if (kind === "positional") values[dest] = token_key;
|
||||
else if (kind === "rest") values[dest] = token_key;
|
||||
else throw new Error(`Invalid attribute type: '${kind}'`);
|
||||
}
|
||||
|
||||
found = true;
|
||||
@ -184,67 +215,14 @@ function parse_input(spec, input) {
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
console.log(`(Skipping invalid argument: '${token_value}'`);
|
||||
console.log(`(Skipping invalid argument: '${token_key}'`);
|
||||
}
|
||||
}
|
||||
|
||||
log({ commands, var_values });
|
||||
return var_values;
|
||||
log({ commands, values });
|
||||
return values;
|
||||
}
|
||||
|
||||
function tokenize(input) {
|
||||
const raw_tokens = input.split(/\s+/);
|
||||
raw_tokens.shift(); // drop command
|
||||
|
||||
const tokens = [];
|
||||
while (raw_tokens.length > 0) {
|
||||
let token = take_token(raw_tokens);
|
||||
let value;
|
||||
|
||||
if (token === "--") {
|
||||
tokens.push(`rest::${take_all(raw_tokens).join(" ")}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.startsWith("--")) {
|
||||
[token, value] = token.slice(2).split("=");
|
||||
tokens.push(`narg::${token}`);
|
||||
} else if (token.startsWith("-")) {
|
||||
[token, value] = token.slice(1).split("=");
|
||||
tokens.push(...token.split("").map((t) => `narg::${t}`));
|
||||
} else {
|
||||
tokens.push(`unk::${token}`);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
tokens.push(`value::${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function take_token(tokens) {
|
||||
return tokens.shift();
|
||||
}
|
||||
|
||||
function take_all(tokens) {
|
||||
const all = [...tokens];
|
||||
tokens.length = 0;
|
||||
return all;
|
||||
}
|
||||
|
||||
function ref(...rest) {
|
||||
return commands.slice(0, level).concat(rest).join(".");
|
||||
}
|
||||
|
||||
function join(...rest) {
|
||||
return rest.filter((v) => !!v).join(".");
|
||||
}
|
||||
|
||||
function log(text) {
|
||||
console.log(JSON.stringify(text, null, 2));
|
||||
}
|
||||
const SPEC = [
|
||||
"command;build;Build a dev container",
|
||||
"argument;from;type:option;map:from_name;help:Name of the base container",
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
{
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"prettier": {
|
||||
"printWidth": 100
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user