The Crystal Programming Language Forum

Clip: deserialize CLI parameters to an object, with errors and help management

Hi,

I present you my last project: Clip. It is a library to build CLI or CLI-like application.

I wasn’t satisfied with existing tools. Either they don’t do as much I as would like (like OptionParser), or they do too much like calling my code (like Admiral, Commander, clicr, …).

My goals were:

  • support all standard behaviors a CLI application should have: long and short options, arguments, commands, repeated options or arguments, nice help and errors messages, and more.
  • compilation time type validation: I don’t want to have to check at runtime the type of a parsed value. I want be sure that if the parsing succeed I have exactly what I wanted.
  • work with non CLI application, like text bots (think IRC bots).
  • the library must not call my code. I want to manage my code like I want, especially I want to be able to give the parsed parameters to a function plus other parameters.i

I would say that with Clip I nailed it.

Here is how it looks like:

require "clip"

@[Clip::Doc("An example command.")]
struct Command
  include Clip::Mapper

  @[Clip::Doc("Enable some effect.")]
  getter effect = false

  @[Clip::Doc("The file to work on.")]
  getter file : String
end

begin
  command = Command.parse
rescue ex : Clip::ParsingError
  puts ex
  exit
end

case command
when Clip::Mapper::Help
  puts command.help
else
  if command.effect
    puts "Doing something with an effect on #{command.file}."
  else
    puts "Doing something on #{command.file}."
  end
end
$ crystal build command.cr
$ ./command
Error:
  argument is required: FILE
$ ./command --help
Usage: ./command [OPTIONS] FILE

An example command.

Arguments:
  FILE  The file to work on.  [required]

Options:
  --effect / --no-effect  Enable some effect.  [default: false]
  --help                  Show this message and exit.
$ ./command myfile
Doing something on myfile.
$ ./command --effect myfile
Doing something with an effect on myfile.

Let me know what you think about it :)

Source code: https://github.com/erdnaxeli/clip
Documentation: https://erdnaxeli.github.io/clip/

3 Likes

Always nice to have options.

I’m curious why you decided to use annotations for the doc-strings.
wouldn’t something simpler have worked?

Like collecting the doc strings in a Hash or something?

1 Like

How’s that “simpler”? :confused:

I consider annotations an “advanced Language feature”.
I never used them, and so don’t really know how they work.

So, the question was just literally to find out what makes them better suited or preferred for this usecase.

They fit perfectly because they allow to annotate the language features that are used in the domain model. It’s really straightforward to attach documentation info to the types and ivars that represent what is being documented.

i guess this is a good opportunity to finally learn about annotatins :slight_smile:

1 Like

Thanks for the interest :)

That’s actually a good idea, I didn’t think about it.

Annotations felt the right thing to me as says @straight-shoota . They are indeed an advanced language feature as you have to write macros to do something with them, but as a user point of view (the dev using the lib) it seems to me that using annotation is straightforward. For example If you already use the JSON lib it uses annotations to specify some behavior on (de)serialization.

But yeah using the docstring could have done the job too. Actually Typer (a python lib that was a big inspiration for this project) uses docstrings for commands’ help too. However I am not sure about three things:

  • are docstrings accessible with macro? I didn’t found anything in the doc.
  • that would mean mixing annotations (because will still need them for more advanced cases, not only the doc) plus docstrings for documentation, two different behaviors.
  • maybe you want to have a different documentation for the api doc that will be used by devs and the help that will used by users
2 Likes