dsherret / dax

Cross-platform shell tools for Deno and Node.js inspired by zx.
MIT License
997 stars 34 forks source link

Basic argument parsing #117

Closed dsherret closed 3 months ago

dsherret commented 1 year ago

Generally when writing scripts, you don't need complex argument parsing and want something that can easily be figured out by looking at the code. You also don't need help text. For that reason, I think an approach like the following might be more appropriate than something more featureful like deno_std's argument parser.

$.args.on("prepare", () => {
  console.log("Preparing...");
});

$.args.on("build", () => {
  console.log("Building...");
});
> deno run -A script.ts prepare
Preparing...
> deno run -A script.ts build
Building
> deno run -A script.ts other
Unknown command: other

Possibilities:
  - prepare
  - build

More complex example:

const args = $.args(); // shorthand for `const args = $.args(Deno.args);`

await args.on("build", async (args) => {
  await args.on("data", (args) => {
    console.log(1);
    if (args.hasFlag("force") || args.hasFlag("f") || hasChanged()) {
     await buildData();
     console.log(2);
    }
  });

  // can be sync or async
  args.on("info", (args) => {
    console.log(3);
  }); 
});

args.on("version", (args) => {
  console.log(4);
});

// no match here causes a non-zero exit code that shows all the found options
> deno run -A script.ts build data
1
2
> deno run -A script.ts build data
1
> deno run -A script.ts build data --force
1
2
> deno run -A script.ts build data -f
1
2
> deno run -A script.ts build info
3
> deno run -A script.ts build other
Unknown command: build other

Possibilities:
  - build data
  - build info
> deno run -A script.ts version
4
> deno run -A script.ts invalid
Unknown command: invalid

Possibilities:
  - build
  - version
matklad commented 11 months ago

People could call a function on args to prevent the "unknown command" error (maybe args.markHandled() or something).

One idea I've been toying with was "immediate mode" argument parser, with the API along the lines of

const force = args.bool("-f/--force", "ignore errors")
const path = args.string("--path", "path to operate on", { required: true })
args.done()

The trick would be that required --path does not return the error immediately -- it returns "" (or maybe even undefined (just blatantly lying to the type system)). The actual error is raised in .done(). The benefit here is that .done() knows the full grammar for the CLI, so it actually can emit a help message.

From the other angle, clap-style derives are actually inconvenient for quick scripts, as they force you to declare a struct type for the CLI args, while, in the script, you often need only local variables.

And I think low-effort option to add help text (or just to print the options themselves, without any human-authored help) is pretty valueable -- a common use-case are internal tools for a team of software engineers, where you don't necessary want to polish everything to be fit for the end-user, but you also don't want to bug your collegue-author in slack to learn how to use the tool.

joehillen commented 6 months ago

Not going to lie, but this feels outside the scope of dax. cliffy has a really good interface that is really easy to understand and create good CLI interfaces.

dsherret commented 6 months ago

I think it's in scope. Dax is meant to be a swiss army knife for automation scripts and being able to understand inputs to that script is important part of that. Both cliffy and deno_std don't really provide the kind of API I'm looking for in this case.

balupton commented 5 months ago

Also agree out of scope. There is no necessity to couple such functionality, as it can just be composed. You could write your desired functionality as your own module.

If you want something existing, I wrote this arg parser https://github.com/bevry/argument/blob/HEAD/source/example.ts based on the patterns from a decade of writing hundreds of commands in Dorothy https://github.com/bevry/dorothy - which I'm thinking of using dax for as a replacement for bash

dsherret commented 3 months ago

Yeah, I'm going to close this for now (mainly because I don't have the bandwidth atm)