Skip to content

Commit

Permalink
Add support for positional arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
sisakat committed Aug 26, 2023
1 parent 9ebbf99 commit 1916a37
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 16 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ int main(int argc, char* argv[])
.description("Some really useful cli program.")
.addHelp();

std::string inputFilename;
parser.option<std::string>("INPUT_FILENAME")
.required()
.description("Input filename.")
.store(inputFilename);

parser.option("-v", "--version")
.flag()
.overruling()
Expand Down Expand Up @@ -46,6 +52,7 @@ int main(int argc, char* argv[])

if (parser.parse())
{
std::cout << "Input filename: " << inputFilename << std::endl;
std::cout << "Config file: " << config << std::endl;
std::cout << "Silent mode set: " << silent << std::endl;
std::cout << "Flag set: " << flag.value() << std::endl;
Expand All @@ -58,10 +65,12 @@ int main(int argc, char* argv[])
Sample Application 1.0.0
Some really useful cli program.

./a.out [-h] [-v] -c <json config file> [-s]
./a.out [-h] INPUT_FILENAME [-v] -c <json config file> [-s]
[-f] [--loglevel debug|info|trace]
-h --help
Print this help message.
INPUT_FILENAME
Input filename.
-v --version
-c --cfg <json config file>
Sets the config file.
Expand All @@ -74,7 +83,8 @@ Some really useful cli program.
```
```
> ./basic -c config.json -f
> ./basic test.txt -c config.json -f
Input filename: test.txt
Config file: config.json
Silent mode set: 0
Flag set: 1
Expand Down
7 changes: 7 additions & 0 deletions examples/basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ int main(int argc, char* argv[])
.description("Some really useful cli program.")
.addHelp();

std::string inputFilename;
parser.option<std::string>("INPUT_FILENAME")
.required()
.description("Input filename.")
.store(inputFilename);

parser.option("-v", "--version")
.flag()
.overruling()
Expand Down Expand Up @@ -41,6 +47,7 @@ int main(int argc, char* argv[])

if (parser.parse())
{
std::cout << "Input filename: " << inputFilename << std::endl;
std::cout << "Config file: " << config << std::endl;
std::cout << "Silent mode set: " << silent << std::endl;
std::cout << "Flag set: " << flag.value() << std::endl;
Expand Down
73 changes: 61 additions & 12 deletions include/clapp.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
___ _ _ ___ ___
/ __| | /_\ | _ \ _ \ Command Line Argument Parser++
| (__| |__ / _ \| _/ _/ Version 1.3.1
| (__| |__ / _ \| _/ _/ Version 1.4.0
\___|____/_/ \_\_| |_| https://github.com/sisakat/clapp
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Expand Down Expand Up @@ -42,8 +42,8 @@ SOFTWARE.
#include <vector>

#define CLAPP_VERSION_MAJOR 1
#define CLAPP_VERSION_MINOR 3
#define CLAPP_VERSION_PATCH 1
#define CLAPP_VERSION_MINOR 4
#define CLAPP_VERSION_PATCH 0

namespace clapp
{
Expand Down Expand Up @@ -113,12 +113,15 @@ class ArgumentParser
long_option{std::move(_long_option)}
{
}

explicit Option(const std::string& long_option)
: Option("", long_option)
{
}

virtual void setValue(const std::string& value) = 0;
virtual bool isAllowedValue(const std::string& value) = 0;
[[nodiscard]] virtual bool isAllowedValue(const std::string& value) = 0;
[[nodiscard]] virtual bool isPositionalOption() const = 0;
virtual std::set<std::string> choices() = 0;
virtual void invokeCallback() = 0;

Expand Down Expand Up @@ -346,6 +349,12 @@ class ArgumentParser
return false;
}

[[nodiscard]] bool isPositionalOption() const override
{
return !long_option.empty() && short_option.empty() &&
long_option.at(0) != '-';
}

std::set<std::string> choices() override
{
std::ostringstream oss;
Expand Down Expand Up @@ -517,17 +526,31 @@ class ArgumentParser
{
ss << "[";
}
if (!option->short_option.empty())
{
ss << option->short_option;
}
else if (!option->long_option.empty())
if (option->isPositionalOption())
{
ss << option->long_option;
if (!option->argument_name.empty())
{
ss << "<" << option->argument_name << ">";
}
else
{
ss << option->long_option;
}
}
if (!option->argument_name.empty())
else
{
ss << " <" << option->argument_name << ">";
if (!option->short_option.empty())
{
ss << option->short_option;
}
else if (!option->long_option.empty())
{
ss << option->long_option;
}
if (!option->argument_name.empty())
{
ss << " <" << option->argument_name << ">";
}
}
auto choices = option->choices();
if (!choices.empty())
Expand All @@ -554,6 +577,11 @@ class ArgumentParser

for (const auto& option : m_options)
{
if (option->isPositionalOption() && option->description.empty())
{
continue;
}

if (!option->short_option.empty())
{
ss << std::setw(2) << std::left << option->short_option;
Expand Down Expand Up @@ -723,6 +751,7 @@ class ArgumentParser

if (m_options_map.find(optionStr) != m_options_map.end())
{
// we have a proper option
auto idx = m_options_map.at(optionStr);
auto& option = m_options.at(idx);

Expand Down Expand Up @@ -751,6 +780,26 @@ class ArgumentParser

m_option_order.push_back(idx);
}
else if (!optionStr.empty() && optionStr.at(0) == '-')
{
std::ostringstream oss;
oss << "Unknown option '" << optionStr << "'.";
throw ArgumentParserException(oss.str());
}
else
{
// we have no proper option - possibly a positional option
for (auto& option : m_options)
{
if (option->isPositionalOption() && !option->set)
{
option->setValue(optionStr);
m_option_order.push_back(
m_options_map.at(option->long_option));
break;
}
}
}

++m_curr_arg;
}
Expand Down
112 changes: 110 additions & 2 deletions test/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ TEST_CASE("test_int_store")

int out = 0;
parser.option<int>("-a").store(out);
parser.option<std::string>("-b");
parser.parse();

REQUIRE(out == 123);
Expand All @@ -22,6 +23,7 @@ TEST_CASE("test_double_store")

double out = 0;
parser.option<double>("-a").store(out);
parser.option<std::string>("-b");
parser.parse();

REQUIRE(out == 3.1415);
Expand All @@ -34,6 +36,7 @@ TEST_CASE("test_string_store")

std::string out;
parser.option<std::string>("-a").store(out);
parser.option<std::string>("-b");
parser.parse();

REQUIRE(out == "something");
Expand All @@ -45,6 +48,7 @@ TEST_CASE("test_value")
clapp::ArgumentParser parser(arguments);

auto& option = parser.option<std::string>("-a");
parser.option<std::string>("-b");
parser.parse();

REQUIRE(option.value() == "something");
Expand All @@ -55,6 +59,8 @@ TEST_CASE("test_default_value")
std::vector<std::string> arguments{"", "-x", "something", "-b", "hello"};
clapp::ArgumentParser parser(arguments);

parser.option<std::string>("-x");
parser.option<std::string>("-b");
auto& option = parser.option<std::string>("-a").defaultValue("abc");
parser.parse();

Expand Down Expand Up @@ -120,6 +126,7 @@ TEST_CASE("test_option_with_choices")
parser.option<std::string>("--option")
.choices({"value", "value1"})
.store(cfg);
parser.option("-a").flag();
parser.parse();

REQUIRE(cfg == "value");
Expand All @@ -134,6 +141,107 @@ TEST_CASE("test_option_with_choices_fail")
parser.option<std::string>("--option")
.choices({"value", "value1"})
.store(cfg);

REQUIRE_THROWS(parser.parse());
}
}

TEST_CASE("test_option_positional")
{
std::vector<std::string> arguments{"", "file.txt"};
clapp::ArgumentParser parser(arguments);

auto& option = parser.option<std::string>("SOME_FILE");
parser.parse();

REQUIRE(option.value() == "file.txt");
}

TEST_CASE(
"test_option_positional_with_other_arguments_after_positional_argument")
{
std::vector<std::string> arguments{"", "file.txt", "-d", "1"};
clapp::ArgumentParser parser(arguments);

auto& positionalOption = parser.option<std::string>("SOME_FILE");
auto& intOption = parser.option<int>("-d");
parser.parse();

REQUIRE(positionalOption.value() == "file.txt");
REQUIRE(intOption.value() == 1);
}

TEST_CASE(
"test_option_positional_with_other_arguments_before_positional_argument")
{
std::vector<std::string> arguments{"", "-i", "1", "file.txt"};
clapp::ArgumentParser parser(arguments);

auto& positionalOption = parser.option<std::string>("SOME_FILE");
auto& intOption = parser.option<int>("-i");
parser.parse();

REQUIRE(positionalOption.value() == "file.txt");
REQUIRE(intOption.value() == 1);
}

TEST_CASE("test_option_multiple_positional")
{
std::vector<std::string> arguments{"", "fileIn.txt", "fileOut.txt"};
clapp::ArgumentParser parser(arguments);

auto& positionalOptionIn = parser.option<std::string>("INPUT_FILE");
auto& positionalOptionOut = parser.option<std::string>("OUTPUT_FILE");
parser.parse();

REQUIRE(positionalOptionIn.value() == "fileIn.txt");
REQUIRE(positionalOptionOut.value() == "fileOut.txt");
}

TEST_CASE("test_option_multiple_positional_interleaved_with_other_arguments")
{
std::vector<std::string> arguments{
"", "fileIn.txt", "-i1", "1", "fileOut.txt", "-i2", "2"};
clapp::ArgumentParser parser(arguments);

auto& positionalOptionIn = parser.option<std::string>("INPUT_FILE");
auto& positionalOptionOut = parser.option<std::string>("OUTPUT_FILE");
auto& intOption1 = parser.option<int>("-i1");
auto& intOption2 = parser.option<int>("-i2");
parser.parse();

REQUIRE(positionalOptionIn.value() == "fileIn.txt");
REQUIRE(positionalOptionOut.value() == "fileOut.txt");
REQUIRE(intOption1.value() == 1);
REQUIRE(intOption2.value() == 2);
}

TEST_CASE("test_option_multiple_positional_with_required_arguments")
{
std::vector<std::string> arguments{"", "fileIn.txt"};
clapp::ArgumentParser parser(arguments);

parser.option<std::string>("INPUT_FILE");
parser.option<int>("-i").required();

REQUIRE_THROWS(parser.parse());
}

TEST_CASE("test_option_multiple_positional_required")
{
std::vector<std::string> arguments{"", "-a", "test"};
clapp::ArgumentParser parser(arguments);

parser.option<std::string>("INPUT_FILE").required();

REQUIRE_THROWS(parser.parse());
}

TEST_CASE("test_option_multiple_positional_required_empty")
{
std::vector<std::string> arguments{""};
clapp::ArgumentParser parser(arguments);

parser.option<std::string>("INPUT_FILE").required();

REQUIRE_FALSE(parser.parse());
}

0 comments on commit 1916a37

Please sign in to comment.