Arcane  4.2.1.0
Developer documentation
Loading...
Searching...
No Matches
ProgramOptions.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* ProgramOptions.cc (C) 2000-2026 */
9/* */
10/* Program options parser implementation. */
11/*---------------------------------------------------------------------------*/
12
13#include "arccore/common/internal/ProgramOptions.h"
14
15#include <cctype>
16
17/*---------------------------------------------------------------------------*/
18/*---------------------------------------------------------------------------*/
19
20namespace Arcane::ProgramOptions
21{
22
23/*---------------------------------------------------------------------------*/
24/*---------------------------------------------------------------------------*/
25
26namespace
27{
28
29 void
30 _parseNameString(const std::string& name_str, std::string& long_name,
31 std::string& short_name, bool& has_short)
32 {
33 auto pos = name_str.find(',');
34 if (pos != std::string::npos) {
35 long_name = name_str.substr(0, pos);
36 short_name = name_str.substr(pos + 1);
37 has_short = true;
38 }
39 else {
40 long_name = name_str;
41 has_short = false;
42 }
43 }
44
45} // anonymous namespace
46
47/*---------------------------------------------------------------------------*/
48/*---------------------------------------------------------------------------*/
49
50options_description::
51options_description(const std::string& title)
52: m_title(title)
53{}
54
55/*---------------------------------------------------------------------------*/
56/*---------------------------------------------------------------------------*/
57
58options_description::options_proxy options_description::
59add_options()
60{
61 return options_proxy(*this);
62}
63
64/*---------------------------------------------------------------------------*/
65/*---------------------------------------------------------------------------*/
66
67void options_description::
68add_option(const std::string& name,
69 std::shared_ptr<option_value> value_semantic,
70 const std::string& description)
71{
72 option_descriptor opt;
73 _parseNameString(name, opt.long_name, opt.short_name, opt.has_short);
74 opt.description = description;
75 opt.value_semantic = std::move(value_semantic);
76 m_options.push_back(std::move(opt));
77}
78
79/*---------------------------------------------------------------------------*/
80/*---------------------------------------------------------------------------*/
81
82const option_descriptor* options_description::
83find(const std::string& name) const
84{
85 for (const auto& opt : m_options) {
86 if (opt.long_name == name)
87 return &opt;
88 }
89 return nullptr;
90}
91
92/*---------------------------------------------------------------------------*/
93/*---------------------------------------------------------------------------*/
94
95const option_descriptor* options_description::
96find_by_short(char short_name) const
97{
98 for (const auto& opt : m_options) {
99 if (opt.has_short && !opt.short_name.empty() && opt.short_name[0] == short_name)
100 return &opt;
101 }
102 return nullptr;
103}
104
105/*---------------------------------------------------------------------------*/
106/*---------------------------------------------------------------------------*/
107// options_proxy: no-value option (bare flag, e.g. ("help,h", "description"))
108options_description::options_proxy& options_description::options_proxy::
109operator()(const char* name, const char* description)
110{
111 m_desc.add_option(name, std::make_shared<untyped_value>(), description);
112 return *this;
113}
114
115/*---------------------------------------------------------------------------*/
116/*---------------------------------------------------------------------------*/
117// options_proxy: bool option with description
118options_description::options_proxy& options_description::options_proxy::
119operator()(const char* name,
120 const typed_value<bool>* value,
121 const char* description)
122{
123 m_desc.add_option(name,
124 std::shared_ptr<option_value>(const_cast<typed_value<bool>*>(value)),
125 description);
126 return *this;
127}
128
129/*---------------------------------------------------------------------------*/
130/*---------------------------------------------------------------------------*/
131// options_proxy: bool option without description
132options_description::options_proxy& options_description::options_proxy::
133operator()(const char* name,
134 const typed_value<bool>* value)
135{
136 m_desc.add_option(name,
137 std::shared_ptr<option_value>(const_cast<typed_value<bool>*>(value)),
138 std::string());
139 return *this;
140}
141
142/*---------------------------------------------------------------------------*/
143/*---------------------------------------------------------------------------*/
144
145std::ostream&
146operator<<(std::ostream& os, const options_description& desc)
147{
148 if (!desc.m_title.empty())
149 os << desc.m_title << ":\n";
150
151 for (const auto& opt : desc.m_options) {
152 os << " ";
153
154 if (opt.has_short && !opt.short_name.empty()) {
155 if (opt.short_name.size() == 1 && std::isalnum(static_cast<unsigned char>(opt.short_name[0])))
156 os << "-" << opt.short_name << " ";
157 else
158 os << "--" << opt.short_name << " ";
159 }
160
161 os << "[--" << opt.long_name;
162
163 if (opt.value_semantic && !opt.value_semantic->is_bool_switch())
164 os << " arg";
165
166 os << "]";
167
168 if (opt.value_semantic && opt.value_semantic->has_default()) {
169 auto ds = opt.value_semantic->default_string();
170 if (!ds.empty())
171 os << " (=" << ds << ")";
172 }
173
174 if (!opt.description.empty())
175 os << "\t" << opt.description;
176
177 os << "\n";
178 }
179
180 return os;
181}
182
183/*---------------------------------------------------------------------------*/
184/*---------------------------------------------------------------------------*/
185
186positional_options_description& positional_options_description::
187add(const std::string& name, int count)
188{
189 m_options.emplace_back(name, count);
190 return *this;
191}
192
193/*---------------------------------------------------------------------------*/
194/*---------------------------------------------------------------------------*/
195
196void variables_map::
197add(const std::string& name, variable_value value)
198{
199 m_values[name] = std::move(value);
200}
201
202/*---------------------------------------------------------------------------*/
203/*---------------------------------------------------------------------------*/
204
205bool variables_map::
206count(const std::string& name) const
207{
208 return m_values.find(name) != m_values.end();
209}
210
211/*---------------------------------------------------------------------------*/
212/*---------------------------------------------------------------------------*/
213
214const variables_map::variable_value& variables_map::
215operator[](const std::string& name) const
216{
217 static variable_value empty_value;
218 auto it = m_values.find(name);
219 if (it != m_values.end())
220 return it->second;
221 return empty_value;
222}
223
224/*---------------------------------------------------------------------------*/
225/*---------------------------------------------------------------------------*/
226
227void variables_map::
228set_semantic(const std::string& name,
229 std::shared_ptr<option_value> semantic)
230{
231 auto it = m_values.find(name);
232 if (it != m_values.end())
233 it->second.set_semantic(std::move(semantic));
234}
235
236/*---------------------------------------------------------------------------*/
237/*---------------------------------------------------------------------------*/
247parsed_options command_line_parser::
248run()
249{
250 parsed_options result(m_desc);
251
252 if (!m_desc)
253 return result;
254
255 std::vector<std::string> positional_args;
256 bool end_of_options = false;
257
258 for (int i = 1; i < m_argc; ++i) {
259 std::string token(m_argv[i]);
260
261 if (end_of_options || token.empty() || token[0] != '-') {
262 positional_args.push_back(token);
263 continue;
264 }
265
266 // End-of-options marker
267 if (token == "--") {
268 end_of_options = true;
269 continue;
270 }
271
272 if (token.size() >= 2 && token[1] == '-') {
273 // Long option: --name or --name=value
274 std::string name;
275 std::string value;
276 bool has_eq_value = false;
277
278 auto eq_pos = token.find('=', 2);
279 if (eq_pos != std::string::npos) {
280 name = token.substr(2, eq_pos - 2);
281 value = token.substr(eq_pos + 1);
282 has_eq_value = true;
283 }
284 else {
285 name = token.substr(2);
286 }
287
288 if (name.empty())
289 continue;
290
291 const auto* opt = m_desc->find(name);
292 if (!opt || !opt->value_semantic)
293 continue;
294
295 if (opt->value_semantic->is_bool_switch()) {
296 result.add_option(name, { has_eq_value ? value : "true" });
297 }
298 else if (opt->value_semantic->is_multitoken()) {
299 std::vector<std::string> values;
300 if (has_eq_value)
301 values.push_back(value);
302 while (i + 1 < m_argc) {
303 std::string next(m_argv[i + 1]);
304 if (!next.empty() && next[0] == '-' && next.size() > 1)
305 break;
306 ++i;
307 values.push_back(next);
308 }
309 result.add_option(name, std::move(values));
310 }
311 else {
312 if (has_eq_value) {
313 result.add_option(name, { value });
314 }
315 else if (i + 1 < m_argc) {
316 ++i;
317 result.add_option(name, { std::string(m_argv[i]) });
318 }
319 }
320 }
321 else {
322 // Short option: -s
323 if (token.size() < 2)
324 continue;
325
326 char short_char = token[1];
327 const auto* opt = m_desc->find_by_short(short_char);
328 if (!opt || !opt->value_semantic)
329 continue;
330
331 std::string name = opt->long_name;
332 std::string inline_value;
333 bool has_inline_value = (token.size() > 2);
334
335 if (has_inline_value)
336 inline_value = token.substr(2);
337
338 if (opt->value_semantic->is_bool_switch()) {
339 result.add_option(name, { "true" });
340 }
341 else if (opt->value_semantic->is_multitoken()) {
342 std::vector<std::string> values;
343 if (has_inline_value)
344 values.push_back(inline_value);
345 while (i + 1 < m_argc) {
346 std::string next(m_argv[i + 1]);
347 if (!next.empty() && next[0] == '-' && next.size() > 1)
348 break;
349 ++i;
350 values.push_back(next);
351 }
352 result.add_option(name, std::move(values));
353 }
354 else {
355 if (has_inline_value) {
356 result.add_option(name, { inline_value });
357 }
358 else if (i + 1 < m_argc) {
359 ++i;
360 result.add_option(name, { std::string(m_argv[i]) });
361 }
362 }
363 }
364 }
365
366 // Map positional arguments to named options
367 if (m_positional && !positional_args.empty()) {
368 size_t pos_idx = 0;
369 for (const auto& pos_opt : m_positional->options()) {
370 if (pos_idx >= positional_args.size())
371 break;
372
373 if (pos_opt.second == -1) {
374 // All remaining positional args
375 std::vector<std::string> remaining(positional_args.begin() + pos_idx,
376 positional_args.end());
377 result.add_option(pos_opt.first, std::move(remaining));
378 break;
379 }
380 else {
381 int count = pos_opt.second;
382 std::vector<std::string> values;
383 for (int c = 0; c < count && pos_idx < positional_args.size(); ++c, ++pos_idx)
384 values.push_back(positional_args[pos_idx]);
385 result.add_option(pos_opt.first, std::move(values));
386 }
387 }
388 }
389
390 return result;
391}
392
393/*---------------------------------------------------------------------------*/
394/*---------------------------------------------------------------------------*/
395
397parse_command_line(int argc, char** argv, const options_description& desc)
398{
399 command_line_parser parser(argc, argv);
400 parser.options(desc);
401 return parser.run();
402}
403
404/*---------------------------------------------------------------------------*/
405/*---------------------------------------------------------------------------*/
413void store(const parsed_options& parsed, variables_map& vm)
414{
415 // Store all parsed values
416 for (const auto& po : parsed.options()) {
417 const auto* opt_desc = parsed.description().find(po.name);
418 if (!opt_desc || !opt_desc->value_semantic)
419 continue;
420
421 const auto& semantic = opt_desc->value_semantic;
422
423 if (po.values.empty())
424 continue;
425
426 if (semantic->is_multitoken()) {
427 std::any value = std::make_any<std::vector<std::string>>(po.values);
428 variables_map::variable_value vv(value, false);
429 vv.set_semantic(semantic);
430 vm.add(po.name, std::move(vv));
431 }
432 else {
433 std::any value = semantic->parse(po.values[0]);
434 variables_map::variable_value vv(value, false);
435 vv.set_semantic(semantic);
436 vm.add(po.name, std::move(vv));
437 }
438 }
439
440 // Apply defaults for defined options not present on command line
441 for (const auto& opt : parsed.description().options()) {
442 if (!vm.count(opt.long_name) && opt.value_semantic && opt.value_semantic->has_default()) {
443 std::any default_val = opt.value_semantic->default_value_any();
444 variables_map::variable_value vv(default_val, true);
445 vv.set_semantic(opt.value_semantic);
446 vm.add(opt.long_name, std::move(vv));
447 }
448 }
449}
450
451/*---------------------------------------------------------------------------*/
452/*---------------------------------------------------------------------------*/
459void notify(variables_map& vm)
460{
461 for (auto& [name, val] : vm) {
462 if (val.empty())
463 continue;
464 if (auto semantic = val.semantic())
465 semantic->assign_bound(val.value_ref());
466 }
467}
468
469/*---------------------------------------------------------------------------*/
470/*---------------------------------------------------------------------------*/
471
472} // namespace Arcane::ProgramOptions
473
474/*---------------------------------------------------------------------------*/
475/*---------------------------------------------------------------------------*/
Fluent command-line parser builder.
Describes a set of command-line options.
Result of parsing command-line arguments.