Skip to content

vic/clap-nix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

clap.nix - Command Line Argument Processing in Nix.

This library provides a clap Nix function for parsing command line arguments into a Nix attribute set.

Test

Features

  • The implementation and tests are pure Nix.

  • Familiar --long and -short option styles.

  • Boolean long options can be negated with --no- surprises.

  • Short options can be collapsed. -abc equals -a -b -c.

  • Option values can be any Nix data type, not only strings.

  • Nested trees of commands as popularized by tools like git.

  • A path of subcommands can be enabled by default. (eg, you can make foo help be executed when foo receives no more arguments)

  • Options are specified by virtue of Nix lib.mkOption and lib.types. Meaning your options can provide defaults, value coercions, aggregation or a composition of different types.

  • Leverages the power of lib.evalModules so you can define option aliases (eg, -h and --help having the same value) or define your own config by providing custom Nix modules and use lib.mkIf and friends.

  • Supports typed positional arguments on each command.

  • Distributed as a flake or legacy library.

  • Made with <3 by oeiuwq.

The slac tree made of { short ? {}, long ? {}, argv ? [], command ? {}, ...}

An slac tree describes the structure of the command line interface that will be parsed using the clap Nix function:

{
  # an optional attribute set of one letter options
  short = {
    f = lib.mkOption {
      description = "file";
      type = lib.types.path;
    };
  };

  # an optional attribute set of long options
  long = {
    help = lib.mkEnableOption "help";
  };
  
  # an optional list of positional typed arguments
  argv = [
    lib.types.int
    (lib.types.separatedString ":")
  ];
  
  # an optional attribute set of sub-commands and their `slac` tree.
  command = {
    show = {
      long = {
        pretty = lib.mkEnableOption "pretty print";
      };
      # ... other nested `short`, `command` or `argv`
    };
  };
}

Calling the clap function.

Once you have your slac tree definition, you are ready to invoke clap with some command line arguments.

{ clap, ... }:
let
  slac = {...}; # the attribute set from the snippet above.

  ####
  # The important thing on this snipped is how to invoke the `clap` function:
  # 
  # The firsr argument is the `slac` tree structure that defines the CLI design.
  # Second argument is a list of Nix values (not just strings) representing 
  # the user entered command line arguments.
  cli = clap slac [ "--help" ];
in
  # More on `clap` return value in the following section.
  if cli.opts.long.help then
    # somehow help the user.
  else
    # actually do the thing.

The clap return value.

The following is an annotated attribute set with the values returned to you by clap:

{
  # A list of all arguments not processed by `clap`
  # Unknown options and unused values will be aggregated in this list.
  # Also, if `clap` finds the string `--` in the command line arguments,
  # it will stop further processing, so `--` and it's following arguments
  # will be in `rest` untouched.
  rest = [ "--" "skipped-values" ];
  
  
  # Typically you'd want to inspect the `opts` attribute in order to
  # know what options the user assigned values to. 
  # Notice that it basically follows the same structure a `slac` has. 
  #
  # Note: Accessing `opts` will make sure that all options correspond to
  # their defined type, by virtue of using `lib.evalModules` -more on this later-,
  # and of course Nix will throw an error if some option has incorrect value type.
  opts = {
    # here you'll find `long` and `short` options assigned to their values.
    long = { help = false; };             # from `--no-help`
    short = { f = /home/vic/some-file; }; # from `-f /home/vic/some-file`
    
    argv = [ 42 "foo:bar" ]; # from positional arguments matching types
    
    # commands also map to their resolved values.
    command = {
      show = {
        enabled = true;  # meaning the user specified the `show` command.
        long = {
          pretty = true; # from `show --pretty` 
        };
      };
    };

  }; # end opts
  
  
  ##-# That's it. The attributes bellow are lower level representations of the
  # `opts` set. But could be useful anyways to you:
  
  optsSet = {}; # Another slac-like set. *BUT* this one is not type-checked at all.

  optsMod = {}; # A Nix Module that contains all the options declarations and definitions.
                #
                # This one is useful if you want to mix with your own modules using `lib.evalModules`
                # for example, for creating option aliases or merging with other conditions.
                #
                # Actually `opts = (lib.evalModules { modules = [ optsMod ]; }).config`.
                
  optsAcc = []; # A list of attribute sets that enable options and subcommands as they are seen.
                # This is the lowest level output, optsSet and optsMod are a by-product of it
                # and it is used directly mostly in tests bellow number 100 to assert the order
                # in which options are read from the command line.
  
}

Examples

Enabling a default subcommand

Enabling a default command means that the user does not have to explicitly name the subcommand yet they can specify the subcommand's options directly. see test

To enable a default command you can set it's command.foo.enabled attribute to either a true boolean or an option with default value of true.

{lib, ...}:
let 
 # an option that takes integers, not relevant to this example;
 intOption = mkOption { type = lib.types.int; };
in
{
 short.a = intOption;
 
 # auto-enable this command by default, so that the user can directly use `-b` without naming `foo`
 command.foo.enabled = true;
 command.foo.short.b = intOption;

 # bar is not auto-enabled, user must explicitly the name `bar` command before setting `-c`.
 command.bar.short.c = intOption;
 
 # since foo is enabled, and its baz subcommand is also enabled, the user could simply provide `-d` directly.
 command.foo.command.baz.enabled = true;
 command.foo.command.baz.short.d = intOption;
} 
Other examples as tests.

Some other examples can be found in the test directory.

Developing

This repo checks for nixfmt on all .nix files. Tests can be run using nix flake check -L --show-trace. Adding more test by adding a 10th step consecutive -test.nix file inside the test/ directory.

Wait, but why?

I know... Nix is a configuration language not a general purpose one. Who needs to parse command line arguments via pure-nix, right? That very person happens to be vic, like many other people I've been trying to learn Nix and configure my system with it.

Also I'm planning to release a nix-related tool soon and really wanted to get away from bash this time. So I'm just trying to program as much as I can in Nix. Yet I'm liking doing Nix a lot more than writing shell scripts with sed,grep,read,tr,awk,bashfulness.

Contributing

Yes, please. Pull-requests are more than welcome!

About

Command line argument parser in pure Nix. Supports sub-commands, typed positional arguments, value coercion and resolution via Nix Modules.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages