LCOV - code coverage report
Current view: top level - src/parser - CommandLine.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: fef103 Lines: 223 229 97.4 %
Date: 2025-09-03 20:01:23 Functions: 25 25 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             : #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             : 
      27         121 : CommandLine::CommandLine() {}
      28      112173 : CommandLine::CommandLine(int argc, char * argv[]) { addArguments(argc, argv); }
      29       12435 : CommandLine::CommandLine(const std::vector<std::string> & args) { addArguments(args); }
      30             : 
      31             : void
      32      112173 : CommandLine::addArguments(int argc, char * argv[])
      33             : {
      34     1306527 :   for (int i = 0; i < argc; i++)
      35     2388708 :     addArgument(argv[i]);
      36      112173 : }
      37             : 
      38             : void
      39     1257698 : CommandLine::addArgument(const std::string & arg)
      40             : {
      41             :   mooseAssert(!hasParsed(), "Has already parsed");
      42     1257698 :   _argv.push_back(arg);
      43     1257698 : }
      44             : 
      45             : void
      46       12444 : CommandLine::addArguments(const std::vector<std::string> & args)
      47             : {
      48       75450 :   for (const auto & arg : args)
      49       63006 :     addArgument(arg);
      50       12444 : }
      51             : 
      52             : bool
      53          10 : CommandLine::hasArgument(const std::string & arg) const
      54             : {
      55          10 :   return std::find(_argv.begin(), _argv.end(), arg) != _argv.end();
      56             : }
      57             : 
      58             : void
      59           2 : CommandLine::removeArgument(const std::string & arg)
      60             : {
      61             :   mooseAssert(!hasParsed(), "Has already parsed");
      62           2 :   auto it = std::find(_argv.begin(), _argv.end(), arg);
      63           2 :   if (it == _argv.end())
      64           1 :     mooseError("CommandLine::removeArgument(): The argument '", arg, "' does not exist");
      65           1 :   _argv.erase(it);
      66           1 : }
      67             : 
      68             : void
      69      124727 : CommandLine::parse()
      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      124727 :   bool has_value_accepting_entry = false;
      76             : 
      77             :   // Helper for adding an entry
      78     1117586 :   auto add_entry = [this](const auto & name) -> Entry &
      79             :   {
      80     1117586 :     auto & entry = _entries.emplace_back();
      81     1117586 :     entry.name = name;
      82     1117586 :     return entry;
      83      124727 :   };
      84             : 
      85             :   // Work through each argument
      86     1382414 :   for (const auto i : index_range(_argv))
      87             :   {
      88     1257695 :     const auto & arg = _argv[i];
      89     1257695 :     const auto begins_with_dash = MooseUtils::beginsWith(arg, "-");
      90     1257695 :     std::string subapp_prefix, subapp_name, hit_path, hit_value;
      91             : 
      92             :     // MultiApp syntax with a non-hit option
      93     1257695 :     if (!begins_with_dash && std::regex_search(arg, std::regex("^(([^\\s\n\t[\\]\\/=#&:]+):)[-]")))
      94             :     {
      95           6 :       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     1735953 :     else if (!begins_with_dash &&
     105     1735953 :              pcrecpp::RE("(([^\\s\n\t[\\]\\/=#&:]+)?:)?((?:[^\\s\n\t[\\]=#&]+\\/"
     106             :                          ")?(?:[^\\s\n\t=#$'\"]+))=([^#]+)?")
     107     1735953 :                  .FullMatch(arg, &subapp_prefix, &subapp_name, &hit_path, &hit_value))
     108             :     {
     109      226204 :       auto & entry = add_entry(hit_path);
     110      226204 :       if (subapp_prefix.size())
     111             :       {
     112         848 :         if (subapp_name.empty()) // :param=value; means apply to all
     113         151 :           entry.global = true;
     114             :         else
     115         697 :           entry.subapp_name = subapp_name;
     116             :       }
     117      226204 :       entry.value = MooseUtils::removeExtraWhitespace(hit_value);
     118      226204 :       entry.value_separator = "=";
     119      226204 :       entry.raw_args.push_back(arg);
     120      226204 :       entry.hit_param = true;
     121      226204 :       has_value_accepting_entry = false;
     122             :     }
     123             :     // Has an = sign in it, so we have a name=value (non-HIT)
     124     1031485 :     else if (const auto find_equals = arg.find("="); find_equals != std::string::npos)
     125             :     {
     126      121879 :       const auto begin = arg.substr(0, find_equals);
     127      121879 :       const auto end = arg.substr(find_equals + 1);
     128      121879 :       auto & entry = add_entry(begin);
     129      121879 :       entry.value = MooseUtils::removeExtraWhitespace(end);
     130      121879 :       entry.value_separator = "=";
     131      121879 :       entry.raw_args.push_back(arg);
     132      121879 :       has_value_accepting_entry = false;
     133      121879 :     }
     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      909606 :     else if (std::regex_search(arg, std::regex("^\\-+[a-zA-Z]")))
     138             :     {
     139      657542 :       auto & entry = add_entry(arg);
     140      657542 :       entry.raw_args.push_back(arg);
     141      657542 :       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      252064 :       if (i == 0)
     148             :       {
     149      111961 :         auto & entry = add_entry(arg);
     150      111961 :         entry.raw_args.push_back(arg);
     151      111961 :         continue;
     152      111961 :       }
     153             : 
     154             :       // Throw an error if this a value and we don't have anything to apply it to
     155      140103 :       if (!has_value_accepting_entry)
     156             :       {
     157           2 :         std::stringstream err;
     158             :         err << "The command line argument '" << arg
     159           2 :             << "' 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           2 :         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           1 :               << _entries.back().name << *_entries.back().value_separator << "'"
     167           2 :               << *_entries.back().value << " " << arg << "'\n";
     168             :         }
     169           4 :         mooseError(err.str());
     170           2 :       }
     171             : 
     172      140101 :       auto & entry = _entries.back();
     173      140101 :       if (entry.value)
     174         458 :         *entry.value += " " + MooseUtils::removeExtraWhitespace(arg);
     175             :       else
     176      139643 :         entry.value = MooseUtils::removeExtraWhitespace(arg);
     177      140101 :       entry.value_separator = " ";
     178      140101 :       entry.raw_args.push_back(arg);
     179             :     }
     180     1593602 :   }
     181             : 
     182      124719 :   _has_parsed = true;
     183      124719 : }
     184             : 
     185             : const std::list<CommandLine::Entry> &
     186     4938206 : CommandLine::getEntries() const
     187             : {
     188             :   mooseAssert(hasParsed(), "Has not parsed");
     189     4938206 :   return _entries;
     190             : }
     191             : 
     192             : std::list<CommandLine::Entry> &
     193       93367 : CommandLine::getEntries()
     194             : {
     195             :   mooseAssert(hasParsed(), "Has not parsed");
     196       93367 :   return _entries;
     197             : }
     198             : 
     199      181936 : CommandLine::~CommandLine() {}
     200             : 
     201             : std::unique_ptr<CommandLine>
     202       12426 : CommandLine::initSubAppCommandLine(const std::string & multiapp_name,
     203             :                                    const std::string & subapp_name,
     204             :                                    const std::vector<std::string> & input_cli_args)
     205             : {
     206             :   mooseAssert(MooseUtils::beginsWith(subapp_name, multiapp_name),
     207             :               "Name for the subapp should begin with the multiapp");
     208             : 
     209       12426 :   std::vector<std::string> subapp_args;
     210             : 
     211             :   // Start with the arguments from the input file; we want these to take the
     212             :   // lowest priority so we put them first. Also trim extra whitespace
     213       19866 :   for (const auto & arg : input_cli_args)
     214        7440 :     subapp_args.push_back(MooseUtils::removeExtraWhitespace(arg));
     215             : 
     216             :   // Pull out all of the arguments that are relevant to this multiapp from the parent
     217             :   // Note that the 0th argument of the main app, i.e., the name used to invoke the program,
     218             :   // is neither a global entry nor a subapp entry and, as such, won't be passed on
     219      138262 :   for (auto & entry : as_range(getEntries().begin(), getEntries().end()))
     220      198285 :     if (entry.global || (entry.subapp_name && (*entry.subapp_name == multiapp_name ||
     221       72449 :                                                *entry.subapp_name == subapp_name)))
     222             :     {
     223       54126 :       if (entry.hit_param)
     224             :       {
     225             :         // Append : to the beginning if this is global and should be passed to all
     226         547 :         const std::string prefix = entry.global ? ":" : "";
     227             :         // Apply the param, but without the subapp name
     228         547 :         subapp_args.push_back(prefix + entry.name + *entry.value_separator + *entry.value);
     229             :         // Mark this entry as used as a child has consumed it
     230         547 :         entry.used = true;
     231         547 :       }
     232             :       else
     233       53579 :         subapp_args.insert(subapp_args.end(), entry.raw_args.begin(), entry.raw_args.end());
     234             :     }
     235             : 
     236       24852 :   return std::make_unique<CommandLine>(subapp_args);
     237       12426 : }
     238             : 
     239             : std::vector<std::string>
     240       68515 : CommandLine::buildHitParams()
     241             : {
     242             :   mooseAssert(!_hit_params_built, "Already built");
     243       68515 :   _hit_params_built = true;
     244             : 
     245       68515 :   std::vector<std::string> params;
     246             : 
     247             :   // Collect all hit parameters that aren't for subapps
     248      657970 :   for (auto & entry : getEntries())
     249      589455 :     if (entry.hit_param && !entry.subapp_name)
     250             :     {
     251             :       mooseAssert(entry.value, "Should have a value");
     252             :       mooseAssert(entry.value_separator, "Should have value separator");
     253             :       mooseAssert(*entry.value_separator == "=", "Should be an equals");
     254             : 
     255      116703 :       const std::string name_and_equals = entry.name + *entry.value_separator;
     256      116703 :       std::string arg = name_and_equals;
     257             :       // In the case of empty values, we want them to be empty to hit
     258      116703 :       if (entry.value->empty())
     259        1126 :         arg += "''";
     260             :       else
     261      115577 :         arg += *entry.value;
     262             : 
     263             :       // We could have issues with bash eating strings, so the first try
     264             :       // gives us a chance to wrap the value in quotes
     265             :       try
     266             :       {
     267      233406 :         hit::check("CLI_ARG", arg);
     268             :       }
     269        5601 :       catch (hit::Error & err)
     270             :       {
     271             :         // bash might have eaten quotes around a hit string value or vector
     272             :         // so try quoting after the "=" and reparse
     273        5601 :         arg = name_and_equals + "'" + *entry.value + "'";
     274             :         try
     275             :         {
     276        5601 :           hit::check("CLI_ARG", arg);
     277             :         }
     278             :         // At this point, we've failed to fix it
     279           0 :         catch (hit::Error & err)
     280             :         {
     281           0 :           mooseError("Failed to parse HIT in command line argument '", arg, "'\n\n", err.what());
     282           0 :         }
     283        5601 :       }
     284             : 
     285             :       // Append to the total output
     286      116703 :       params.push_back(arg);
     287             :       // Consider this parameter used
     288      116703 :       entry.used = true;
     289      116703 :     }
     290             : 
     291       68515 :   return params;
     292           0 : }
     293             : 
     294             : void
     295      124628 : CommandLine::populateCommandLineParams(InputParameters & params)
     296             : {
     297             :   mooseAssert(!_command_line_params_populated, "Already populated");
     298             : 
     299             :   // Set the metadata for each command line parameter
     300             :   // We set this separately so that it can be used later to print usage
     301      124628 :   std::map<std::string, std::string> switch_param_map;
     302     6499232 :   for (const auto & name_value_pair : params)
     303             :   {
     304     6374605 :     const auto & name = name_value_pair.first;
     305     6374605 :     if (const auto metadata = params.queryCommandLineMetadata(name))
     306             :     {
     307     5040150 :       auto it_inserted_pair = _command_line_params.emplace(name, CommandLineParam());
     308     5040150 :       auto & option = it_inserted_pair.first->second;
     309             : 
     310     5040150 :       option.description = params.getDocString(name);
     311     5040150 :       option.metadata = *metadata;
     312             : 
     313             :       // Make sure that one switch isn't specified for multiple parameters
     314    10491137 :       for (const auto & cli_switch : option.metadata.switches)
     315     5450988 :         if (const auto it_inserted_pair = switch_param_map.emplace(cli_switch, name);
     316     5450988 :             !it_inserted_pair.second)
     317           1 :           mooseError("The command line options '",
     318           1 :                      it_inserted_pair.first->second,
     319             :                      "' and '",
     320             :                      name,
     321             :                      "' both declare the command line switch '",
     322             :                      cli_switch,
     323             :                      "'");
     324     6374605 :     }
     325             :   }
     326             : 
     327             :   // Set each paramter that we have a value for
     328     5164766 :   for (const auto & [name, param] : _command_line_params)
     329             :   {
     330     5040148 :     auto entry_it = findCommandLineParam(name);
     331     5040148 :     if (entry_it != _entries.end())
     332             :     {
     333      457598 :       auto & entry = *entry_it;
     334             :       mooseAssert(!entry.subapp_name, "Should not be for a subapp");
     335             : 
     336      457598 :       bool found = false;
     337             : #define trySetParameter(type)                                                                      \
     338             :   if (!found && params.have_parameter<type>(name))                                                 \
     339             :   {                                                                                                \
     340             :     static_assert(InputParameters::isValidCommandLineType<type>::value, "Not a supported value");  \
     341             :     auto & value = params.set<type>(name);                                                         \
     342             :     setCommandLineParam(entry_it, param, entry.name, value);                                       \
     343             :     found = true;                                                                                  \
     344             :   }
     345             : 
     346      457598 :       trySetParameter(std::string);
     347      457596 :       trySetParameter(std::vector<std::string>);
     348      457596 :       trySetParameter(Real);
     349      457595 :       trySetParameter(unsigned int);
     350      457594 :       trySetParameter(int);
     351      457593 :       trySetParameter(bool);
     352      457592 :       trySetParameter(MooseEnum);
     353             : #undef trySetParameter
     354             : 
     355             :       mooseAssert(found, "Should have been found");
     356             : 
     357             :       // If we found this parameter, that means we set it and we should mark in the
     358             :       // InputParameters that it is set so that isParamSetByUser() returns true for this param
     359      457590 :       params.commandLineParamSet(name, {});
     360             : 
     361             :       // If this parameter is global, mark its entry as global
     362      457590 :       if (param.metadata.global)
     363      281944 :         entry.global = true;
     364             : 
     365             :       // If the arg is of the form "--key=value", PETSc will recognize it as an unused
     366             :       // argument. That is, setting "--key" as a known command line argument is not
     367             :       // sufficient for PETSc to consider "--key=value" as known. Thus, we explicitly
     368             :       // add "--key=value" args as known when we come across them.
     369      457590 :       if (entry.value_separator && *entry.value_separator == "=")
     370             :       {
     371             :         mooseAssert(entry.raw_args.size() == 1, "Should have one value");
     372       55683 :         libMesh::add_command_line_name(entry.raw_args[0]);
     373             :       }
     374             :     }
     375             :     // If we didn't find it and it is required, we need to error
     376     4582550 :     else if (param.metadata.required)
     377           1 :       mooseError(
     378           1 :           "Missing required command-line parameter: ", name, "\nDoc string: ", param.description);
     379             :   }
     380             : 
     381      124618 :   _command_line_params_populated = true;
     382      124628 : }
     383             : 
     384             : std::string
     385          64 : CommandLine::getExecutableName() const
     386             : {
     387             :   mooseAssert(_entries.size() > 0, "Does not have any entries");
     388             : 
     389             :   // Grab the first item out of argv
     390          64 :   const auto & command = _entries.begin()->name;
     391          64 :   return command.substr(command.find_last_of("/\\") + 1);
     392             : }
     393             : 
     394             : std::string
     395          14 : CommandLine::getExecutableNameBase() const
     396             : {
     397          14 :   auto name = getExecutableName();
     398          14 :   name = name.substr(0, name.find_last_of("-"));
     399          14 :   if (name.find_first_of("/") != std::string::npos)
     400           0 :     name = name.substr(name.find_first_of("/") + 1, std::string::npos);
     401          14 :   return name;
     402           0 : }
     403             : 
     404             : void
     405          29 : CommandLine::printUsage() const
     406             : {
     407          29 :   Moose::out << "Usage: " << getExecutableName() << " [<options>]\n\n";
     408             : 
     409          29 :   std::size_t max_len = 0;
     410        2093 :   for (const auto & name_option_pair : _command_line_params)
     411        2064 :     max_len = std::max(max_len, name_option_pair.second.metadata.syntax.size());
     412             : 
     413          58 :   const auto output_options = [this, &max_len](const bool global)
     414             :   {
     415          58 :     Moose::out << (global ? "Global " : "") << "Options:\n" << std::left;
     416        4186 :     for (const auto & name_option_pair : _command_line_params)
     417             :     {
     418        4128 :       const auto & option = name_option_pair.second;
     419        4128 :       if (option.metadata.syntax.empty() || option.metadata.global != global)
     420        2064 :         continue;
     421             : 
     422        4128 :       Moose::out << "  " << std::setw(max_len + 2) << option.metadata.syntax << option.description
     423        2064 :                  << "\n";
     424             :     }
     425          58 :     Moose::out << "\n";
     426          58 :   };
     427             : 
     428          29 :   output_options(false);
     429          29 :   output_options(true);
     430             : 
     431          29 :   Moose::out << "Solver Options:\n"
     432          29 :              << "  See PETSc manual for details" << std::endl;
     433             : 
     434             :   // If we get here, we are not running a simulation and should silence petsc's unused options
     435             :   // warning
     436          87 :   Moose::PetscSupport::setSinglePetscOption("-options_left", "0");
     437          29 : }
     438             : 
     439             : std::vector<std::string>
     440       62840 : CommandLine::unusedHitParams(const Parallel::Communicator & comm) const
     441             : {
     442             :   libmesh_parallel_only(comm);
     443             : 
     444       62840 :   std::vector<const Entry *> hit_params;
     445       62840 :   std::vector<std::size_t> use_count;
     446      606245 :   for (const auto & entry : getEntries())
     447      543405 :     if (entry.hit_param)
     448             :     {
     449      108412 :       hit_params.push_back(&entry);
     450      108412 :       use_count.push_back(entry.used ? 1 : 0);
     451             :     }
     452             : 
     453             :   mooseAssert(comm.verify(use_count.size()), "Inconsistent HIT params across procs");
     454       62840 :   comm.sum(use_count);
     455             : 
     456       62840 :   std::vector<std::string> unused;
     457      171252 :   for (const auto i : index_range(use_count))
     458      108412 :     if (use_count[i] == 0)
     459           4 :       unused.push_back(hit_params[i]->raw_args[0]);
     460      125680 :   return unused;
     461       62840 : }
     462             : 
     463             : std::list<CommandLine::Entry>::const_iterator
     464     5040158 : CommandLine::findCommandLineParam(const std::string & name) const
     465             : {
     466     5040158 :   const auto find_param = _command_line_params.find(name);
     467     5040158 :   if (find_param == _command_line_params.end())
     468           1 :     mooseError("CommandLine::findCommandLineParam(): The parameter '",
     469             :                name,
     470             :                "' is not a command line parameter");
     471             : 
     472             :   // Search for the last thing that matches a switch from this param
     473     5040157 :   const auto & param = find_param->second;
     474    46186324 :   for (auto rit = _entries.rbegin(); rit != _entries.rend(); ++rit)
     475    85567529 :     for (const auto & search_switch : param.metadata.switches)
     476    44421362 :       if (rit->name == search_switch)
     477      457606 :         return --(rit.base());
     478             : 
     479     4582551 :   return getEntries().end();
     480             : }
     481             : 
     482             : std::list<CommandLine::Entry>::iterator
     483     5040148 : CommandLine::findCommandLineParam(const std::string & name)
     484             : {
     485     5040148 :   const auto it = std::as_const(*this).findCommandLineParam(name);
     486             :   // Easy way to go from a const iterator -> non-const iterator
     487    10080296 :   return _entries.erase(it, it);
     488             : }
     489             : 
     490             : std::string
     491      622598 : CommandLine::formatEntry(const CommandLine::Entry & entry) const
     492             : {
     493      622598 :   std::stringstream oss;
     494      622598 :   oss << entry.name;
     495      622598 :   if (entry.value)
     496             :   {
     497      250043 :     const auto q = (*entry.value).find(" ") != std::string::npos ? "'" : "";
     498      250043 :     oss << *entry.value_separator << q << *entry.value << q;
     499             :   }
     500     1245196 :   return oss.str();
     501      622598 : }

Generated by: LCOV version 1.14