feat(cli): add command-line argument parser
This commit is contained in:
@@ -7,6 +7,7 @@ add_library(iZo::izo ALIAS izo)
|
||||
|
||||
target_sources(izo
|
||||
PRIVATE
|
||||
src/command_line.cpp
|
||||
src/dialogs.cpp
|
||||
src/directory_watcher.cpp
|
||||
src/time.cpp
|
||||
@@ -89,6 +90,7 @@ if(IZO_BUILD_TESTS)
|
||||
set_tests_properties(${name} PROPERTIES TIMEOUT 10)
|
||||
endfunction()
|
||||
izo_add_test(environment_paths)
|
||||
izo_add_test(command_line)
|
||||
izo_add_test(system_time_debug)
|
||||
izo_add_test(dynamic_library)
|
||||
izo_add_test(process)
|
||||
|
||||
39
include/izo/CommandLine.hpp
Normal file
39
include/izo/CommandLine.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace izo {
|
||||
|
||||
class CommandLine {
|
||||
public:
|
||||
CommandLine(int argc, const char* const argv[]);
|
||||
CommandLine(int argc, char* argv[]);
|
||||
|
||||
// Has reports whether an option was supplied, including --name=value.
|
||||
bool Has(std::string_view name) const noexcept;
|
||||
|
||||
// Get returns the last value supplied as --name value or --name=value.
|
||||
// A flag without a value and a missing option both return std::nullopt.
|
||||
std::optional<std::string> Get(std::string_view name) const;
|
||||
std::string Get(std::string_view name, std::string_view fallback) const;
|
||||
int GetInt(std::string_view name, int fallback) const;
|
||||
|
||||
// Arguments not consumed as option values. argv[0] is never included.
|
||||
const std::vector<std::string>& Positionals() const noexcept { return positionals_; }
|
||||
|
||||
private:
|
||||
struct Option {
|
||||
std::string name;
|
||||
std::optional<std::string> value;
|
||||
};
|
||||
|
||||
void Parse(int argc, const char* const argv[]);
|
||||
|
||||
std::vector<Option> options_;
|
||||
std::vector<std::string> positionals_;
|
||||
};
|
||||
|
||||
} // namespace izo
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <izo/Clipboard.hpp>
|
||||
#include <izo/CommandLine.hpp>
|
||||
#include <izo/Debug.hpp>
|
||||
#include <izo/Dialogs.hpp>
|
||||
#include <izo/DirectoryWatcher.hpp>
|
||||
|
||||
68
src/command_line.cpp
Normal file
68
src/command_line.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include <izo/CommandLine.hpp>
|
||||
|
||||
#include <charconv>
|
||||
#include <utility>
|
||||
|
||||
namespace izo {
|
||||
|
||||
CommandLine::CommandLine(int argc, const char* const argv[]) { Parse(argc, argv); }
|
||||
|
||||
CommandLine::CommandLine(int argc, char* argv[]) {
|
||||
std::vector<const char*> arguments;
|
||||
if (argc > 0) arguments.reserve(static_cast<std::size_t>(argc));
|
||||
for (int index = 0; index < argc; ++index) arguments.push_back(argv ? argv[index] : nullptr);
|
||||
Parse(argc, arguments.data());
|
||||
}
|
||||
|
||||
void CommandLine::Parse(int argc, const char* const argv[]) {
|
||||
bool options_ended = false;
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
const std::string argument = argv && argv[index] ? argv[index] : "";
|
||||
if (!options_ended && argument == "--") {
|
||||
options_ended = true;
|
||||
continue;
|
||||
}
|
||||
if (!options_ended && argument.size() > 2 && argument.compare(0, 2, "--") == 0) {
|
||||
const auto equals = argument.find('=');
|
||||
Option option;
|
||||
option.name = argument.substr(0, equals);
|
||||
if (equals != std::string::npos) {
|
||||
option.value = argument.substr(equals + 1);
|
||||
} else if (index + 1 < argc && argv[index + 1] &&
|
||||
std::string_view(argv[index + 1]).compare(0, 2, "--") != 0) {
|
||||
option.value = argv[++index];
|
||||
}
|
||||
options_.push_back(std::move(option));
|
||||
} else {
|
||||
positionals_.push_back(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CommandLine::Has(std::string_view name) const noexcept {
|
||||
for (const auto& option : options_)
|
||||
if (option.name == name) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> CommandLine::Get(std::string_view name) const {
|
||||
for (auto option = options_.rbegin(); option != options_.rend(); ++option)
|
||||
if (option->name == name) return option->value;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string CommandLine::Get(std::string_view name, std::string_view fallback) const {
|
||||
const auto value = Get(name);
|
||||
return value ? *value : std::string(fallback);
|
||||
}
|
||||
|
||||
int CommandLine::GetInt(std::string_view name, int fallback) const {
|
||||
const auto value = Get(name);
|
||||
if (!value || value->empty()) return fallback;
|
||||
int parsed = 0;
|
||||
const auto result = std::from_chars(value->data(), value->data() + value->size(), parsed);
|
||||
return result.ec == std::errc{} && result.ptr == value->data() + value->size()
|
||||
? parsed : fallback;
|
||||
}
|
||||
|
||||
} // namespace izo
|
||||
34
tests/command_line.cpp
Normal file
34
tests/command_line.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "test_support.hpp"
|
||||
|
||||
#include <izo/CommandLine.hpp>
|
||||
|
||||
int main() {
|
||||
const char* arguments[] = {
|
||||
"program", "--verbose", "--config", "settings.json", "input.txt",
|
||||
"--threads=8", "--threads", "12", "--invalid", "many", "--empty=",
|
||||
"--", "--literal", "output.txt",
|
||||
};
|
||||
izo::CommandLine args(static_cast<int>(sizeof(arguments) / sizeof(arguments[0])), arguments);
|
||||
|
||||
CHECK(args.Has("--verbose"));
|
||||
CHECK(!args.Has("--missing"));
|
||||
CHECK(!args.Get("--verbose"));
|
||||
CHECK(args.Get("--config") == "settings.json");
|
||||
CHECK(args.Get("--missing", "fallback") == "fallback");
|
||||
CHECK(args.GetInt("--threads", 4) == 12);
|
||||
CHECK(args.GetInt("--invalid", 4) == 4);
|
||||
CHECK(args.GetInt("--empty", 4) == 4);
|
||||
CHECK(args.Get("--empty") == "");
|
||||
CHECK(args.Positionals().size() == 3);
|
||||
CHECK(args.Positionals()[0] == "input.txt");
|
||||
CHECK(args.Positionals()[1] == "--literal");
|
||||
CHECK(args.Positionals()[2] == "output.txt");
|
||||
|
||||
const char* negative[] = {"program", "--count", "-2"};
|
||||
CHECK(izo::CommandLine(3, negative).GetInt("--count", 0) == -2);
|
||||
|
||||
char program[] = "program";
|
||||
char flag[] = "--flag";
|
||||
char* mutable_arguments[] = {program, flag};
|
||||
CHECK(izo::CommandLine(2, mutable_arguments).Has("--flag"));
|
||||
}
|
||||
Reference in New Issue
Block a user