LCOV - code coverage report
Current view: top level - include/parser - CommandLine.h (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 8601ad Lines: 45 45 100.0 %
Date: 2025-07-18 13:27:08 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             :   std::unique_ptr<CommandLine>
     118             :   initSubAppCommandLine(const std::string & multiapp_name,
     119             :                         const std::string & subapp_name,
     120             :                         const std::vector<std::string> & input_cli_args);
     121             : 
     122             :   /**
     123             :    * @return The parsed HIT command line parameters per the command line arguments.
     124             :    *
     125             :    * This will also mark all found HIT parameters as used.
     126             :    */
     127             :   std::string buildHitParams();
     128             : 
     129             :   /**
     130             :    * @return The raw argv arguments as a vector
     131             :    */
     132       96333 :   const std::vector<std::string> & getArguments() { return _argv; }
     133             : 
     134             :   /**
     135             :    * Populates the command line input parameters from \p params.
     136             :    *
     137             :    * Will throw errors when conversions fail and may combine entires in
     138             :    * _entries if some are found that can be combined.
     139             :    */
     140             :   void populateCommandLineParams(InputParameters & params);
     141             : 
     142             :   /**
     143             :    * @return An iterator to the beginning of the options
     144             :    */
     145             :   auto begin() const { return _entries.begin(); }
     146             :   /**
     147             :    * @return An iterator to the end of the options
     148             :    */
     149             :   auto end() const { return _entries.end(); }
     150             : 
     151             :   /**
     152             :    * @return The combined argument entries
     153             :    */
     154             :   const std::list<Entry> & getEntries() const;
     155             : 
     156             :   /**
     157             :    * @return The executable name.
     158             :    */
     159             :   std::string getExecutableName() const;
     160             : 
     161             :   /**
     162             :    * @return The exectuable name base (the name without the -[opt,oprof,devel,dbg])
     163             :    */
     164             :   std::string getExecutableNameBase() const;
     165             : 
     166             :   /**
     167             :    * Print the usage info for this command line
     168             :    */
     169             :   void printUsage() const;
     170             : 
     171             :   /**
     172             :    * Returns the HIT command line arguments that are not used.
     173             :    *
     174             :    * The HIT command line arguments are considered used when they are accumulated
     175             :    * in buildHitParams().
     176             :    *
     177             :    * The commmunicator is needed because we need to sync this in parallel due to
     178             :    * the fact that sub apps could only be created on a subset of processors.
     179             :    */
     180             :   std::vector<std::string> unusedHitParams(const Parallel::Communicator & comm) const;
     181             : 
     182             :   /**
     183             :    * @return The entry iterator for the command line arguments for the command line input
     184             :    * parameter with name \p name, if any.
     185             :    */
     186             :   std::list<Entry>::const_iterator findCommandLineParam(const std::string & name) const;
     187             : 
     188             :   /**
     189             :    * @return A formatted representation of the given command line entry
     190             :    */
     191             :   std::string formatEntry(const Entry & entry) const;
     192             : 
     193             : private:
     194             :   /**
     195             :    * @return The combined argument entries
     196             :    */
     197             :   std::list<Entry> & getEntries();
     198             : 
     199             :   /**
     200             :    * Sets an InputParameters command line option at \p value.
     201             :    *
     202             :    * Will report an error if string -> value conversions fail or if the
     203             :    * parameter requires a value and one was not found.
     204             :    *
     205             :    * @param entry_it Iterator to the Entry object that we're extracting from
     206             :    * @param param The internal metadata for the command line parameter
     207             :    * @param cli_switch The command line switch for the parameter (-t, --timing, etc)
     208             :    * @param value The value that we want to fill into
     209             :    */
     210             :   template <typename T>
     211             :   void setCommandLineParam(std::list<Entry>::iterator entry_it,
     212             :                            const CommandLineParam & param,
     213             :                            const std::string & cli_switch,
     214             :                            T & value);
     215             : 
     216             :   /**
     217             :    * @return The entry iterator for the command line arguments for the command line input
     218             :    * parameter with name \p name, if any.
     219             :    */
     220             :   std::list<Entry>::iterator findCommandLineParam(const std::string & name);
     221             : 
     222             :   /// Storage for the raw argv
     223             :   std::vector<std::string> _argv;
     224             : 
     225             :   /// The parsed command line entries (arguments split into name value pairs)
     226             :   /// This is a list because it is necessary to combine Entry objects later on
     227             :   std::list<Entry> _entries;
     228             : 
     229             :   /// The command line parameters, added by populateCommandLineParams()
     230             :   std::map<std::string, CommandLineParam> _command_line_params;
     231             : 
     232             :   /// Whether or not the Parser has parsed yet
     233             :   bool _has_parsed = false;
     234             :   /// Whether or not command line parameters have been populated
     235             :   bool _command_line_params_populated = false;
     236             : };
     237             : 
     238             : template <typename T>
     239             : void
     240      303874 : CommandLine::setCommandLineParam(std::list<CommandLine::Entry>::iterator entry_it,
     241             :                                  const CommandLineParam & param,
     242             :                                  const std::string & cli_switch,
     243             :                                  T & value)
     244             : {
     245      303874 :   auto & entry = *entry_it;
     246      303874 :   const auto required = param.metadata.argument_type == ArgumentType::REQUIRED;
     247             : 
     248             :   // Mark this entry as used
     249      303874 :   entry.used = true;
     250             : 
     251             :   // Option doesn't have any arguments (boolean)
     252             :   if constexpr (std::is_same_v<bool, T>)
     253             :   {
     254             :     mooseAssert(param.metadata.argument_type == ArgumentType::NONE, "Incorrect argument type");
     255             : 
     256      190498 :     if (entry.value)
     257           1 :       mooseError("The command line option '",
     258             :                  cli_switch,
     259             :                  "' is a boolean and does not support a value but the value '",
     260             :                  *entry.value,
     261             :                  "' was provided.\nDoc string: ",
     262           1 :                  param.description);
     263      190497 :     value = true;
     264             :   }
     265             :   // Option has arguments
     266             :   else
     267             :   {
     268             :     mooseAssert(param.metadata.argument_type != ArgumentType::NONE, "Incorrect argument type");
     269             : 
     270             :     // Helper for setting a value depending on its type and also throwing a useful
     271             :     // error when the conversion fails
     272      328576 :     const auto set_value = [&cli_switch](const std::string & from, auto & value)
     273             :     {
     274             :       using type = typename std::remove_reference<decltype(value)>::type;
     275             : 
     276             :       // Keep track of and change the throw on error characteristics so that
     277             :       // we can catch parsing errors for the argument
     278      107600 :       const auto throw_on_error_orig = Moose::_throw_on_error;
     279      107600 :       Moose::_throw_on_error = true;
     280             : 
     281             :       try
     282             :       {
     283             :         if constexpr (std::is_same_v<type, std::string> || std::is_same_v<type, MooseEnum>)
     284      107465 :           value = from;
     285             :         else
     286         135 :           value = MooseUtils::convert<type>(from, true);
     287             :       }
     288           8 :       catch (std::exception & e)
     289             :       {
     290           4 :         Moose::_throw_on_error = throw_on_error_orig;
     291           4 :         mooseError("While parsing command line option '",
     292             :                    cli_switch,
     293             :                    "' with value '",
     294             :                    from,
     295             :                    "':\n\n",
     296           4 :                    e.what());
     297             :       }
     298             : 
     299      107596 :       Moose::_throw_on_error = throw_on_error_orig;
     300             :     };
     301             : 
     302             :     // If a value doesn't exist, check the next argument to see if it
     303             :     // would work. This is needed for when we have argument values that
     304             :     // have = signs that get split. For example:
     305             :     // "--required-capabilities 'petsc>=3.11'" would get split into:
     306             :     //   - "--required-capabilities" with no value
     307             :     //   - "petsc>" with value "3.11"
     308             :     // which we want to re-combine into
     309             :     //   - "--required-capabilities" with value "petsc>=3.11"
     310      113376 :     if (!entry.value)
     311             :     {
     312        6242 :       auto next_entry_it = std::next(entry_it);
     313        6242 :       if (next_entry_it != _entries.end())
     314             :       {
     315        6239 :         const auto & next_entry = *next_entry_it;
     316        2893 :         if (!next_entry.used &&                    // isn't already used
     317        2879 :             (required || !next_entry.hit_param) && // if required, get the last value. if not, get
     318             :                                                    // it if it's not a hit param
     319        9111 :             !MooseUtils::beginsWith(next_entry.name, "-") && // doesn't start with a -
     320          14 :             next_entry.value_separator &&                    // has a separator
     321        9146 :             *next_entry.value_separator == "=" &&            // that separator is =
     322             :             next_entry.value)                                // and has a value after the =
     323             :         {
     324          14 :           const auto & next_entry = *next_entry_it;
     325             :           // Merge with the next Entry object and remove said next object
     326          14 :           entry.value = next_entry.name + *next_entry.value_separator + *next_entry.value;
     327          28 :           entry.raw_args.insert(
     328          14 :               entry.raw_args.end(), next_entry.raw_args.begin(), next_entry.raw_args.end());
     329          14 :           _entries.erase(next_entry_it);
     330             :         }
     331             :       }
     332             :     }
     333             : 
     334             :     // If we have a value, set the parameter to it
     335      113376 :     if (entry.value)
     336             :     {
     337             :       // For vector<string>, we need to unpack the values
     338             :       if constexpr (std::is_same_v<T, std::vector<std::string>>)
     339             :       {
     340      101999 :         std::vector<std::string> split_values;
     341      101999 :         MooseUtils::tokenize(*entry.value, split_values, 1, " ");
     342      101999 :         value.resize(split_values.size());
     343      204450 :         for (const auto i : index_range(split_values))
     344      102451 :           set_value(split_values[i], value[i]);
     345      101999 :       }
     346             :       // For everything else, we can set them directly
     347             :       else
     348        5149 :         set_value(*entry.value, value);
     349             :     }
     350             :     // No value, but one is required
     351        6228 :     else if (param.metadata.argument_type == ArgumentType::REQUIRED)
     352           3 :       mooseError("The command line option '",
     353             :                  cli_switch,
     354             :                  "' requires a value and one was not provided.\nDoc string: ",
     355           3 :                  param.description);
     356             :   }
     357      303866 : }

Generated by: LCOV version 1.14