kopia lustrzana https://gitlab.com/rysiekpl/libresilient
261 wiersze
7.1 KiB
JavaScript
Executable File
261 wiersze
7.1 KiB
JavaScript
Executable File
#!/usr/bin/env -S deno run --allow-read --allow-write
|
|
|
|
// TODO: handle the permissions better... somehow?
|
|
|
|
import { parse } from "https://deno.land/std/flags/mod.ts";
|
|
|
|
let getUsage = () => {
|
|
let usage = `
|
|
Command-line interface for LibResilient.
|
|
|
|
This script creates a common interface to CLI actions implemented by LibResilient plugins.
|
|
|
|
Usage:
|
|
${new URL('', import.meta.url).toString().split('/').at(-1)} [options] [plugin-name [plugin-options]]
|
|
|
|
Options:
|
|
|
|
-h, --help [plugin-name]
|
|
Print this message, if no plugin-name is given.
|
|
If plugin-name is provided, print usage information of that plugin.
|
|
|
|
`
|
|
|
|
return usage
|
|
}
|
|
|
|
|
|
let getPluginActionUsage = (action, action_name) => {
|
|
|
|
// initialize
|
|
let options = ""
|
|
let positional = ""
|
|
let positional_desc = ""
|
|
|
|
if ("arguments" in action) {
|
|
for (const opt in action.arguments) {
|
|
if (opt == '_') {
|
|
continue
|
|
}
|
|
options += `\n --${opt}`
|
|
if ("default" in action.arguments[opt]) {
|
|
options += ` (default: ${action.arguments[opt].default})`
|
|
}
|
|
options += `\n ${action.arguments[opt].description}`
|
|
options += `\n`
|
|
}
|
|
|
|
if ('_' in action.arguments) {
|
|
if ('name' in action.arguments._) {
|
|
positional = ` <${action.arguments._.name}...>`
|
|
} else {
|
|
positional = ' <item...>'
|
|
}
|
|
positional_desc = `
|
|
|
|
${positional}
|
|
${action.arguments._.description}`
|
|
}
|
|
}
|
|
|
|
let usage = ` ${action_name}${ (options != "") ? " [options...]" : "" }${positional}
|
|
${action.description}${positional_desc}
|
|
${options}`
|
|
|
|
return usage
|
|
}
|
|
|
|
|
|
let getPluginUsage = (plugin) => {
|
|
let usage = `
|
|
CLI plugin:
|
|
${plugin.name}
|
|
|
|
Plugin Description:
|
|
${plugin.description.replace('\n', '\n ')}
|
|
|
|
Usage:
|
|
${new URL('', import.meta.url).toString().split('/').at(-1)} [general-options] ${plugin.name} [plugin-action [action-options]]
|
|
|
|
General Options:
|
|
|
|
-h, --help [plugin-name]
|
|
Print this message, if no plugin-name is given.
|
|
If plugin-name is provided, print usage information of that plugin.
|
|
|
|
Actions and Action Options:
|
|
|
|
`
|
|
for (const action in plugin.actions) {
|
|
usage += getPluginActionUsage(plugin.actions[action], action) + '\n'
|
|
}
|
|
|
|
return usage
|
|
}
|
|
|
|
let parsePluginActionArgs = (args, argdef) => {
|
|
|
|
var plugin_args_config = {
|
|
boolean: [],
|
|
string: [],
|
|
alias: {},
|
|
collect: [],
|
|
negatable: [],
|
|
unknown: null,
|
|
default: {
|
|
}
|
|
}
|
|
|
|
for (const [argname, argconfig] of Object.entries(argdef)) {
|
|
if (argname == '_') {
|
|
continue;
|
|
}
|
|
if ( ("collect" in argconfig) && (argconfig.collect === true) ) {
|
|
plugin_args_config.collect.push(argname)
|
|
}
|
|
if ( ("string" in argconfig) && (argconfig.string === true) ) {
|
|
plugin_args_config.string.push(argname)
|
|
}
|
|
if ( ("boolean" in argconfig) && (argconfig.boolean === true) ) {
|
|
plugin_args_config.boolean.push(argname)
|
|
}
|
|
if ( ("negatable" in argconfig) && (argconfig.negatable === true) ) {
|
|
plugin_args_config.negatable.push(argname)
|
|
}
|
|
if ("default" in argconfig) {
|
|
plugin_args_config.default[argname] = argconfig.default
|
|
}
|
|
}
|
|
|
|
var parsed = parse(args, plugin_args_config)
|
|
|
|
var result = []
|
|
|
|
// we want to keep the order of arguments
|
|
// as defined in the plugin cli code
|
|
for (const argname of Object.keys(argdef)) {
|
|
if (argname in parsed) {
|
|
result.push(parsed[argname])
|
|
}
|
|
}
|
|
|
|
// we're done
|
|
return result
|
|
}
|
|
|
|
// assuming:
|
|
// - the first unknown argument is the name of the plugin
|
|
// - plugins live in ../plugins/<plugin-name>/cli.js, relative to lrcli.js location
|
|
// - only one plugin loaded per invocation, at least for now
|
|
//
|
|
// we *always* pass arguments to plugins as arrays of strings,
|
|
// even if we only got one value
|
|
|
|
let main = async (args) => {
|
|
|
|
var parsed_args = parse(
|
|
args,
|
|
{
|
|
default: {
|
|
h: false,
|
|
},
|
|
stopEarly: true,
|
|
boolean: [ "h" ],
|
|
string: [],
|
|
alias: {
|
|
h: [ "help" ]
|
|
},
|
|
collect: [],
|
|
negatable: [],
|
|
// a function which is invoked with a command line parameter not defined
|
|
// in the options configuration object. If the function returns false,
|
|
// the unknown option is not added to parsedArgs.
|
|
unknown: null
|
|
}
|
|
);
|
|
|
|
// no unknown parsed args? that means we have no plugin specified
|
|
if (parsed_args._.length == 0) {
|
|
console.log(getUsage())
|
|
if (parsed_args.help) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// try loading the plugin
|
|
let plugin
|
|
try {
|
|
plugin = await import(`../plugins/${parsed_args._[0]}/cli.js`);
|
|
} catch (e) {
|
|
// unable to load the plugin? bail with info
|
|
console.log(`\n*** ${e} ***`)
|
|
console.log(getUsage())
|
|
return 2
|
|
}
|
|
|
|
// if we only had exactly one unknown arg, we only have the plugin name
|
|
// but no info from the user what to do with it
|
|
// → print plugin usage and exit
|
|
if (parsed_args._.length == 1) {
|
|
if (!parsed_args.help) {
|
|
console.log('\n*** No action specified for plugin ***')
|
|
}
|
|
console.log(getPluginUsage(plugin))
|
|
if (parsed_args.help) {
|
|
return 0;
|
|
} else {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
let action = parsed_args._[1]
|
|
if ( ! (action in plugin.actions) ) {
|
|
var exit_code = 0
|
|
if (!['--help', '-h'].includes(action)) {
|
|
console.log(`\n*** Action not supported: ${action} ***`)
|
|
exit_code = 4
|
|
}
|
|
console.log(getPluginUsage(plugin))
|
|
return exit_code
|
|
}
|
|
|
|
if (['--help', '-h'].includes(parsed_args._[2])) {
|
|
console.log(getPluginUsage(plugin))
|
|
return 0
|
|
}
|
|
|
|
var parsed_plugin_args = parsePluginActionArgs(
|
|
// removing the plugin name and the method name
|
|
parsed_args._.slice(2),
|
|
// empty object in case arguments key does not exist
|
|
plugin.actions[action].arguments || {}
|
|
)
|
|
|
|
// not using console.log here because we want the *exact* output
|
|
// without any extra ending newlines
|
|
try {
|
|
await Deno.stdout.write(
|
|
new TextEncoder().encode(
|
|
await plugin.actions[action].run(...parsed_plugin_args)
|
|
)
|
|
)
|
|
return 0
|
|
} catch (e) {
|
|
console.log(`\n*** ${e} ***`)
|
|
console.log(getPluginUsage(plugin))
|
|
return 5
|
|
}
|
|
}
|
|
|
|
// export the main function
|
|
export {
|
|
main
|
|
}
|
|
|
|
// run only if we're the main module
|
|
if (import.meta.main) {
|
|
Deno.exit(await main(Deno.args))
|
|
}
|