diff --git a/parser.js b/parser.js index 96368c1..ad2babd 100644 --- a/parser.js +++ b/parser.js @@ -29,6 +29,7 @@ const ATTR_TYPE = { POSITIONAL: "positional", OPTION: "option", FLAG: "flag", + REST: "rest", }; const CONTEXT = { POS_COUNT: "pos_count", @@ -98,6 +99,11 @@ function parse_spec_entry(raw_entry, context) { }, })[attribute](attribute_value); }); + + // Set default values + if (!attributes[ATTR.MAP]) { + attributes[ATTR.MAP] = entry.aliases[0]; // alias 0 is always the larger format + } return attributes; }, }[entry.type](); @@ -126,12 +132,11 @@ function parse_spec(spec, command = null) { const context = { ...ctx_defaults, reset() { - Object.assing(this, defaults); + Object.assign(this, ctx_defaults); }, }; for (const raw_entry of spec) { const entry = parse_spec_entry(raw_entry, context); - console.log(entry.type); const exit = { [ENTRY.COMMAND]: () => { // Previous command was populated @@ -164,26 +169,90 @@ function parse_spec(spec, command = null) { spec_data[`${cmd_id}.${entry.id}`] = entry; entry.aliases.forEach((alias) => { if (entry.attr.type === ATTR_TYPE.POSITIONAL) { - spec_aliases[`${cmd_id}.${entry.attr[ATTR.POSITION]}`] = entry.id; - } else { - spec_aliases[`${cmd_id}.${alias}`] = entry.id; + const alias_id = `${cmd_id}.${entry.attr[ATTR.POSITION]}`; + spec_aliases[alias_id] = `${cmd_id}.${entry.id}`; + } else if ( + entry.attr.type === ATTR_TYPE.OPTION || + entry.attr.type === ATTR_TYPE.FLAG + ) { + const alias_id = `${cmd_id}.${alias}`; + spec_aliases[alias_id] = `${cmd_id}.${entry.id}`; + } else if (entry.attr.type === ATTR_TYPE.REST) { + const alias_id = `${cmd_id}._rest`; + spec_aliases[alias_id] = `${cmd_id}.${entry.id}`; } }); }, }[entry.type](); + context.reset(); if (exit) break; } - return { spec_data, spec_aliases }; + if (command && cmd_id === null) { + throw new Error(`Invalid command: ${command}`); + } + + return { + data: spec_data, + aliases: spec_aliases, + cmd_id: command ? cmd_id : null, + }; } -const r = parse_spec(SPEC, "build"); -console.log(JSON.stringify(r, null, 2)); +const r = parse_spec(SPEC); +// console.log(JSON.stringify(r, null, 2)); /** - * INPUT: build -i tm0:node mybox - * - * spec_aliases[build] -> "0" - * -i -> option/flag short -> spec_aliases["0.i"] -> "1" - * + * @return {:} */ +const inspect = (v) => console.log(JSON.stringify(v, null, 2)); +function parse_args(spec, args_raw) { + const vars = {}; + const args = args_raw.split(" "); + args.shift(); // script name + const cmd = args.shift(); + + const get_next = () => args[0]; + const consume = () => args.shift(); + const consume_all = () => args.splice(0); + + const cmd_spec = parse_spec(spec, cmd); + const cmd_info = cmd_spec.data[cmd_spec.cmd_id]; + let positional_count = 0; + while (args.length > 0) { + const next = get_next(); + const [type, next_arg] = (() => { + if (next === "--") { + return [ATTR_TYPE.REST, "_rest"]; // check if spec if expecing a rest arg + } else if (next.startsWith("--")) { + return [ATTR_TYPE.OPTION, next.slice(2)]; // invalid type. use only to know if to search per key or whole string + } else if (next.startsWith("-")) { + return [ATTR_TYPE.FLAG, next.slice(1)]; // same + } else { + return [ATTR_TYPE.POSITIONAL, positional_count++]; + } + })(); + const alias_id = `${cmd_info.id}.${next_arg}`; + const next_arg_id = cmd_spec.aliases[alias_id]; + if (typeof next_arg_id !== "undefined") { + consume(); + const next_arg_info = cmd_spec.data[next_arg_id]; + if (next_arg_info.attr.type === ATTR_TYPE.OPTION) { + vars[next_arg_info.attr.map] = consume(); + } else if (next_arg_info.attr.type === ATTR_TYPE.FLAG) { + vars[next_arg_info.attr.map] = true; + } else if (next_arg_info.attr.type === ATTR_TYPE.REST) { + vars[next_arg_info.attr.map] = consume_all(); + } else if (next_arg_info.attr.type === ATTR_TYPE.POSITIONAL) { + vars[next_arg_info.attr.map] = next; + } + } else { + inspect(vars); + throw new Error(`Invalid argument: '${next_arg}'`); + } + } + return vars; +} + +const args = "dev build -q -v --image tm0:node mybox -v -- bla1 bla2"; +inspect(parse_args(SPEC, args));