Skip to content

jibstack64/field

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

field

GitHub tag (latest by date) GitHub all releases GitHub

A subcommand-based argument parsing library for C++.

Information

field is not a replacement for argh! and similar libraries, it is for use in specific types of programs, as described below.

It is not for multi-argument parsing. field is for programs where, instead of having traditional GNU-style --arg arguments, you have so-called 'command arguments', that act as individual parts of the program.

If you insist on using field for multi-argument parsing, you may set parser.disableLock to true. This will allow multiple subcommands to be used, instead of locking to one.

For example, say you are developing a package manager that is solely controlled using command-line arguments. When installing a package, you want the command-line syntax to look something like this:

package-manager install <package>

Well? Perfect! This is exactly what field is for, segmenting your program into individual chunks that play their own part in the application.

How does it work? The basic idea is that when an argument is passed, it has a matching function that is called alongside it - a field::context object and an std::vector<const char *> are passed to the function, allowing the function to access the flags passed, and the values passed to it through the arguments.

What about flags? They are completely supported. As long as they are passed before the subcommand, or after all of the subcommand's positional arguments.

Basic syntax

The syntax in field is relatively basic, and is designed to be as sleek as possible.

A basic application would look something like this:

#include <iostream>
#include <vector>

#include ".../field.hpp"

void say(field::context& ctx, std::vector<const char *> values) {
    // as we did not specify a limit to the number of positional arguments,
    // values will include all positional argument values after 'say'
    for (const auto& c : values) {
        std::cout << c << std::endl;
    }
}

int main(int argc, char ** argv) {
    // create the argument parser
    field::parser parser;

    // add our argument
    // we pass our 'say' function
    // this will be ran when the argument is passed
    field::arg sayArg = parser.add("say", say);

    // now we parse
    parser.parse(argc, argv);
    // or alternatively drop the argc
    // parser.parse(argv);

    return 0;
}

Lets run it:

./out.o say "hello world!" "and FOSS developers!!!"

Output:

hello world!
and FOSS developers!!!

Context

Context (field::context) is passed to every function bound to an subcommand/flag if it is passed. It contains a list of flags passed, and any values passed outside of the range of positional arguments.


Subcommands

Subcommands can be added with the parser.add(...) function. You pass a name, and optionally a function and takes (the number of positional arguments the subcommand receives).

For instance, if I wanted a basic text-mirroring program:

#include <iostream>
#include <vector>

#include ".../field.hpp"

// this is ran when "mirror" is passed
void mirror(field::context& _, std::vector<const char *> text) {
    for (const auto& c : text) {
        std::cout << c << std::endl;
    }
}

int main(int argc, char ** argv) {
    field::parser parser;

    // we now add the subcommand with its matching function
    //                      number of pos. arguments
    parser.add("mirror", mirror, 3);

    parser.parse();

    return 0;
}

Now, when we run the program, it takes a maximum of 3 positional arguments:

./out.o mirror hey! im very cool

Output:

hey!
im
very

As you can see, cool was cut off due to the takes (number of taken values) being 3.


Flags

Flags can be named in any way you would prefer, however I recommend using the GNU-style - and -- prefixes to distinguish them from subcommands.

Flags are very similar to arguments, lets try making a simple hello world program:

#include <iostream>

#include ".../field.hpp"

void helloWorld(field::context& _) {
    std::cout << "hello world!" << std::endl;
}

int main(int argc, char ** argv) {
    field::parser parser;

    parser.addFlag("--hello-world", helloWorld);

    parser.parse();

    return 0;
}

And when we run it:

./a.out --hello-world

Output:

hello world!

Hate using functions?

If you would rather not pass functions around, and instead have a series of if statements, you can do that too.

Instead of passing a function, only pass a name (and optionally takes).

#include ".../field.hpp"

int main(int argc, char ** argv) {
    field::parser parser;
    
    // no function passed
    field::arg* ar = parser.add("no-func", 2);

    parser.parse();

    if (ar->passed) {
        std::cout << "passed values:" << std::endl;
        for (const auto& v : ar->values()) {
        std::cout << v << std::endl;
        }
        std::cout << "passed flags:" << std::endl;
        for (auto& fl : parser.ctx.passedFlags) {
            std::cout << fl->name << std::endl;
        }
        std::cout << "overflowing values:" << std::endl;
        for (auto& of : parser.ctx.overflowValues) {
            std::cout << of << std::endl;
        }
    }
}

As you can see, this allows you to access the passed boolean of the argument, and manage code based on if statements.

Not only this, you can access the context outside of a function by accessing the ctx variable of your parser. It is the equivalent of using the context variable passed to a function.