YesChief!
C++ argv parser
Loading...
Searching...
No Matches
yeschief.h
1/*
2 * MIT License
3 *
4 * Copyright (c) 2025-Present Kevin Traini
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24#pragma once
25#ifndef YESCHIEF_H
26#define YESCHIEF_H
32
33#include <any>
34#include <cassert>
35#include <expected>
36#include <iostream>
37#include <map>
38#include <memory>
39#include <optional>
40#include <regex>
41#include <string>
42#include <vector>
43
44#define assert_message(expr, message) assert((message, expr))
45
46namespace yeschief {
47class CLI;
48class OptionGroup;
49class Command;
50class CLIResults;
51
75
79enum class FaultType {
83 InvalidArgs,
87 UnrecognizedOption,
91 MissingRequiredOption,
95 InvalidOptionType,
99 MissingOptionValue,
103 UnknownCommand,
104};
105
109typedef struct Fault {
117 FaultType type;
118} Fault;
119
145
149class OptionGroup final {
150 friend class CLI;
151
152 public:
157 explicit OptionGroup(CLI *parent, std::string name);
158
174 template<typename T = bool>
175 auto
176 addOption(const std::string &name, const std::string &description, const OptionConfiguration &configuration = {})
177 -> OptionGroup &;
178
179 private:
180 CLI *_parent;
181 std::string _name;
183
184 auto addOption(const std::shared_ptr<Option> &option) -> void;
185};
186
190class CLI final {
191 friend class OptionGroup;
192 friend class HelpCommand;
193
194 enum class Mode { OPTIONS, COMMANDS };
195
196 public:
201 CLI(std::string name, std::string description);
202
218 template<typename T = bool>
219 auto
220 addOption(const std::string &name, const std::string &description, const OptionConfiguration &configuration = {})
221 -> CLI &;
222
230 [[nodiscard]] auto addGroup(const std::string &name) -> OptionGroup &;
231
241 auto addCommand(Command *command) -> CLI &;
242
252 template<typename... Tail> auto parsePositional(const std::string &option_name, Tail &&...options) -> void;
253
269 auto run(int argc, char **argv) const -> std::expected<CLIResults, Fault>;
270
310 auto help(std::ostream &out = std::cout) const -> void;
311
312 private:
313 std::string _name;
314 std::string _description;
316 std::map<std::string, OptionGroup> _groups;
317 std::map<std::string, std::shared_ptr<const Option>> _options;
318 std::vector<std::string> _positional_options;
319 std::map<std::string, Command *> _commands;
320 std::map<std::string, CLI> _commands_cli;
321
322 [[nodiscard]] auto buildUsageHelp() const -> std::string;
323
324 [[nodiscard]] auto buildPositionalHelp() const -> std::string;
325
326 [[nodiscard]] static auto buildOptionUsageHelp(const std::shared_ptr<const Option> &option) -> std::string;
327
328 static auto checkOptionType(const std::type_info &type) -> void;
329
330 [[nodiscard]] static auto
331 getValueForOption(const std::shared_ptr<const Option> &option, const std::vector<std::string> &values)
332 -> std::expected<std::any, Fault>;
333
334 template<typename T = bool>
335 auto addOption(
336 const std::string &name,
337 const std::string &description,
338 const std::string &group_name,
339 const OptionConfiguration &configuration
340 ) -> CLI &;
341
342 auto parsePositional() -> void {
343 // Nothing to do here
344 }
345};
346
350class Command {
351 public:
355 [[nodiscard]] virtual auto getName() const -> std::string = 0;
359 [[nodiscard]] virtual auto getDescription() const -> std::string {
360 return "";
361 }
362
369 virtual auto setup(CLI &cli) -> void {}
370
379 virtual auto run(const CLIResults &results) -> int = 0;
380
381 virtual ~Command() = default;
382};
383
387class CLIResults final {
388 public:
392 explicit CLIResults(const std::map<std::string, std::any> &values);
393
401 [[nodiscard]] auto get(const std::string &option) const -> std::optional<std::any>;
402
403 private:
404 std::map<std::string, std::any> _values;
405};
406
413class HelpCommand final : public Command {
414 public:
415 explicit HelpCommand(CLI *cli): _cli(cli) {}
416
417 [[nodiscard]] auto getName() const -> std::string override {
418 return "help";
419 }
420
421 [[nodiscard]] auto getDescription() const -> std::string override {
422 return "Display this help message\n"
423 "When COMMAND is given, display help for this command";
424 }
425
426 auto setup(CLI &cli) -> void override;
427
428 auto run(const CLIResults &results) -> int override;
429
430 private:
431 CLI *_cli;
432};
433} // namespace yeschief
434
435template<typename T>
437 const std::string &name, const std::string &description, const OptionConfiguration &configuration
438) -> OptionGroup & {
439 _parent->addOption<T>(name, description, _name, configuration);
440 return *this;
441}
442
443template<typename T>
445 const std::string &name, const std::string &description, const OptionConfiguration &configuration
446) -> CLI & {
447 return addOption<T>(name, description, "", configuration);
448}
449
450template<typename T>
452 const std::string &name,
453 const std::string &description,
454 const std::string &group_name,
455 const OptionConfiguration &configuration
456) -> CLI & {
457 assert_message(
458 ! _mode.has_value() || _mode.value() != Mode::COMMANDS, "Cannot add an option group to a cli using commands"
459 );
460 _mode = Mode::OPTIONS;
461
462 assert_message(! _options.contains(name), "CLI has already this option");
463 assert_message(_groups.contains(group_name), "Option group does not exist");
464
465 std::string long_name = name;
466 std::string short_name;
467 const std::regex name_regex("(.*),(.*)");
468 if (std::smatch match; std::regex_match(name, match, name_regex)) {
469 if (match.size() == 3) {
470 long_name = match[1].str();
471 short_name = match[2].str();
472 assert_message(
473 short_name.length() == 1 && isalpha(short_name[0]), "Short name of an option can be only one letter"
474 );
475 }
476 }
477
478 const auto &type_info = typeid(T);
479 checkOptionType(type_info);
480 const auto option = std::make_shared<Option>(long_name, short_name, description, type_info, configuration);
481 _options.emplace(long_name, option);
482 _groups.at(group_name).addOption(option);
483
484 return *this;
485}
486
487template<typename... Tail>
488auto yeschief::CLI::parsePositional(const std::string &option_name, Tail &&...options) -> void {
489 assert_message(_options.contains(option_name), "Option doesn't exists");
490 const auto option = _options.at(option_name);
491 if (! _positional_options.empty()) {
492 const auto last_option_name = _positional_options[_positional_options.size() - 1];
493 const auto last_option = _options.at(last_option_name);
494 const auto &last_option_type = last_option->type;
495 assert_message(
496 last_option_type != typeid(std::vector<int>) && last_option_type != typeid(std::vector<float>)
497 && last_option_type != typeid(std::vector<double>),
498 "Cannot add a new positional argument after one with a list type"
499 );
500 assert_message(
501 ! option->configuration.required || last_option->configuration.required,
502 "Option is required but is placed after a non required one"
503 );
504 }
505
506 _positional_options.push_back(option_name);
507 parsePositional(std::forward<Tail>(options)...);
508}
509
510#endif // YESCHIEF_H
T ostream
T string(T... args)
Definition yeschief.h:387
CLIResults(const std::map< std::string, std::any > &values)
auto get(const std::string &option) const -> std::optional< std::any >
Definition yeschief.h:190
auto addCommand(Command *command) -> CLI &
auto parsePositional(const std::string &option_name, Tail &&...options) -> void
Definition yeschief.h:488
auto addOption(const std::string &name, const std::string &description, const OptionConfiguration &configuration={}) -> CLI &
Definition yeschief.h:444
CLI(std::string name, std::string description)
auto addGroup(const std::string &name) -> OptionGroup &
auto run(int argc, char **argv) const -> std::expected< CLIResults, Fault >
auto help(std::ostream &out=std::cout) const -> void
Definition yeschief.h:350
virtual auto run(const CLIResults &results) -> int=0
virtual auto getDescription() const -> std::string
Definition yeschief.h:359
virtual auto setup(CLI &cli) -> void
Definition yeschief.h:369
virtual auto getName() const -> std::string=0
auto getDescription() const -> std::string override
Definition yeschief.h:421
auto setup(CLI &cli) -> void override
auto getName() const -> std::string override
Definition yeschief.h:417
auto run(const CLIResults &results) -> int override
Definition yeschief.h:149
auto addOption(const std::string &name, const std::string &description, const OptionConfiguration &configuration={}) -> OptionGroup &
Definition yeschief.h:436
OptionGroup(CLI *parent, std::string name)
T cout
T expected
T nullopt
T optional
Definition yeschief.h:109
FaultType type
Definition yeschief.h:117
std::string message
Definition yeschief.h:113
Definition yeschief.h:55
std::string value_help
Definition yeschief.h:65
std::optional< std::any > default_value
Definition yeschief.h:69
bool required
Definition yeschief.h:61
std::optional< std::any > implicit_value
Definition yeschief.h:73
Definition yeschief.h:123
std::string short_name
Definition yeschief.h:131
const std::type_info & type
Definition yeschief.h:139
std::string name
Definition yeschief.h:127
std::string description
Definition yeschief.h:135
OptionConfiguration configuration
Definition yeschief.h:143
T vector