Skip to content

ysufender/CLIParser

Repository files navigation

CLIParser

A simple CLI parser. Simple data types and simple operations are allowed, for simple usages.

Installation

Simply clone the repository by

git clone https://github.com/ysufender/CLIParser.git

then you can either use

./build.ps1 or ./build.sh

and create an executable named CLIParser.exe, which contains the entry point defined in debug.cpp for you to test things after modifying the source,

or you can do

./publish.ps1 or ./publish.sh

and create the static library libCLIParser.a, which you can link to your projects and use.

Or you can just go and do cmake blah blah blah -DCMAKE_BUILD_TYPE=DebugTest to include debug.cpp and create an executable, Or yet again cmake blah blah blah -DCMAKE_BUILD_TYPE=whateveryouwantking to just compile it to a static lib.

Adding/Removing Flags

The Parser class does all the work for us. Simply pass the args, argc, and the prefix of your choice to the constructor, then use the AddFlag method.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--"};
    parser.AddFlag<FlagType::Int>("i");
    parser.AddFlag<FlagType::Float>("f");
    parser.AddFlag<FlagType::String>("s");
    parser.AddFlag<FlagType::Bool>("b");
    parser.AddFlag<FlagType::IntList>("il");
    parser.AddFlag<FlagType::FloatList>("fl");
    parser.AddFlag<FlagType::StringList>("sl");
}

Voila! Now it's time to parse the command line and return our flags.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--"};
    parser.AddFlag<FlagType::Int>("i");
    parser.AddFlag<FlagType::Float>("f");
    parser.AddFlag<FlagType::String>("s");
    parser.AddFlag<FlagType::Bool>("b");
    parser.AddFlag<FlagType::IntList>("il");
    parser.AddFlag<FlagType::FloatList>("fl");
    parser.AddFlag<FlagType::StringList>("sl");

    Flags flags = parser.Parse();
    int i = flags.GetFlag<FlagType::Int>("i");
    float f = flags.GetFlag<FlagType::Float>("f");
    std::string s = flags.GetFlag<FlagType::String>("s");
    bool b = flags.GetFlag<FlagType::Bool>("b");
    std::vector<int> il = flags.GetFlag<FlagType::IntList>("il");
    std::vector<float> fl = flags.GetFlag<FlagType::FloatList>("fl");
    std::vector<string> sl = flags.GetFlag<FlagType::StringList>("sl");
}

Be aware that with this way, all the flags will be set to the default constructed values of their types. If you want to specify a default value, see the next header.

Binding Flags

Sometimes we might want to use both -h and --help for help flag. But we want them both to have the same value depending on usage. So what do we do? Yeah we bind them. Binding is a one way operation. You can bind a flag to another but can't (you can actually but I wouldn't suggest doing so) bind the binder one to the binded. Yeah.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--"};
    parser.AddFlag<FlagType::Bool>("help");
    parser.BindFlag("h", "help");
}

Now, we binded the --h flag to --help. So whenever we do exe --h, the value of --help will be true.

#include <iostream>

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--"};
    parser.AddFlag<FlagType::Bool>("help");
    parser.BindFlag("h", "help");

    Flags flags = parser.Parse();
    bool help = flags.GetFlag<FlagType::Bool>("help");

    std::cout <<  "Help needed: " << std::boolalpha << help << '\n';
}

Fun Fact: If you try to flags.GetFlag<FlagType::Bool>("h"), you'll get a nice, warm error message slapped into your face. That's because h flag doesn't exist, it was a CLI alias for help.

Another Fun Fact: You can change the prefix used for binded flags. Parser class has another constructor that takes an argument named boundPrefix.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--", "-"};   // now the bound flags will use `-` as prefix
    parser.AddFlag<FlagType::Bool>("help");
    parser.BindFLag("h", "help");

    Flags flags = parser.Parse();
    bool help = flags.GetFlag<FlagType::Bool>("help");
    std::cout <<  "Help needed: " << std::boolalpha << help << '\n';
}

Now the -h is bound to --help instead of --h. Pretty neat huh? (Just say yes)

Automatic Help Text Generation

As you add and bind flags, the Parser will configure an Available Flags: ... text for you.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int agrc, char** args)
{
    Parser parser { args, argc, "--", "-" };

    parser.AddFlag<FlagType::String>("string1", "Some String Value");
    parser.AddFlag<FlagType::String>("string2", "Some Other String Value");
    parser.AddFlag<FlagType::String>("string3", "Defaulted string value", "Default Value");
    parser.AddFlag<FlagType::String>("string4", "Binded String Value");

    parser.BindFlag("s4", "string4");

    Flags flags { parser.Parse() };

    std::cout << "Debug CLI Usage:\n\tCLIParser <..flags..>\n";
    std::cout << flags.GetHelpText() << '\n';
}

// OUTPUT:
//    Debug CLI Usage:
//            CLIParser <..flags..>
//
//    Available Flags:
//            --string1 : Some String Value
//            --string2 : Some Other String Value
//            --string3 : Defaulted string value
//            --string4, -s4 : Binded String Value

Creating Separators

You might want to group your flags in the auto-generated help text. Parser provides the Parser.Separator() method for this purpose. Simply call this method where you want to place an empty line.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int agrc, char** args)
{
    Parser parser { args, argc, "--", "-" };

    parser.AddFlag<FlagType::String>("string1", "Some String Value");
    parser.Separator();
    parser.AddFlag<FlagType::String>("string2", "Some Other String Value");
    parser.AddFlag<FlagType::String>("string3", "Defaulted string value", "Default Value");
    parser.Separator();
    parser.AddFlag<FlagType::String>("string4", "Binded String Value");

    parser.BindFlag("s4", "string4");

    Flags flags { parser.Parse() };

    std::cout << "Debug CLI Usage:\n\tCLIParser <..flags..>\n";
    std::cout << flags.GetHelpText() << '\n';
}

// OUTPUT:
//    Debug CLI Usage:
//            CLIParser <..flags..>
//
//    Available Flags:
//            --string1 : Some String Value
//
//            --string2 : Some Other String Value
//            --string3 : Defaulted string value
//
//            --string4, -s4 : Binded String Value

Default Values

Specifying default values is easy, just add the value as another argument to AddFlag method. The template metaprogramming dark magic will handle the rest.

#include "CLIParser.hpp"

using namespace CLIParser;

int main(int argc, char** args)
{
    Parser parser {args, argc, "--"};
    parser.AddFlag<FlagType::String>("someStr", "","Yeah, defaults...");

    Flags flags = parser.Parse();

    std::cout << flags.GetFlag<FlagType::String>("someStr") << '\n';
}

// Outputs 'Yeah, defaults...' if no value is provided in CLI.

Passing Conventions

As for primitives:

exe -b -str1 string -str2 "string 2" -str3 'string 3' -i 69 -f 42

And for lists:

exe -floatL 5.4 .5 .4 7 360 -strL yup "this" is_a 'string list' -intL 15 20 88

Fun Fact: I forgot that you can use hexadecimal with integers. Like:

exe --int-list 0xFF 15 0x15 --straight-up-int 0x00

NFAQ (Not so Frequently Asked Questions)

Q: Can I do "[ 5, ]" for an integer list?
A: For God's sake NO!

Q: You got rid of that stupid list syntax?
A: I absolutely did.

Q: How are you able to think of a solution that is soooo ugly?
A: Natural talent.

Q: Will you create more ugly things like this?
A: Absolutely YES!

Q: Has anyone ever saw your codes and gave their opinion?
A: Absolutely NO!

Q: Why are you writing these docs and readmes that no one will read?
A: One day, I was sleeping. But Jesus, what a sleep! Then a pigeon came and said "You shall write thy docs and readmes". That day I started to write them.

NQ: Please stop writing READMEs.
A: Nah.

About

Parses the CLI. But in the most inefficient, ugly, dirty and stupid way possible known to man. You're welcome.

Topics

Resources

License

Stars

Watchers

Forks

Contributors