Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cpp-argon-demo
24 changes: 5 additions & 19 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,6 @@ parser.version("alpha")
> * [Argument Groups](#argument-groups) - organize related optional arguments into sections and optionally enforce usage rules.
> * [Subparsers](#subparsers) - create hierarchical CLI subcommand structures.

> [!TIP]
> You can specify the program version using a string (like in the example above) or using the `argon::version` structure:
> ```cpp
> parser.version({0u, 0u, 0u})
> parser.version({ .major = 1u, .minor = 1u, .patch = 1u });
> argon::version ver{2u, 2u, 2u};
> parser.version(ver);
> ```
>
> **NOTE:** The `argon::version` struct
> * contains the three members - `major`, `minor`, `patch` - all of which are of type `std::uint32_t`,
> * defines a `std::string str() const` method which returns a `v{major}.{minor}.{path}` version string,
> * defines the `std::ostream& operator<<` for stream insertion.

### Dynamic Program Name

If you prefer not to hardcode the program name, you can instruct the parser to dynamically deduce it from the command-line input (specifically `argv[0]`) by initializing it with the `argon::dynamic_name` tag:
Expand Down Expand Up @@ -427,7 +413,7 @@ parser.add_optional_argument("version", "v").suppress_arg_checks();

parser.parse_args(argc, argv);

if (parser.count("version")) {
if (parser.is_used("version")) {
std::cout << PROJECT_VERSION << std::endl;
std::exit(EXIT_SUCCESS);
}
Expand Down Expand Up @@ -832,7 +818,7 @@ parser.default_arguments(<args>);
parser.print_version();
std::exit(EXIT_SUCCESS);
})
.help("Display program version info");
.help("Display program's version info");
```

- `o_input` and `o_multi_input`:
Expand Down Expand Up @@ -1103,7 +1089,7 @@ int main(int argc, char* argv[]) {
argon::argument_parser parser("some-program");

// define the parser's attributes and default arguments
parser.version({0u, 0u, 0u})
parser.version("v0.1.0")
.description("The program does something with command-line arguments")
.default_arguments(argon::default_argument::o_help);

Expand Down Expand Up @@ -1647,7 +1633,7 @@ For example:
```cpp
// top-level parser
argon::argument_parser git("ap-git");
git.version({.major = 2u, .minor = 43u, .patch = 0u})
git.version("v0.1.0")
.description("A version control system built with CPP-ARGON")
.default_arguments(argon::default_argument::o_help, argon::default_argument::o_version);

Expand Down Expand Up @@ -1686,7 +1672,7 @@ All defined subparsers will be included in the parent parser's help message:

```txt
> ap-git --help
Program: ap-git (v2.43.0)
Program: ap-git v0.1.0

A version control system built with CPP-ARGON

Expand Down
45 changes: 20 additions & 25 deletions include/argon/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ enum class default_argument : std::uint8_t {
* arg_parser.print_version();
* std::exit(EXIT_SUCCESS);
* })
* .help("Dsiplay program version info");
* .help("Dsiplay program's version info");
* @endcode
*/
o_version,
Expand Down Expand Up @@ -154,7 +154,7 @@ void add_default_argument(const default_argument, argument_parser&) noexcept;
* int main(int argc, char* argv[]) {
* // Create the argument parser instance
* argon::argument_parser parser("fcopy");
* parser.version({ .major = 1, .minor = 0, .patch = 0 })
* parser.version("v0.1.0")
* .description("A simple file copy utility.")
* .default_arguments(
* argon::default_argument::o_help,
Expand Down Expand Up @@ -215,28 +215,20 @@ class argument_parser {
* @brief Set the program version.
* @param version The version of the program.
* @return Reference to the argument parser.
*/
argument_parser& version(const version& version) noexcept {
this->_version.emplace(version.str());
return *this;
}

/**
* @brief Set the program version.
* @param version The version of the program.
* @return Reference to the argument parser.
* @throws argon::invalid_configuration if the given version string contains whitespaces.
*/
argument_parser& version(std::string_view version) {
if (util::contains_whitespaces(version))
throw invalid_configuration("The program version cannot contain whitespace characters!"
);
for (const char c : version)
if (std::iscntrl(static_cast<unsigned char>(c)))
throw invalid_configuration("The program version cannot contain control characters "
"(line breaks, tabs, etc.)!");

this->_version.emplace(version);
this->_version = version;
return *this;
}

/// @return The program version if set, std::nullopt otherwise.
[[nodiscard]] const std::optional<std::string>& version() const noexcept {
/// @return The program version if set, empty string otherwise.
[[nodiscard]] const std::string& version() const noexcept {
return this->_version;
}

Expand Down Expand Up @@ -983,8 +975,8 @@ class argument_parser {
*/
void print_help(const bool verbose, std::ostream& os = std::cout) const noexcept {
os << "Program: " << this->_program_name;
if (this->_version)
os << " (" << this->_version.value() << ')';
if (not this->_version.empty())
os << ' ' << this->_version;
os << '\n';

if (not this->_description.empty())
Expand All @@ -998,13 +990,16 @@ class argument_parser {
/**
* @brief Prints the argument parser's version info to an output stream.
*
* If no version was spcified for the parser, `unspecified` will be printed.
* If no version was specified for the parser, `(version unspecified)` will be printed.
*
* @param os The output stream.
*/
void print_version(std::ostream& os = std::cout) const noexcept {
os << this->_program_name << " : version " << this->_version.value_or("unspecified")
<< std::endl;
os << this->_program_name;
if (this->_version.empty())
os << " (version unspecified)" << std::endl;
else
os << ' ' << this->_version << std::endl;
}

/**
Expand Down Expand Up @@ -1634,7 +1629,7 @@ class argument_parser {
std::string _program_name =
""; // The name of the program in the format "<parent-parser-names>... <program-name>".
std::string _description = "";
std::optional<std::string> _version = std::nullopt;
std::string _version = "";

unknown_policy _unknown_policy = unknown_policy::fail;

Expand Down Expand Up @@ -1727,7 +1722,7 @@ inline void add_default_argument(
arg_parser.print_version();
std::exit(EXIT_SUCCESS);
})
.help("Dsiplay program version info");
.help("Dsiplay program's version info");
break;

case default_argument::o_input:
Expand Down
18 changes: 0 additions & 18 deletions include/argon/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,4 @@ struct dynamic_name_t {
/// @brief Tag value that enables dynamic deduction of the program name.
inline constexpr dynamic_name_t dynamic_name{};

/// @brief A helper structure used to represent a program's version.
struct version {
std::uint32_t major = 0u; ///< The major version number.
std::uint32_t minor = 0u; ///< The minor version number.
std::uint32_t patch = 0u; ///< The patch number.

/// @brief Converts the structure into a string in the `v{major}.{minor}.{path}` format
[[nodiscard]] std::string str() const noexcept {
return std::format("v{}.{}.{}", this->major, this->minor, this->patch);
}

/// @brief The stream insertion operator.
friend std::ostream& operator<<(std::ostream& os, const version& v) {
os << v.str();
return os;
}
};

} // namespace argon
41 changes: 17 additions & 24 deletions tests/source/test_argument_parser_cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ TEST_SUITE_BEGIN("test_argument_parser_cfg");

struct test_argument_parser_cfg : public argument_parser_test_fixture {
const std::string test_description = "test program description";
const argon::version test_version{1u, 2u, 3u};
const std::string test_str_version = "alpha";
const std::string test_version = "v1.2.3";
};

TEST_CASE("argument_parser() should throw if the name is empty") {
Expand Down Expand Up @@ -49,31 +48,29 @@ TEST_CASE_FIXTURE(

// --- version ---

TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() getter should return nullopt by default") {
CHECK_FALSE(sut.version());
TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should update the version member") {
CHECK(sut.version().empty()); // default value

sut.version(test_version);
CHECK_EQ(sut.version(), test_version);
}

TEST_CASE_FIXTURE(
test_argument_parser_cfg,
"version() setter should throw if the version string contains whitespaces"
"version() setter should throw if the version string contains control characters"
) {
CHECK_THROWS_WITH_AS(
sut.version("invalid version"),
"The program version cannot contain whitespace characters!",
invalid_configuration
);
const std::string expected_msg =
"The program version cannot contain control characters (line breaks, tabs, etc.)!";

CHECK_THROWS_WITH_AS(sut.version("1.0\nAlpha"), expected_msg.c_str(), invalid_configuration);
CHECK_THROWS_WITH_AS(sut.version("1.0\tAlpha"), expected_msg.c_str(), invalid_configuration);
}

TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should update the version member") {
sut.version(test_version);
auto stored_version = sut.version();
REQUIRE(stored_version);
CHECK_EQ(stored_version.value(), test_version.str());
TEST_CASE_FIXTURE(test_argument_parser_cfg, "version() setter should allow standard spaces") {
const std::string version_with_spaces = "v0.1.0 alpha";

sut.version(test_str_version);
stored_version = sut.version();
REQUIRE(stored_version);
CHECK_EQ(stored_version.value(), test_str_version);
REQUIRE_NOTHROW(sut.version(version_with_spaces));
CHECK_EQ(sut.version(), version_with_spaces);
}

// --- description ---
Expand All @@ -88,11 +85,7 @@ TEST_CASE_FIXTURE(
test_argument_parser_cfg, "description() setter should update the description member"
) {
sut.description(test_description);

const auto stored_description = sut.description();

REQUIRE_FALSE(stored_description.empty());
CHECK_EQ(stored_description, test_description);
CHECK_EQ(sut.description(), test_description);
}

// --- is_verbose ---
Expand Down
Loading