Skip to content

WIP Powerful command-line utility for C++17 and beyond

License

Notifications You must be signed in to change notification settings

flajann2/hekate

Repository files navigation

Hekate: Powerful command-line parser for C++17 with sub-command support.

Synopsis

Hekate provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++17. It is header only, and relies on the CMake option to mark a module as header-only, so you can make this a submodule to your project.

Why write another CLI parser?

An acceptable CLI parser library should be all of the following:

  • Easy to include (i.e., header only no external requirements)
  • Short Syntax: This is one of the main points of a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
  • or better: Should work with GCC 4.7+ (such as GCC 4.8 on CentOS
    1. or above, or Clang 3.5+, or MSVC 2015+. (Note: for , Clang

    3.4 only fails because of tests, GoogleMock does not support it.)

  • Work on Linux, macOS, and Windows.
  • Clear help printing.
  • Nice error messages.
  • Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
  • Easy to execute, with help, parse errors, etc. providing correct exit and details.
  • Easy to extend as part of a framework that provides “applications” to users.
  • Usable subcommand syntax, with support for multiple subcommands, nested subcommands, and optional fallthrough (explained later).
  • Ability to add a configuration file (ini format), and produce it as well.
  • Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
  • Work with standard types, simple custom types, and extendible to exotic types.
  • Permissively licensed.

The major CLI parsers for C++ include, with my biased opinions: (click to expand)

Documentation

Option Types

type::base

type::numeric

Floating point number.

type::string

type::flag

Boolean, false if not given, true if it is.

NOTE WELL

We want to emulate Thor semantics here, so later we will allow for a –no-xxxx construction

type::unitary

A numeric that describes a unit of measure.

The Gory Details

cmd and the naked-expression

We shall wrap the naked expression with a cmd object, even though there is no “command” as such. This will be a null command

BNF Representation for the Command-Line Parser

This basically represents how the CLI parser will work. it is implemented by hand to keep it clean and simple. This shall represent the “authority” documentation on how this works.

command-line : command-expression | naked-expression
command-expression : command [options] [parameters] [command-expression]
naked-expression : options [command-expression] | [options] parameters
options : option [options]
parameters : parameter [parameters]
option : flag-seq(optname) [= optstring]
optname : alphanum-string
optstring : string-no-spaces
flag-seq : minus-seq | plus-seq
minus-seq : -[-]
plus-seq : +[+]
parameters : parameter [parameters]
parameter : string
command : alphanum-string

Well, I’ve decided to tool this by hand. Not really hard to do.

Hand parser logic

And so, this is what we’ll do.

Take the first token. It will either be a switch or a parameter or a command.

If it is a switch, the token will be preceeded by one or two dashes - (-) or (–). the double dash will always be the “expanded” switch version, the single dash will always be a single-letter switch. I know some CLI parsers will allow for a single dash expanded switch, but we will not support that here.

Optionally, in the case of a boolean expanded switch, it may have a (no-) preceeding it allowing for negation. Do we want to have negation on non-boolean expanded switches? Not in this version, but we may allow for it in the future.

Single-dash switches may be combined, for example (-abf), which otherwise would be written as (-a -b -f). They must all be boolean. If a switch requires a parameter, it must be presented seperately (for now).

Switches with parameter – optionally can take an equals sign(=), followed by the parameter. The switch allowing for an array of parameters must delineate the parameters with commas, example (–reindeer=dasher,prancer,vixen) or (–values=10,20,30).

Mashed switch parameters, say (-sSwanLake) is allowed on the single dash. For the double dash, you must use either a space or an equals sign for delineation.

A free parameter will be a parameter not attached to a switch and does not match a command. Care must be taken to avoid collisions.

A command is a string, no spaces, and can be upper or lower case. It could even be mixed case, but this is discouraged, but who knows what the users will do. They internally will all be converted to lower case, and can be given partially by at least the first 3 characters or more provided there are no collisions with other commands. If there is a collision, it will be treated as an error.

Since this is all recursive, each command may have its own subcommands, and the parsing of parameters and switches will proceed as described above and be associated to that command.

New Direction

Basically, we want to have the commandable hold a list of all the options, all the (suub)commands, and a place for the parameters.

The visitor pattern with the token objects

A tricky problem with typed languages is how to handle different objects in a container. Things get messy with std::variant or std::any approaches, unless you make those objects functors. In that case, you simple call the functor for each object (since it will know what itself is anyway) and proceed that way.

Where I left off

Basically, I am deciding whether to stick with std::any or to split it into the 3 categories of flags, parameter, and commands. I see no reason to stay with std::any at this stage.

I am using the visitor pattern with functors to edge around this case. The functors will be called in succession…

Example

This is an example of how Hekate works: For the command line:

application -a -b -f -n2 FLY --fast pigeon bluejay \
            THROUGH --air --speed=10.4ms "they are birds you know"
hekate<base>
  << opt<flag>("-a", "--all", "Newton's Inspiration")
  << opt<flag>("-b", "--beta_mode", "For testing")
  << opt<flag>("-f", "Force the situation")
  << opt<numeric>("-n", "--count", "How many times to repeat")
  << param<string, 0, 1>("Label to use")
  << cmd<fly> {
    hekate
      << opt<flag>("--fast", "Rapid movement")
      << cmd<through> {
        << opt<unitary>("--speed", "Speed of movement")
        << param<string, 1, inf>("Comments");
      };
  };    

Personal Notes

These notes are for me personally, and are not garenteed to be “up to date” or have any specifc value for the users of Hekate. They will most likely be removed on the final release.

A away to resolve some issues with operations on the various objects

We may employ the Visitor Pattern. https://en.wikipedia.org/wiki/Visitor_pattern

Unicode, UTF-8, and ASCII

Eventually, I want to support UTF-8 with something like https://github.com/unicode-org/icu But this is currently overkill, as I want to keep this header-only library lightweight.

So I will hunt down a simpler, header-only solution later.

For now, we only support ASCII. :(

Journal

This is for “my eyes only”, and shall serve no useful purposee to anyone else, and shall be deleted from the master branch once released.

2019-06-15 – Re-reading my code

The long gaps in dealing with this code is killing me here. It is now hot, no AC in the house, and I’d rather be out somewher cool rather than stuck indoors. Alas…

Also, I now have a 4K monitor to work from. THAT aspect is exciting, as I can get much more on a single screen than ever before. I may buy another after I upgrade my video card to something more suited to 4K!!!!!!

2019-06-16 – And so we continue

Made some progress on things – so now all that remains is the parsing of the opts and params – and then the documentation… but I’ll be more than happy just to get the parsing completed.

About

WIP Powerful command-line utility for C++17 and beyond

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published