Line data Source code
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 : #pragma once
11 :
12 : #include "MooseError.h"
13 : #include "Conversion.h"
14 : #include "MooseEnum.h"
15 : #include "InputParameters.h"
16 : #include "MooseUtils.h"
17 :
18 : #include <list>
19 : #include <string>
20 : #include <map>
21 : #include <memory>
22 : #include <set>
23 : #include <optional>
24 : #include <regex>
25 :
26 : /**
27 : * This class wraps provides and tracks access to command line parameters.
28 : */
29 : class CommandLine
30 : {
31 : public:
32 : using ArgumentType = InputParameters::CommandLineMetadata::ArgumentType;
33 : /**
34 : * Stores name/value pairs for each command line argument
35 : */
36 : struct Entry
37 : {
38 : /// The name, i.e, ["-foo=bar"] -> "-foo" or ["--foo", "bar"] -> "--foo"
39 : std::string name;
40 : /// The name of the subapp, if any (with subapp:something=value syntax)
41 : std::optional<std::string> subapp_name;
42 : /// The value, i.e. ["-foo=bar"] -> "bar" or ["-foo"] -> empty, if any
43 : std::optional<std::string> value;
44 : /// The string that separates the value, if a value exists (space or =)
45 : std::optional<std::string> value_separator;
46 : /// The raw arguments that represent these split values
47 : std::vector<std::string> raw_args;
48 : /// Whether or not this set of arguments was used
49 : bool used = false;
50 : /// Whether or not this parameter is global (passed to MultiApps)
51 : bool global = false;
52 : /// Whether or not this parameter is recognized as a HIT parameter
53 : bool hit_param = false;
54 : };
55 :
56 : /**
57 : * Stores information pertaining to a command line InputParameter
58 : */
59 : struct CommandLineParam
60 : {
61 : /// The description (doc string) for the parameter
62 : std::string description;
63 : /// The command line metadata for the parameter
64 : InputParameters::CommandLineMetadata metadata;
65 : };
66 :
67 : CommandLine();
68 : CommandLine(int argc, char * argv[]);
69 : CommandLine(const std::vector<std::string> & args);
70 : virtual ~CommandLine();
71 :
72 : /**
73 : * Adds arguments from raw argc and argv
74 : */
75 : void addArguments(int argc, char * argv[]);
76 : /**
77 : * Adds a single argument
78 : */
79 : void addArgument(const std::string & arg);
80 : /**
81 : * Adds arguments from a vector
82 : */
83 : void addArguments(const std::vector<std::string> & args);
84 :
85 : /**
86 : * @return Whether or not the raw argument \p arg is contained
87 : */
88 : bool hasArgument(const std::string & arg) const;
89 :
90 : /**
91 : * Removes an argument that must exist
92 : */
93 : void removeArgument(const std::string & arg);
94 :
95 : /**
96 : * Performs the parsing, which is the combining of arguments into [name, value] pairs.
97 : *
98 : * Must be called before extracing anything from the CommandLine.
99 : */
100 : void parse();
101 :
102 : /**
103 : * @return Whether or not the arguments have been parsed
104 : */
105 : bool hasParsed() const { return _has_parsed; }
106 :
107 : /**
108 : * Initializes a new CommandLine for a subapp with a MultiApp named \p multiapp_name
109 : * and a subapp named \p subapp_name.
110 : *
111 : * The arguments \p input_cli_args are the additional arguments to apply to the subapp,
112 : * such as those that have been specified in the MultiApp params (cli_args param).
113 : *
114 : * This will apply all global parameters from this parent application and all HIT CLI
115 : * parameters that have the same.
116 : *
117 : * The optional \p exclude_params is a set of InputParameters names (e.g. "recover",
118 : * "test_checkpoint_half_transient") whose corresponding command-line entries will NOT
119 : * be propagated to the sub-app, even if they are marked global. Entries are matched
120 : * by the parameter name as registered in MooseApp::validParams().
121 : */
122 : std::unique_ptr<CommandLine>
123 : initSubAppCommandLine(const std::string & multiapp_name,
124 : const std::string & subapp_name,
125 : const std::vector<std::string> & input_cli_args,
126 : const std::set<std::string> & exclude_params = {});
127 :
128 : /**
129 : * @return The parsed HIT command line parameters per the command line arguments.
130 : *
131 : * This will also mark all found HIT parameters as used.
132 : */
133 : std::vector<std::string> buildHitParams();
134 :
135 : /**
136 : * @return The raw argv arguments as a vector
137 : */
138 103066 : const std::vector<std::string> & getArguments() { return _argv; }
139 :
140 : /**
141 : * Populates the command line input parameters from \p params.
142 : *
143 : * Will throw errors when conversions fail and may combine entires in
144 : * _entries if some are found that can be combined.
145 : */
146 : void populateCommandLineParams(InputParameters & params);
147 :
148 : /**
149 : * @return An iterator to the beginning of the options
150 : */
151 : auto begin() const { return _entries.begin(); }
152 : /**
153 : * @return An iterator to the end of the options
154 : */
155 : auto end() const { return _entries.end(); }
156 :
157 : /**
158 : * @return The combined argument entries
159 : */
160 : const std::list<Entry> & getEntries() const;
161 :
162 : /**
163 : * @return The executable name.
164 : */
165 : std::string getExecutableName() const;
166 :
167 : /**
168 : * @return The exectuable name base (the name without the -[opt,oprof,devel,dbg])
169 : */
170 : std::string getExecutableNameBase() const;
171 :
172 : /**
173 : * Print the usage info for this command line
174 : */
175 : void printUsage() const;
176 :
177 : /**
178 : * Returns the HIT command line arguments that are not used.
179 : *
180 : * The HIT command line arguments are considered used when they are accumulated
181 : * in buildHitParams().
182 : *
183 : * The commmunicator is needed because we need to sync this in parallel due to
184 : * the fact that sub apps could only be created on a subset of processors.
185 : */
186 : std::vector<std::string> unusedHitParams(const Parallel::Communicator & comm) const;
187 :
188 : /**
189 : * @return The entry iterator for the command line arguments for the command line input
190 : * parameter with name \p name, if any.
191 : */
192 : std::list<Entry>::const_iterator findCommandLineParam(const std::string & name) const;
193 :
194 : /**
195 : * @return A formatted representation of the given command line entry
196 : */
197 : std::string formatEntry(const Entry & entry) const;
198 :
199 : private:
200 : /**
201 : * @return The combined argument entries
202 : */
203 : std::list<Entry> & getEntries();
204 :
205 : /**
206 : * Sets an InputParameters command line option at \p value.
207 : *
208 : * Will report an error if string -> value conversions fail or if the
209 : * parameter requires a value and one was not found.
210 : *
211 : * @param entry_it Iterator to the Entry object that we're extracting from
212 : * @param param The internal metadata for the command line parameter
213 : * @param cli_switch The command line switch for the parameter (-t, --timing, etc)
214 : * @param value The value that we want to fill into
215 : */
216 : template <typename T>
217 : void setCommandLineParam(std::list<Entry>::iterator entry_it,
218 : const CommandLineParam & param,
219 : const std::string & cli_switch,
220 : T & value);
221 :
222 : /**
223 : * @return The entry iterator for the command line arguments for the command line input
224 : * parameter with name \p name, if any.
225 : */
226 : std::list<Entry>::iterator findCommandLineParam(const std::string & name);
227 :
228 : /// Storage for the raw argv
229 : std::vector<std::string> _argv;
230 :
231 : /// The parsed command line entries (arguments split into name value pairs)
232 : /// This is a list because it is necessary to combine Entry objects later on
233 : std::list<Entry> _entries;
234 :
235 : /// The command line parameters, added by populateCommandLineParams()
236 : std::map<std::string, CommandLineParam> _command_line_params;
237 :
238 : /// Whether or not the Parser has parsed yet
239 : bool _has_parsed = false;
240 : /// Whether or not command line parameters have been populated
241 : bool _command_line_params_populated = false;
242 : /// Whether or not the HIT parameters have been built (set as used)
243 : bool _hit_params_built = false;
244 : };
245 :
246 : template <typename T>
247 : void
248 456534 : CommandLine::setCommandLineParam(std::list<CommandLine::Entry>::iterator entry_it,
249 : const CommandLineParam & param,
250 : const std::string & cli_switch,
251 : T & value)
252 : {
253 456534 : auto & entry = *entry_it;
254 456534 : const auto required = param.metadata.argument_type == ArgumentType::REQUIRED;
255 :
256 : // Mark this entry as used
257 456534 : entry.used = true;
258 :
259 : // Option doesn't have any arguments (boolean)
260 : if constexpr (std::is_same_v<bool, T>)
261 : {
262 : mooseAssert(param.metadata.argument_type == ArgumentType::NONE, "Incorrect argument type");
263 :
264 277319 : if (entry.value)
265 2 : mooseError("The command line option '",
266 : cli_switch,
267 : "' is a boolean and does not support a value but the value '",
268 : *entry.value,
269 : "' was provided.\nDoc string: ",
270 2 : param.description);
271 277317 : value = true;
272 : }
273 : // Option has arguments
274 : else
275 : {
276 : mooseAssert(param.metadata.argument_type != ArgumentType::NONE, "Incorrect argument type");
277 :
278 : // Helper for setting a value depending on its type and also throwing a useful
279 : // error when the conversion fails
280 525227 : const auto set_value = [&cli_switch](const std::string & from, auto & value)
281 : {
282 : using type = typename std::remove_reference<decltype(value)>::type;
283 :
284 : // Keep track of and change the throw on error characteristics so that
285 : // we can catch parsing errors for the argument
286 173010 : const auto throw_on_error_orig = Moose::_throw_on_error;
287 173010 : Moose::_throw_on_error = true;
288 :
289 : try
290 : {
291 : if constexpr (std::is_same_v<type, std::string> || std::is_same_v<type, MooseEnum>)
292 172857 : value = from;
293 : else
294 153 : value = MooseUtils::convert<type>(from, true);
295 : }
296 16 : catch (std::exception & e)
297 : {
298 8 : Moose::_throw_on_error = throw_on_error_orig;
299 8 : mooseError("While parsing command line option '",
300 : cli_switch,
301 : "' with value '",
302 : from,
303 : "':\n\n",
304 8 : e.what());
305 : }
306 :
307 173002 : Moose::_throw_on_error = throw_on_error_orig;
308 : };
309 :
310 : // If a value doesn't exist, check the next argument to see if it
311 : // would work. This is needed for when we have argument values that
312 : // have = signs that get split. For example:
313 : // "--required-capabilities 'petsc>=3.11'" would get split into:
314 : // - "--required-capabilities" with no value
315 : // - "petsc>" with value "3.11"
316 : // which we want to re-combine into
317 : // - "--required-capabilities" with value "petsc>=3.11"
318 179215 : if (!entry.value)
319 : {
320 6713 : auto next_entry_it = std::next(entry_it);
321 6713 : if (next_entry_it != _entries.end())
322 : {
323 6707 : const auto & next_entry = *next_entry_it;
324 3616 : if (!next_entry.used && // isn't already used
325 3579 : (required || !next_entry.hit_param) && // if required, get the last value. if not, get
326 : // it if it's not a hit param
327 13935 : !MooseUtils::beginsWith(next_entry.name, "-") && // doesn't start with a -
328 37 : next_entry.value_separator && // has a separator
329 10323 : *next_entry.value_separator == "=" && // that separator is =
330 : next_entry.value) // and has a value after the =
331 : {
332 37 : const auto & next_entry = *next_entry_it;
333 : // Merge with the next Entry object and remove said next object
334 37 : entry.value = next_entry.name + *next_entry.value_separator + *next_entry.value;
335 37 : entry.value_separator = " ";
336 74 : entry.raw_args.insert(
337 37 : entry.raw_args.end(), next_entry.raw_args.begin(), next_entry.raw_args.end());
338 37 : _entries.erase(next_entry_it);
339 : }
340 : }
341 : }
342 :
343 : // If we have a value, set the parameter to it
344 179215 : if (entry.value)
345 : {
346 : // For vector<string>, we need to unpack the values
347 : if constexpr (std::is_same_v<T, std::vector<std::string>>)
348 : {
349 108846 : std::vector<std::string> split_values;
350 108846 : MooseUtils::tokenize(*entry.value, split_values, 1, " ");
351 108846 : value.resize(split_values.size());
352 218163 : for (const auto i : index_range(split_values))
353 109317 : set_value(split_values[i], value[i]);
354 108846 : }
355 : // For everything else, we can set them directly
356 : else
357 63693 : set_value(*entry.value, value);
358 : }
359 : // No value, but one is required
360 6676 : else if (param.metadata.argument_type == ArgumentType::REQUIRED)
361 6 : mooseError("The command line option '",
362 : cli_switch,
363 : "' requires a value and one was not provided.\nDoc string: ",
364 6 : param.description);
365 : }
366 456518 : }
|