A language for command line interfaces

73 views
Skip to first unread message

Siddhartha Kasivajhula

unread,
Jul 24, 2021, 1:14:22 PM7/24/21
to Racket Users
Hello folks,
If you ever have a need to write a command line script in Racket, you may be interested in the package I've just put up. Normally you'd write such scripts using Racket's built-in command-line or parse-command-line form. These offer a lot of functionality, but it takes time to learn how they work and the syntax is somewhat idiosyncratic. So, I've written a #lang that allows you to achieve the same functionality but with more familiar and extensible syntax, #lang cli.

The language is composed of 5 forms - help, flag, constraint, program, and run. With these 5 forms, you get all of the functionality of the built-in parse-command-line form, and with syntax that's much simpler. In fact, the nontrivial forms of the language simply use Racket's normal function definition syntax, so there's very little to learn -- you basically write normal functions and they are implicitly wired to accept their inputs via the command line.

At the moment, the functionality offered is at parity with the built-in forms, but there are some planned additions that would make it more powerful, including composable commandsgeneralized constraints, and argument schemas.

So if you write command line scripts, I encourage you to give it a try.

I also coincidentally came across the package natural-cli by Sage Gerard. Looks like it provides some interesting features and in particular subcommands, which are absent in #lang cli. It very well may be that this package should be leveraged for semantics in #lang cli in the future (Sage if you'd like to add anything about what this package offers, please do).

Enjoy,
-Sid

Sage Gerard

unread,
Jul 24, 2021, 1:47:39 PM7/24/21
to skas...@gmail.com, racket...@googlegroups.com
Thank you for doing this. CLIs are an interesting beast, and I should be available to contribute and/or comment next week.


~slg






-------- Original Message --------
--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CACQBWFnevJaRg61xLv6ayps-ihGpuUFUFEVbLTH7m929tpex3Q%40mail.gmail.com.

Siddhartha Kasivajhula

unread,
Jul 24, 2021, 4:05:16 PM7/24/21
to Sage Gerard, Racket Users
That's great to hear, Sage. My initial thrust here was to just get the syntax in place so that it worked the way it should, and I didn't give a lot of attention to the supporting implementation beyond that it should work correctly. I definitely see a possibility for big changes there and maybe even an intermediary layer between cli and racket/cmdline which could be natural-cli or something resembling it. I made some notes regarding the existing implementation and possible next steps in this issue. Happy to discuss further and I appreciate your offer to collaborate.

D. Ben Knoble

unread,
Jul 25, 2021, 2:23:07 PM7/25/21
to Racket Users
The language is composed of 5 forms - help, flag, constraint, program, and run. With these 5 forms, you get all of the functionality of the built-in parse-command-line form, and with syntax that's much simpler. In fact, the nontrivial forms of the language simply use Racket's normal function definition syntax, so there's very little to learn -- you basically write normal functions and they are implicitly wired to accept their inputs via the command line.

Could we add require? I can think of two compelling reasons:

1. What else is available in the program form? All of racket, or just racket/base? And either way, what if I want to use procedures from other packages?
2. What if I want to write a bunch of library code, and expose some of it in a CLI script? I do this now in different ways: a module+ main that uses code defined in the enclosing module, or a script that requires auxiliary modules. Often the "body" of the program form isn't more than a call to a "main" procedure: this enables me to provide that procedure as part of the library, too.

Siddhartha Kasivajhula

unread,
Jul 25, 2021, 3:09:42 PM7/25/21
to D. Ben Knoble, Racket Users
Right, thank you for bringing that up. I should have mentioned that the #lang provides all of racket/base at the module level, so you can write normal Racket code (including `require`), and any imports at the module level would be available within the `program` body since it compiles down to a normal function.

You can also use `provide`, so once you define your command using `program`, you can provide it the same as any function. The client module requiring your command would need to be a #lang cli module (at least at the moment) so that it can actually run the imported command using `run`.


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

D. Ben Knoble

unread,
Jul 25, 2021, 3:21:19 PM7/25/21
to Siddhartha Kasivajhula, Racket Users
> Right, thank you for bringing that up. I should have mentioned that the #lang provides all of racket/base at the module level, so you can write normal Racket code (including `require`), and any imports at the module level would be available within the `program` body since it compiles down to a normal function.
>
> You can also use `provide`, so once you define your command using `program`, you can provide it the same as any function. The client module requiring your command would need to be a #lang cli module (at least at the moment) so that it can actually run the imported command using `run`.

That would be good to add to the docs in my opinion.

Siddhartha Kasivajhula

unread,
Jul 25, 2021, 5:45:14 PM7/25/21
to D. Ben Knoble, Racket Users
Good call, I'll add a note to the effect. Btw in case it wasn't clear from my response, using #lang cli shouldn't affect the existing workflows that you mentioned, although, you would probably want to define the command line component of your code as a separate #lang cli module, rather than in a module+ main.

Reply all
Reply to author
Forward
0 new messages