Introducing elm-cli-options-parser, for building type-safe CLIs

I just published a type-safe command line options parser for Elm. I was finding that writing even very basic command line parsers in JavaScript/TypeScript was becoming the least fun part of my otherwise enjoyable Elm projects. Here’s an example of the kind of options processing code that felt error-prone and tedious from my graphqelm package.

You can play around with a git example in this live terminal simulation on Ellie, or check out the elm-cli-options-parser project on Github.

Here are some of the core design goals I had for this library.

  1. Build in great UX by design For example, single character options like -v can be confusing. Are they always confusing? Maybe not, but eliminating the possibility makes things much more explicit and predictable. For example, grep -v is an alias for --invert-match ( -V is the alias for --version ). And there is a confusing and somewhat ambiguous syntax for passing arguments to single character flags (for example, you can group multiple flags like grep -veabc , which is the same as grep --invert-match --regexp=abc ). This is difficult for humans to parse or remember, and this library is opinionated about doing things in a way that is very explicit, unambiguous, and easy to understand.

Another example, the --help flag should always be there and work in a standard way… so this is baked into the library rather than being an optional or a manual configuration.

  1. Guaranteed to be in-sync - by automatically generating help messages you know that users are getting the right information. The design of the validation API also ensures that users get focused errors that point to exactly the point of failure and the reason for the failure.

  2. Be explicit and unambiguous - like the Elm ethos, this library aims to give you very clear error messages the instant it knows the options can’t be parsed, rather than when it discovers it’s missing something it requires. For example, if you pass in an unrecognized flag, you will immediately get an error with typo suggestions. Another example, this library enforces that you don’t specify an ambiguous mix of optional and required positional args. This could easily be fixed with some convention to move all optional arguments to the very end regardless of what order you specify them in, but this would go against this value of explicitness.

I love getting feedback of any kind! Please let me know if you try out the library, I’d really appreciate hearing any first impressions.

5 Likes

I’m curious: other than yourself, who is the audience? My guess: library authors building tools with CLIs (like graphqelm.) But that doesn’t seem super common… who else has the problem that you’re solving for? How does your experience writing the graphqelm CLI inform publishing this package?

To be completely transparent about this, I started building a similar library and stopped. My core concern: writing CLIs is so far outside Elm’s sweet spot that you will end up having to use ports for everything. Passing in argv in flags is fine, but what about writing to the console? Or reading a file from the system? Or reading bytes from stdin? Just having an options parser exist opens up a whole can of worms of things that probably won’t be well-supported for a long time. For myself, I was wary of doing things that might encourage people to go off and do Kernel hacks.

That said, I don’t want to completely squish your enthusiasm here! Do you have any thoughts on the stuff above?

(p.s. here was the design document for that experiment. If’s helpful for you, cool. If it doesn’t, ignore. :smile:)

Also:

:clap: for identifying the core emotional pain straight away!

Wow, what a treasure trove of nice ideas, thank you for sharing that document Brian! I didn’t realize that you had sketched something similar out. It looks like we arrived at a lot of the same conclusions. I’ll definitely be using this document for some more inspiration. And by the way, since you’ve already considered an API similar to this, I’d love to hear your thoughts on the API design if you have any first impressions!

The main group I had in mind for using this library are package authors as you say. I’ve built a lot of code generation packages (such as elm-typescript-interop and elm-electron in addition to graphqelm), so it’s actually a pretty common problem just for me alone :smile: . I think that code generation libraries are ideally suited to an Elm-based CLI because all they do is process the CLI options (argv), generate code (a bunch of Strings), and spit it out into files. So most of the JS interop needed is just an init flag to get argv and a port to write to a file.

I see similar imperative options parsing code in the elm-test codebase which I imagine could be nicer to maintain using elm-cli-options-parser.

I also found that doing things like processing stdin was quite doable with a decent abstraction around the ports. I was pretty happy with my Stdin module in the grep example that I built.

I hope that helps answer your question, I’ve found that it was useful enough just for code generation libraries to merit building this library for me. But I hope that others might find it useful for building small, simple tools, perhaps ones that don’t involve lots of intricate Unix low-level functionality. But again, from the examples that I’ve built, I just needed a simple command-line interface to tell Elm to do some heavy lifting, and then it only called on minimal Unix tasks to finish the job.

Thanks for all your thoughts, Brian, I really appreciate it!

Another thought, one of the things that really excited me about having a nice options parser in Elm was that you could have those great explicit, up-front contracts that we love so much in Elm. That means awesome error messages for free (no chance of forgetting a case). And the compiler can guarantee that you either have the data you expect, or else the user gets a clear error. So you can write much more confident code, and your whole contract is specified on one place.

So to me, it’s worth going out to Elm just for that even for a utility with lots of low level Unix work. Then you can always call on some ports and just let JS or Typescript handle the low level IO stuff.

1 Like

This is awesome. I wrote a simple options parser for an Elm Cucumber runner I’ve been writing, but I wasn’t happy with it. This looks like a much better option, thanks. I’ll see if I can integrate it this weekend.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.