Skip to content

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

License

Notifications You must be signed in to change notification settings

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