koutil
Loading...
Searching...
No Matches
command_impl.h
Go to the documentation of this file.
1#ifndef KOUTIL_ARGS_COMMAND_IMPL_H
2#define KOUTIL_ARGS_COMMAND_IMPL_H
3
6#include "koutil/args/help.h"
7
9
10#include <cstddef>
11#include <cstdint>
12#include <format>
13#include <optional>
14#include <ostream>
15#include <span>
16#include <string>
17#include <string_view>
18#include <unordered_map>
19#include <utility>
20#include <vector>
21
22namespace koutil::args {
23
24template <extends_result Result> bool command_t<Result>::add_command(command_t&& command) {
25 if (m_cmd_map.contains(command.name())) {
26 return false;
27 }
28
29 command.m_path = m_path;
30 command.m_path += ' ';
31 command.m_path += m_name;
32
33 const std::uint32_t id = m_commands.size();
34 m_cmd_map.emplace(command.name(), id);
35
36 m_commands.emplace_back(std::move(command));
37
38 return true;
39}
40
41template <extends_result Result> bool command_t<Result>::add_option(const option_t& option) {
42 const auto long_name = option.long_name();
43 const auto short_name = option.short_name();
44
45 if ((long_name.has_value() && m_long_options.contains(*long_name))
46 || (short_name.has_value() && m_short_options.contains(*short_name))) {
47 return false;
48 }
49
50 const std::size_t index = m_options.size();
51
52 if (long_name.has_value()) {
53 m_long_options[*long_name] = index;
54 }
55
56 if (short_name.has_value()) {
57 m_short_options[*short_name] = index;
58 }
59
60 m_options.push_back(option);
61
62 return true;
63}
64
65template <extends_result Result> void command_t<Result>::add_argument(const argument_t& argument) {
66 m_arguments.push_back(argument);
67}
68
69template <extends_result Result>
70void command_t<Result>::process(const char* const* args, std::uint32_t argc, result_t& result) {
71 process(std::span<const char* const>(args, argc), result);
72}
73
74template <extends_result Result> void command_t<Result>::process(std::span<const char* const> args, result_t& result) {
75 std::uint32_t arguments = 0;
76
77 const std::uint32_t needed_arguments = m_arguments.size();
78
79 auto create_argument_error = [this, needed_arguments](std::uint32_t provided) -> std::string {
80 if (m_path.empty()) {
81 return error::make_argument_count(m_name, needed_arguments, provided);
82 }
83
84 return error::make_command_argument_count(m_name, needed_arguments, provided);
85 };
86
87 bool only_arguments = false;
88 for (std::uint32_t i = 0; i < args.size(); ++i) {
89 if (result.need_exit()) {
90 return;
91 }
92
93 std::string_view arg { args[i] };
94
95 if (!only_arguments && arg == "--") {
96 only_arguments = true;
97 continue;
98 }
99
100 if (!only_arguments) {
101 if (arg.starts_with('-')) {
102 i = process_option(arg, args, i, result);
103 continue;
104 }
105
106 auto cmd_it = m_cmd_map.find(arg);
107 if (cmd_it != m_cmd_map.end()) {
108 if (needed_arguments != arguments) {
109 result.add_error(create_argument_error(arguments));
110 }
112 check_options(result);
113
114 auto rest = args.subspan(i + 1);
115
116 auto& cmd = m_commands[cmd_it->second];
117 if (cmd.m_handle) {
118 cmd.m_handle(rest, result);
120
121 cmd.process(rest, result);
122 return;
123 }
124 }
125
126 if (arguments < needed_arguments) {
127 m_arguments[arguments].process(arg, result);
128
129 arguments += 1;
130 } else {
131 result.add_error(error::make_unknown_argument(arg));
132 }
133 }
134
135 if (result.need_exit()) {
136 return;
137 }
138
139 if (needed_arguments != arguments) {
140 result.add_error(create_argument_error(arguments));
141 }
143 check_options(result);
144}
145
146template <extends_result Result>
148 std::string_view name, std::span<const char* const> args, std::uint32_t index, result_t& result
149) {
150 using namespace std::string_view_literals;
151
152 std::string_view arg = name;
153
154 // removes first '-'
155 name.remove_prefix(1);
156
157 auto value_start = arg.find('=');
158 bool has_value = value_start != std::string_view::npos;
159
160 std::string_view whole_name = arg;
161 if (has_value) {
162 whole_name = whole_name.substr(0, value_start);
163 }
164
165 std::uint32_t option_id;
166
167 // long option
168 if (name.starts_with('-')) {
169
170 if (has_value) {
171 name = name.substr(0, value_start - 1);
172 }
173
174 // removes second '-'
175 std::string_view option_name = name.substr(1);
176
177 auto option_it = m_long_options.find(option_name);
178 if (option_it == m_long_options.end()) {
179 result.add_error(error::make_unknown_long_option(option_name));
180 return index;
181 }
182
183 option_id = option_it->second;
184
185 } else {
186 if (has_value) {
187 name = name.substr(0, value_start);
188 }
189
190 if (name.size() != 1) {
191 result.add_error(error::make_invalid_short_option(name));
192 return index;
193 }
194
195 auto option_it = m_short_options.find(name[0]);
196 if (option_it == m_short_options.end()) {
197 result.add_error(error::make_unknown_short_option(name[0]));
198 return index;
199 }
200
201 option_id = option_it->second;
202 }
203
204 auto& option = m_options[option_id];
205
206 if (!option.has_value()) {
207 if (has_value) {
208 result.add_error(error::make_unexpected_option_value(whole_name));
209 return index;
210 }
211
212 option.process(std::nullopt, result);
213 return index;
214 }
215
216 std::string_view value;
217
218 if (has_value) {
219 value = arg.substr(value_start + 1);
220 } else if (args.size() > index + 1) {
221 index += 1;
222 value = args[index];
223 } else {
224 result.add_error(error::make_option_requires_value(whole_name));
225 return index;
226 }
227
228 option.process(value, result);
229 return index;
230}
231
232template <extends_result Result> void command_t<Result>::check_options(result_t& result) {
233 for (const auto& option : m_options) {
234 if (option.required() && !option.used()) {
235 result.add_error(error::make_missing_required_option(option));
236 }
237 }
238}
239
240template <extends_result Result> void command_t<Result>::show_help(std::ostream& out, std::size_t terminal_size) const {
241
242 help_printer_t printer(*this, terminal_size);
243 printer.print(out);
244}
245
246template <extends_result Result> void command_t<Result>::clear_used() {
247 for (auto& option : m_options) {
248 option.clear_used();
249 }
250
251 for (auto& cmd : m_commands) {
252 cmd.clear_used();
253 }
254}
255
256}
257
258#endif
Represents a command in the command-line interface.
Definition help.h:15
void check_options(result_t &result)
Definition command_impl.h:232
void clear_used()
Clears state.
Definition command_impl.h:246
option_t< result_t > option_t
Definition command.h:33
Result result_t
Definition command.h:32
bool add_option(const option_t &option)
Adds an option to the command.
Definition command_impl.h:41
void add_argument(const argument_t &argument)
Adds a positional argument.
Definition command_impl.h:65
argument_t< result_t > argument_t
Definition command.h:34
bool add_command(command_t &&command)
Adds a subcommand.
Definition command_impl.h:24
void process(const char *const *args, std::uint32_t argc, result_t &result)
Processes command-line arguments.
Definition command_impl.h:70
void show_help(std::ostream &out, std::size_t terminal_size=80) const
Displays help text for this command.
Definition command_impl.h:240
std::uint32_t process_option(std::string_view name, std::span< const char *const > args, std::uint32_t index, result_t &result)
Definition command_impl.h:147
Definition help.h:17
void print(std::ostream &out)
Definition help_impl.h:19
std::string make_invalid_short_option(std::string_view name)
Definition errors.h:36
std::string make_missing_required_option(const option_t< Result > &option)
Definition errors.h:48
std::string make_command_argument_count(std::string_view name, std::uint32_t expected, std::uint32_t provided)
Definition errors.h:24
std::string make_unknown_argument(std::string_view name)
Definition errors.h:28
std::string make_unexpected_option_value(std::string_view whole_name)
Definition errors.h:44
std::string make_unknown_short_option(char name)
Definition errors.h:34
std::string make_argument_count(std::string_view name, std::uint32_t expected, std::uint32_t provided)
Definition errors.h:13
std::string make_option_requires_value(std::string_view whole_name)
Definition errors.h:40
std::string make_unknown_long_option(std::string_view name)
Definition errors.h:30
Definition argument.h:9