LCOV - code coverage report
Current view: top level - include/parser - CommandLine.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fa5e60 Lines: 46 46 100.0 %
Date: 2026-06-24 08:03:36 Functions: 14 14 100.0 %
Legend: Lines: hit not hit

          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 : }

Generated by: LCOV version 1.14