https://mooseframework.inl.gov
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 
28 CommandLine::CommandLine(int argc, char * argv[]) { addArguments(argc, argv); }
29 CommandLine::CommandLine(const std::vector<std::string> & args) { addArguments(args); }
30 
31 void
32 CommandLine::addArguments(int argc, char * argv[])
33 {
34  for (int i = 0; i < argc; i++)
35  addArgument(argv[i]);
36 }
37 
38 void
39 CommandLine::addArgument(const std::string & arg)
40 {
41  mooseAssert(!hasParsed(), "Has already parsed");
42  _argv.push_back(arg);
43 }
44 
45 void
46 CommandLine::addArguments(const std::vector<std::string> & args)
47 {
48  for (const auto & arg : args)
49  addArgument(arg);
50 }
51 
52 bool
53 CommandLine::hasArgument(const std::string & arg) const
54 {
55  return std::find(_argv.begin(), _argv.end(), arg) != _argv.end();
56 }
57 
58 void
59 CommandLine::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 
68 void
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);
129  entry.value = MooseUtils::removeExtraWhitespace(end);
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 
185 const std::list<CommandLine::Entry> &
187 {
188  mooseAssert(hasParsed(), "Has not parsed");
189  return _entries;
190 }
191 
192 std::list<CommandLine::Entry> &
194 {
195  mooseAssert(hasParsed(), "Has not parsed");
196  return _entries;
197 }
198 
200 
201 std::unique_ptr<CommandLine>
202 CommandLine::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 
255 std::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 
310 void
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)
393  mooseError(
394  "Missing required command-line parameter: ", name, "\nDoc string: ", param.description);
395  }
396 
398 }
399 
400 std::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 
410 std::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 
420 void
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 
455 std::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 
479 std::list<CommandLine::Entry>::const_iterator
480 CommandLine::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 
498 std::list<CommandLine::Entry>::iterator
499 CommandLine::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 
506 std::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 }
bool _has_parsed
Whether or not the Parser has parsed yet.
Definition: CommandLine.h:239
std::string name(const ElemQuality q)
OStreamProxy err
bool _hit_params_built
Whether or not the HIT parameters have been built (set as used)
Definition: CommandLine.h:243
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
Definition: KokkosUtils.h:40
std::string getExecutableName() const
Definition: CommandLine.C:401
std::optional< std::string > value_separator
The string that separates the value, if a value exists (space or =)
Definition: CommandLine.h:45
bool beginsWith(const std::string &value, const std::string &begin_value)
Definition: MooseUtils.C:955
std::list< Entry >::const_iterator findCommandLineParam(const std::string &name) const
Definition: CommandLine.C:480
std::string formatEntry(const Entry &entry) const
Definition: CommandLine.C:507
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
Stores name/value pairs for each command line argument.
Definition: CommandLine.h:36
std::vector< std::string > buildHitParams()
Definition: CommandLine.C:256
void printUsage() const
Print the usage info for this command line.
Definition: CommandLine.C:421
std::string name
The name, i.e, ["-foo=bar"] -> "-foo" or ["--foo", "bar"] -> "--foo".
Definition: CommandLine.h:39
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::string removeExtraWhitespace(const std::string &input)
Definition: MooseUtils.C:225
auto max(const L &left, const R &right)
void addArgument(const std::string &arg)
Adds a single argument.
Definition: CommandLine.C:39
auto end() const
Definition: CommandLine.h:155
std::list< Entry > _entries
The parsed command line entries (arguments split into name value pairs) This is a list because it is ...
Definition: CommandLine.h:233
bool _command_line_params_populated
Whether or not command line parameters have been populated.
Definition: CommandLine.h:241
auto begin() const
Definition: CommandLine.h:151
void populateCommandLineParams(InputParameters &params)
Populates the command line input parameters from params.
Definition: CommandLine.C:311
Stores information pertaining to a command line InputParameter.
Definition: CommandLine.h:59
std::map< std::string, CommandLineParam > _command_line_params
The command line parameters, added by populateCommandLineParams()
Definition: CommandLine.h:236
std::vector< std::string > _argv
Storage for the raw argv.
Definition: CommandLine.h:229
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
const std::list< Entry > & getEntries() const
Definition: CommandLine.C:186
virtual ~CommandLine()
Definition: CommandLine.C:199
bool hasArgument(const std::string &arg) const
Definition: CommandLine.C:53
void setSinglePetscOption(const std::string &name, const std::string &value="", FEProblemBase *const problem=nullptr)
A wrapper function for dealing with different versions of PetscOptionsSetValue.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void add_command_line_name(const std::string &name)
timpi_pure bool verify(const T &r) const
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...
Definition: CommandLine.C:202
void removeArgument(const std::string &arg)
Removes an argument that must exist.
Definition: CommandLine.C:59
void parse()
Performs the parsing, which is the combining of arguments into [name, value] pairs.
Definition: CommandLine.C:69
bool hasParsed() const
Definition: CommandLine.h:105
std::vector< std::string > unusedHitParams(const Parallel::Communicator &comm) const
Returns the HIT command line arguments that are not used.
Definition: CommandLine.C:456
std::optional< std::string > value
The value, i.e. ["-foo=bar"] -> "bar" or ["-foo"] -> empty, if any.
Definition: CommandLine.h:43
auto index_range(const T &sizable)
std::string getExecutableNameBase() const
Definition: CommandLine.C:411
void addArguments(int argc, char *argv[])
Adds arguments from raw argc and argv.
Definition: CommandLine.C:32