https://mooseframework.inl.gov
Loading...
Searching...
No Matches
CommandLine.C
Go to the documentation of this file.
1//* This file is part of the MOOSE framework
2//* https://mooseframework.inl.gov
3//*
4//* All rights reserved, see COPYRIGHT for full restrictions
5//* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6//*
7//* Licensed under LGPL 2.1, please see LICENSE for details
8//* https://www.gnu.org/licenses/lgpl-2.1.html
9
10#include "CommandLine.h"
11
12// C++ includes
13#include <iomanip>
14#include <optional>
15
16#include "pcrecpp.h"
17
18#include "hit.h"
19
20#include "libmesh/libmesh.h"
21#include "libmesh/simple_range.h"
22#include "libmesh/parallel_algebra.h"
23#include "libmesh/parallel_sync.h"
24
25#include "PetscSupport.h"
26
28CommandLine::CommandLine(int argc, char * argv[]) { addArguments(argc, argv); }
29CommandLine::CommandLine(const std::vector<std::string> & args) { addArguments(args); }
30
31void
32CommandLine::addArguments(int argc, char * argv[])
33{
34 for (int i = 0; i < argc; i++)
35 addArgument(argv[i]);
36}
37
38void
39CommandLine::addArgument(const std::string & arg)
40{
41 mooseAssert(!hasParsed(), "Has already parsed");
42 _argv.push_back(arg);
43}
44
45void
46CommandLine::addArguments(const std::vector<std::string> & args)
47{
48 for (const auto & arg : args)
49 addArgument(arg);
50}
51
52bool
53CommandLine::hasArgument(const std::string & arg) const
54{
55 return std::find(_argv.begin(), _argv.end(), arg) != _argv.end();
56}
57
58void
59CommandLine::removeArgument(const std::string & arg)
60{
61 mooseAssert(!hasParsed(), "Has already parsed");
62 auto it = std::find(_argv.begin(), _argv.end(), arg);
63 if (it == _argv.end())
64 mooseError("CommandLine::removeArgument(): The argument '", arg, "' does not exist");
65 _argv.erase(it);
66}
67
68void
70{
71 mooseAssert(!hasParsed(), "Has already parsed");
72 mooseAssert(_entries.empty(), "Should be empty");
73
74 // Whether or not we have a entry that accepts values
75 bool has_value_accepting_entry = false;
76
77 // Helper for adding an entry
78 auto add_entry = [this](const auto & name) -> Entry &
79 {
80 auto & entry = _entries.emplace_back();
81 entry.name = name;
82 return entry;
83 };
84
85 // Work through each argument
86 for (const auto i : index_range(_argv))
87 {
88 const auto & arg = _argv[i];
89 const auto begins_with_dash = MooseUtils::beginsWith(arg, "-");
90 std::string subapp_prefix, subapp_name, hit_path, hit_value;
91
92 // MultiApp syntax with a non-hit option
93 if (!begins_with_dash && std::regex_search(arg, std::regex("^(([^\\s\n\t[\\]\\/=#&:]+):)[-]")))
94 {
95 mooseError("The MultiApp command line argument '",
96 arg,
97 "' sets a command line option.\nMultiApp command line arguments can only be "
98 "used for setting HIT parameters.");
99 }
100 // Match HIT CLI syntax (including for multiapps with the prefix)
101 // For subapp hit cli syntax (i.e, <subname>:<value)), we will store the
102 // subapp names to be stripped away in initSubAppCommandLine() as they
103 // are passed down
104 else if (!begins_with_dash &&
105 pcrecpp::RE("(([^\\s\n\t[\\]\\/=#&:]+)?:)?((?:[^\\s\n\t[\\]=#&]+\\/"
106 ")?(?:[^\\s\n\t=#$'\"]+))=([^#]+)?")
107 .FullMatch(arg, &subapp_prefix, &subapp_name, &hit_path, &hit_value))
108 {
109 auto & entry = add_entry(hit_path);
110 if (subapp_prefix.size())
111 {
112 if (subapp_name.empty()) // :param=value; means apply to all
113 entry.global = true;
114 else
115 entry.subapp_name = subapp_name;
116 }
117 entry.value = MooseUtils::removeExtraWhitespace(hit_value);
118 entry.value_separator = "=";
119 entry.raw_args.push_back(arg);
120 entry.hit_param = true;
121 has_value_accepting_entry = false;
122 }
123 // Has an = sign in it, so we have a name=value (non-HIT)
124 else if (const auto find_equals = arg.find("="); find_equals != std::string::npos)
125 {
126 const auto begin = arg.substr(0, find_equals);
127 const auto end = arg.substr(find_equals + 1);
128 auto & entry = add_entry(begin);
130 entry.value_separator = "=";
131 entry.raw_args.push_back(arg);
132 has_value_accepting_entry = false;
133 }
134 // Begins with dash(es) and a character, so a new argument. We pass on
135 // everything else that starts with a dash as a value, so the error
136 // will be associated with the value before it
137 else if (std::regex_search(arg, std::regex("^\\-+[a-zA-Z]")))
138 {
139 auto & entry = add_entry(arg);
140 entry.raw_args.push_back(arg);
141 has_value_accepting_entry = true;
142 }
143 // Should be tagging on a value to the previous argument
144 else
145 {
146 // First one is the executable
147 if (i == 0)
148 {
149 auto & entry = add_entry(arg);
150 entry.raw_args.push_back(arg);
151 continue;
152 }
153
154 // Throw an error if this a value and we don't have anything to apply it to
155 if (!has_value_accepting_entry)
156 {
157 std::stringstream err;
158 err << "The command line argument '" << arg
159 << "' is not applied to an option and is not a HIT parameter.";
160 // Maybe they meant to apply it to the previous thing
161 // Example: "-i foo.i bar.i" would suggest "-i 'foo.i bar.i'"
162 if (i > 0 && _entries.back().value)
163 {
164 err << "\n\nDid you mean to combine this argument with the previous argument, such "
165 "as:\n\n "
166 << _entries.back().name << *_entries.back().value_separator << "'"
167 << *_entries.back().value << " " << arg << "'\n";
168 }
169 mooseError(err.str());
170 }
171
172 auto & entry = _entries.back();
173 if (entry.value)
174 *entry.value += " " + MooseUtils::removeExtraWhitespace(arg);
175 else
176 entry.value = MooseUtils::removeExtraWhitespace(arg);
177 entry.value_separator = " ";
178 entry.raw_args.push_back(arg);
179 }
180 }
181
182 _has_parsed = true;
183}
184
185const std::list<CommandLine::Entry> &
187{
188 mooseAssert(hasParsed(), "Has not parsed");
189 return _entries;
190}
191
192std::list<CommandLine::Entry> &
194{
195 mooseAssert(hasParsed(), "Has not parsed");
196 return _entries;
197}
198
200
201std::unique_ptr<CommandLine>
202CommandLine::initSubAppCommandLine(const std::string & multiapp_name,
203 const std::string & subapp_name,
204 const std::vector<std::string> & input_cli_args,
205 const std::set<std::string> & exclude_params)
206{
207 mooseAssert(MooseUtils::beginsWith(subapp_name, multiapp_name),
208 "Name for the subapp should begin with the multiapp");
209
210 std::vector<std::string> subapp_args;
211
212 // Start with the arguments from the input file; we want these to take the
213 // lowest priority so we put them first. Also trim extra whitespace
214 for (const auto & arg : input_cli_args)
215 subapp_args.push_back(MooseUtils::removeExtraWhitespace(arg));
216
217 // Resolve the excluded parameter names to their Entry pointers so we can
218 // efficiently skip them in the propagation loop below. We skip entries for
219 // which findCommandLineParam returns end() (param registered but not present).
220 std::set<const Entry *> skip_entries;
221 for (const auto & param_name : exclude_params)
222 {
223 auto it = findCommandLineParam(param_name);
224 if (it != getEntries().end())
225 skip_entries.insert(&*it);
226 }
227
228 // Pull out all of the arguments that are relevant to this multiapp from the parent
229 // Note that the 0th argument of the main app, i.e., the name used to invoke the program,
230 // is neither a global entry nor a subapp entry and, as such, won't be passed on
231 for (auto & entry : as_range(getEntries().begin(), getEntries().end()))
232 if (entry.global || (entry.subapp_name && (*entry.subapp_name == multiapp_name ||
233 *entry.subapp_name == subapp_name)))
234 {
235 // Skip entries that the caller has asked to suppress from propagation
236 if (skip_entries.count(&entry))
237 continue;
238
239 if (entry.hit_param)
240 {
241 // Append : to the beginning if this is global and should be passed to all
242 const std::string prefix = entry.global ? ":" : "";
243 // Apply the param, but without the subapp name
244 subapp_args.push_back(prefix + entry.name + *entry.value_separator + *entry.value);
245 // Mark this entry as used as a child has consumed it
246 entry.used = true;
247 }
248 else
249 subapp_args.insert(subapp_args.end(), entry.raw_args.begin(), entry.raw_args.end());
250 }
251
252 return std::make_unique<CommandLine>(subapp_args);
253}
254
255std::vector<std::string>
257{
258 mooseAssert(!_hit_params_built, "Already built");
259 _hit_params_built = true;
260
261 std::vector<std::string> params;
262
263 // Collect all hit parameters that aren't for subapps
264 for (auto & entry : getEntries())
265 if (entry.hit_param && !entry.subapp_name)
266 {
267 mooseAssert(entry.value, "Should have a value");
268 mooseAssert(entry.value_separator, "Should have value separator");
269 mooseAssert(*entry.value_separator == "=", "Should be an equals");
270
271 const std::string name_and_equals = entry.name + *entry.value_separator;
272 std::string arg = name_and_equals;
273 // In the case of empty values, we want them to be empty to hit
274 if (entry.value->empty())
275 arg += "''";
276 else
277 arg += *entry.value;
278
279 // We could have issues with bash eating strings, so the first try
280 // gives us a chance to wrap the value in quotes
281 try
282 {
283 hit::check("CLI_ARG", arg);
284 }
285 catch (hit::Error & err)
286 {
287 // bash might have eaten quotes around a hit string value or vector
288 // so try quoting after the "=" and reparse
289 arg = name_and_equals + "'" + *entry.value + "'";
290 try
291 {
292 hit::check("CLI_ARG", arg);
293 }
294 // At this point, we've failed to fix it
295 catch (hit::Error & err)
296 {
297 mooseError("Failed to parse HIT in command line argument '", arg, "'\n\n", err.what());
298 }
299 }
300
301 // Append to the total output
302 params.push_back(arg);
303 // Consider this parameter used
304 entry.used = true;
305 }
306
307 return params;
308}
309
310void
312{
313 mooseAssert(!_command_line_params_populated, "Already populated");
314
315 // Set the metadata for each command line parameter
316 // We set this separately so that it can be used later to print usage
317 std::map<std::string, std::string> switch_param_map;
318 for (const auto & name_value_pair : params)
319 {
320 const auto & name = name_value_pair.first;
321 if (const auto metadata = params.queryCommandLineMetadata(name))
322 {
323 auto it_inserted_pair = _command_line_params.emplace(name, CommandLineParam());
324 auto & option = it_inserted_pair.first->second;
325
326 option.description = params.getDocString(name);
327 option.metadata = *metadata;
328
329 // Make sure that one switch isn't specified for multiple parameters
330 for (const auto & cli_switch : option.metadata.switches)
331 if (const auto it_inserted_pair = switch_param_map.emplace(cli_switch, name);
332 !it_inserted_pair.second)
333 mooseError("The command line options '",
334 it_inserted_pair.first->second,
335 "' and '",
336 name,
337 "' both declare the command line switch '",
338 cli_switch,
339 "'");
340 }
341 }
342
343 // Set each paramter that we have a value for
344 for (const auto & [name, param] : _command_line_params)
345 {
346 auto entry_it = findCommandLineParam(name);
347 if (entry_it != _entries.end())
348 {
349 auto & entry = *entry_it;
350 mooseAssert(!entry.subapp_name, "Should not be for a subapp");
351
352 bool found = false;
353#define trySetParameter(type) \
354 if (!found && params.have_parameter<type>(name)) \
355 { \
356 static_assert(InputParameters::isValidCommandLineType<type>::value, "Not a supported value"); \
357 auto & value = params.set<type>(name); \
358 setCommandLineParam(entry_it, param, entry.name, value); \
359 found = true; \
360 }
361
362 trySetParameter(std::string);
363 trySetParameter(std::vector<std::string>);
364 trySetParameter(Real);
365 trySetParameter(unsigned int);
366 trySetParameter(int);
367 trySetParameter(bool);
368 trySetParameter(MooseEnum);
369#undef trySetParameter
370
371 mooseAssert(found, "Should have been found");
372
373 // If we found this parameter, that means we set it and we should mark in the
374 // InputParameters that it is set so that isParamSetByUser() returns true for this param
375 params.commandLineParamSet(name, {});
376
377 // If this parameter is global, mark its entry as global
378 if (param.metadata.global)
379 entry.global = true;
380
381 // If the arg is of the form "--key=value", PETSc will recognize it as an unused
382 // argument. That is, setting "--key" as a known command line argument is not
383 // sufficient for PETSc to consider "--key=value" as known. Thus, we explicitly
384 // add "--key=value" args as known when we come across them.
385 if (entry.value_separator && *entry.value_separator == "=")
386 {
387 mooseAssert(entry.raw_args.size() == 1, "Should have one value");
388 libMesh::add_command_line_name(entry.raw_args[0]);
389 }
390 }
391 // If we didn't find it and it is required, we need to error
392 else if (param.metadata.required)
394 "Missing required command-line parameter: ", name, "\nDoc string: ", param.description);
395 }
396
398}
399
400std::string
402{
403 mooseAssert(_entries.size() > 0, "Does not have any entries");
404
405 // Grab the first item out of argv
406 const auto & command = _entries.begin()->name;
407 return command.substr(command.find_last_of("/\\") + 1);
408}
409
410std::string
412{
413 auto name = getExecutableName();
414 name = name.substr(0, name.find_last_of("-"));
415 if (name.find_first_of("/") != std::string::npos)
416 name = name.substr(name.find_first_of("/") + 1, std::string::npos);
417 return name;
418}
419
420void
422{
423 Moose::out << "Usage: " << getExecutableName() << " [<options>]\n\n";
424
425 std::size_t max_len = 0;
426 for (const auto & name_option_pair : _command_line_params)
427 max_len = std::max(max_len, name_option_pair.second.metadata.syntax.size());
428
429 const auto output_options = [this, &max_len](const bool global)
430 {
431 Moose::out << (global ? "Global " : "") << "Options:\n" << std::left;
432 for (const auto & name_option_pair : _command_line_params)
433 {
434 const auto & option = name_option_pair.second;
435 if (option.metadata.syntax.empty() || option.metadata.global != global)
436 continue;
437
438 Moose::out << " " << std::setw(max_len + 2) << option.metadata.syntax << option.description
439 << "\n";
440 }
441 Moose::out << "\n";
442 };
443
444 output_options(false);
445 output_options(true);
446
447 Moose::out << "Solver Options:\n"
448 << " See PETSc manual for details" << std::endl;
449
450 // If we get here, we are not running a simulation and should silence petsc's unused options
451 // warning
452 Moose::PetscSupport::setSinglePetscOption("-options_left", "0");
453}
454
455std::vector<std::string>
457{
458 libmesh_parallel_only(comm);
459
460 std::vector<const Entry *> hit_params;
461 std::vector<std::size_t> use_count;
462 for (const auto & entry : getEntries())
463 if (entry.hit_param)
464 {
465 hit_params.push_back(&entry);
466 use_count.push_back(entry.used ? 1 : 0);
467 }
468
469 mooseAssert(comm.verify(use_count.size()), "Inconsistent HIT params across procs");
470 comm.sum(use_count);
471
472 std::vector<std::string> unused;
473 for (const auto i : index_range(use_count))
474 if (use_count[i] == 0)
475 unused.push_back(hit_params[i]->raw_args[0]);
476 return unused;
477}
478
479std::list<CommandLine::Entry>::const_iterator
480CommandLine::findCommandLineParam(const std::string & name) const
481{
482 const auto find_param = _command_line_params.find(name);
483 if (find_param == _command_line_params.end())
484 mooseError("CommandLine::findCommandLineParam(): The parameter '",
485 name,
486 "' is not a command line parameter");
487
488 // Search for the last thing that matches a switch from this param
489 const auto & param = find_param->second;
490 for (auto rit = _entries.rbegin(); rit != _entries.rend(); ++rit)
491 for (const auto & search_switch : param.metadata.switches)
492 if (rit->name == search_switch)
493 return --(rit.base());
494
495 return getEntries().end();
496}
497
498std::list<CommandLine::Entry>::iterator
499CommandLine::findCommandLineParam(const std::string & name)
500{
501 const auto it = std::as_const(*this).findCommandLineParam(name);
502 // Easy way to go from a const iterator -> non-const iterator
503 return _entries.erase(it, it);
504}
505
506std::string
508{
509 std::stringstream oss;
510 oss << entry.name;
511 if (entry.value)
512 {
513 const auto q = (*entry.value).find(" ") != std::string::npos ? "'" : "";
514 oss << *entry.value_separator << q << *entry.value << q;
515 }
516 return oss.str();
517}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
bool hasParsed() const
bool _hit_params_built
Whether or not the HIT parameters have been built (set as used)
bool _command_line_params_populated
Whether or not command line parameters have been populated.
std::list< Entry > _entries
The parsed command line entries (arguments split into name value pairs) This is a list because it is ...
void removeArgument(const std::string &arg)
Removes an argument that must exist.
Definition CommandLine.C:59
std::unique_ptr< CommandLine > initSubAppCommandLine(const std::string &multiapp_name, const std::string &subapp_name, const std::vector< std::string > &input_cli_args, const std::set< std::string > &exclude_params={})
Initializes a new CommandLine for a subapp with a MultiApp named multiapp_name and a subapp named sub...
const std::list< Entry > & getEntries() const
std::string getExecutableName() const
void populateCommandLineParams(InputParameters &params)
Populates the command line input parameters from params.
void addArguments(int argc, char *argv[])
Adds arguments from raw argc and argv.
Definition CommandLine.C:32
bool _has_parsed
Whether or not the Parser has parsed yet.
void addArgument(const std::string &arg)
Adds a single argument.
Definition CommandLine.C:39
std::string getExecutableNameBase() const
std::list< Entry >::const_iterator findCommandLineParam(const std::string &name) const
std::string formatEntry(const Entry &entry) const
std::vector< std::string > _argv
Storage for the raw argv.
bool hasArgument(const std::string &arg) const
Definition CommandLine.C:53
virtual ~CommandLine()
auto begin() const
void parse()
Performs the parsing, which is the combining of arguments into [name, value] pairs.
Definition CommandLine.C:69
void printUsage() const
Print the usage info for this command line.
auto end() const
std::vector< std::string > buildHitParams()
std::map< std::string, CommandLineParam > _command_line_params
The command line parameters, added by populateCommandLineParams()
std::vector< std::string > unusedHitParams(const Parallel::Communicator &comm) const
Returns the HIT command line arguments that are not used.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system.
void commandLineParamSet(const std::string &name, const CommandLineParamSetKey)
Marks the command line parameter name as set by the CommandLine.
std::optional< InputParameters::CommandLineMetadata > queryCommandLineMetadata(const std::string &name) const
std::string getDocString(const std::string &name) const
Returns the documentation string for the specified parameter name.
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition MooseEnum.h:55
timpi_pure bool verify(const T &r) const
std::string removeExtraWhitespace(const std::string &input)
Definition MooseUtils.C:236
bool beginsWith(const std::string &value, const std::string &begin_value)
Definition MooseUtils.C:966
void setSinglePetscOption(const std::string &name, const std::string &value="", FEProblemBase *const problem=nullptr)
A wrapper function for dealing with different versions of PetscOptionsSetValue.
void add_command_line_name(const std::string &name)
Stores information pertaining to a command line InputParameter.
Definition CommandLine.h:60
Stores name/value pairs for each command line argument.
Definition CommandLine.h:37
std::optional< std::string > value
The value, i.e. ["-foo=bar"] -> "bar" or ["-foo"] -> empty, if any.
Definition CommandLine.h:43
std::optional< std::string > value_separator
The string that separates the value, if a value exists (space or =)
Definition CommandLine.h:45
std::string name
The name, i.e, ["-foo=bar"] -> "-foo" or ["--foo", "bar"] -> "--foo".
Definition CommandLine.h:39