LCOV - code coverage report
Current view: top level - src/parser - CommandLine.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 223 228 97.8 %
Date: 2025-07-17 01:28:37 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      102745 : CommandLine::CommandLine(int argc, char * argv[]) { addArguments(argc, argv); }
      29       11410 : CommandLine::CommandLine(const std::vector<std::string> & args) { addArguments(args); }
      30             : 
      31             : void
      32      102745 : CommandLine::addArguments(int argc, char * argv[])
      33             : {
      34     1175168 :   for (int i = 0; i < argc; i++)
      35     1072423 :     addArgument(argv[i]);
      36      102745 : }
      37             : 
      38             : void
      39     1116941 : CommandLine::addArgument(const std::string & arg)
      40             : {
      41             :   mooseAssert(!hasParsed(), "Has already parsed");
      42     1116941 :   _argv.push_back(arg);
      43     1116941 : }
      44             : 
      45             : void
      46       11417 : CommandLine::addArguments(const std::vector<std::string> & args)
      47             : {
      48       55597 :   for (const auto & arg : args)
      49       44180 :     addArgument(arg);
      50       11417 : }
      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      114274 : 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      114274 :   bool has_value_accepting_entry = false;
      76             : 
      77             :   // Helper for adding an entry
      78      902594 :   auto add_entry = [this](const auto & name) -> Entry &
      79             :   {
      80      902594 :     auto & entry = _entries.emplace_back();
      81      902594 :     entry.name = name;
      82      902594 :     return entry;
      83      114274 :   };
      84             : 
      85             :   // Work through each argument
      86     1231204 :   for (const auto i : index_range(_argv))
      87             :   {
      88     1116938 :     const auto & arg = _argv[i];
      89     1116938 :     const auto begins_with_dash = MooseUtils::beginsWith(arg, "-");
      90     1116938 :     std::string subapp_prefix, subapp_name, hit_path, hit_value;
      91             : 
      92             :     // MultiApp syntax with a non-hit option
      93     1116938 :     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     1641448 :     else if (!begins_with_dash &&
     105     1641448 :              pcrecpp::RE("(([^\\s\n\t[\\]\\/=#&:]+)?:)?((?:[^\\s\n\t[\\]=#&]+\\/"
     106             :                          ")?(?:[^\\s\n\t=#$'\"]+))=([^#]+)?")
     107     1641448 :                  .FullMatch(arg, &subapp_prefix, &subapp_name, &hit_path, &hit_value))
     108             :     {
     109      207651 :       auto & entry = add_entry(hit_path);
     110      207651 :       if (subapp_prefix.size())
     111             :       {
     112         788 :         if (subapp_name.empty()) // :param=value; means apply to all
     113         141 :           entry.global = true;
     114             :         else
     115         647 :           entry.subapp_name = subapp_name;
     116             :       }
     117      207651 :       entry.value = MooseUtils::removeExtraWhitespace(hit_value);
     118      207651 :       entry.value_separator = "=";
     119      207651 :       entry.raw_args.push_back(arg);
     120      207651 :       entry.hit_param = true;
     121      207651 :       has_value_accepting_entry = false;
     122             :     }
     123             :     // Has an = sign in it, so we have a name=value (non-HIT)
     124      909281 :     else if (const auto find_equals = arg.find("="); find_equals != std::string::npos)
     125             :     {
     126       15951 :       const auto begin = arg.substr(0, find_equals);
     127       15951 :       const auto end = arg.substr(find_equals + 1);
     128       15951 :       auto & entry = add_entry(begin);
     129       15951 :       entry.value = MooseUtils::removeExtraWhitespace(end);
     130       15951 :       entry.value_separator = "=";
     131       15951 :       entry.raw_args.push_back(arg);
     132       15951 :       has_value_accepting_entry = false;
     133       15951 :     }
     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      893330 :     else if (std::regex_search(arg, std::regex("^\\-+[a-zA-Z]")))
     138             :     {
     139      576461 :       auto & entry = add_entry(arg);
     140      576461 :       entry.raw_args.push_back(arg);
     141      576461 :       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      316869 :       if (i == 0)
     148             :       {
     149      102531 :         auto & entry = add_entry(arg);
     150      102531 :         entry.raw_args.push_back(arg);
     151      102531 :         continue;
     152      102531 :       }
     153             : 
     154             :       // Throw an error if this a value and we don't have anything to apply it to
     155      214338 :       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      214336 :       auto & entry = _entries.back();
     173      214336 :       if (entry.value)
     174         422 :         *entry.value += " " + MooseUtils::removeExtraWhitespace(arg);
     175             :       else
     176      213914 :         entry.value = MooseUtils::removeExtraWhitespace(arg);
     177      214336 :       entry.value_separator = " ";
     178      214336 :       entry.raw_args.push_back(arg);
     179             :     }
     180     1424555 :   }
     181             : 
     182      114266 :   _has_parsed = true;
     183      114266 : }
     184             : 
     185             : const std::list<CommandLine::Entry> &
     186     4530175 : CommandLine::getEntries() const
     187             : {
     188             :   mooseAssert(hasParsed(), "Has not parsed");
     189     4530175 :   return _entries;
     190             : }
     191             : 
     192             : std::list<CommandLine::Entry> &
     193       84998 : CommandLine::getEntries()
     194             : {
     195             :   mooseAssert(hasParsed(), "Has not parsed");
     196       84998 :   return _entries;
     197             : }
     198             : 
     199      165991 : CommandLine::~CommandLine() {}
     200             : 
     201             : std::unique_ptr<CommandLine>
     202       11403 : 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       11403 :   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       18224 :   for (const auto & arg : input_cli_args)
     214        6821 :     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      113814 :   for (auto & entry : as_range(getEntries().begin(), getEntries().end()))
     220      168206 :     if (entry.global || (entry.subapp_name && (*entry.subapp_name == multiapp_name ||
     221       65795 :                                                *entry.subapp_name == subapp_name)))
     222             :     {
     223       37295 :       if (entry.hit_param)
     224             :       {
     225             :         // Append : to the beginning if this is global and should be passed to all
     226         500 :         const std::string prefix = entry.global ? ":" : "";
     227             :         // Apply the param, but without the subapp name
     228         500 :         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         500 :         entry.used = true;
     231         500 :       }
     232             :       else
     233       36795 :         subapp_args.insert(subapp_args.end(), entry.raw_args.begin(), entry.raw_args.end());
     234             :     }
     235             : 
     236       22806 :   return std::make_unique<CommandLine>(subapp_args);
     237       11403 : }
     238             : 
     239             : std::string
     240       62192 : CommandLine::buildHitParams()
     241             : {
     242             :   mooseAssert(_command_line_params_populated, "Must populate command line params first");
     243             : 
     244       62192 :   std::vector<std::string> params;
     245             : 
     246             :   // Collect all hit parameters that aren't for subapps
     247      533166 :   for (auto & entry : getEntries())
     248      470974 :     if (entry.hit_param && !entry.subapp_name)
     249             :     {
     250             :       mooseAssert(entry.value, "Should have a value");
     251             :       mooseAssert(entry.value_separator, "Should have value separator");
     252             :       mooseAssert(*entry.value_separator == "=", "Should be an equals");
     253             : 
     254      106869 :       const std::string name_and_equals = entry.name + *entry.value_separator;
     255      106869 :       std::string arg = name_and_equals;
     256             :       // In the case of empty values, we want them to be empty to hit
     257      106869 :       if (entry.value->empty())
     258        1063 :         arg += "''";
     259             :       else
     260      105806 :         arg += *entry.value;
     261             : 
     262             :       // We could have issues with bash eating strings, so the first try
     263             :       // gives us a chance to wrap the value in quotes
     264             :       try
     265             :       {
     266      117089 :         hit::check("CLI_ARG", arg);
     267             :       }
     268        5110 :       catch (hit::ParseError & err)
     269             :       {
     270             :         // bash might have eaten quotes around a hit string value or vector
     271             :         // so try quoting after the "=" and reparse
     272        5110 :         arg = name_and_equals + "'" + *entry.value + "'";
     273             :         try
     274             :         {
     275        5110 :           hit::check("CLI_ARG", arg);
     276             :         }
     277             :         // At this point, we've failed to fix it
     278           0 :         catch (hit::ParseError & err)
     279             :         {
     280           0 :           mooseError("Failed to parse HIT in command line argument '", arg, "'\n\n", err.what());
     281           0 :         }
     282        5110 :       }
     283             : 
     284             :       // Append to the total output
     285      106869 :       params.push_back(arg);
     286             :       // Consider this parameter used
     287      106869 :       entry.used = true;
     288      106869 :     }
     289             : 
     290      124384 :   return MooseUtils::stringJoin(params, " ");
     291       62192 : }
     292             : 
     293             : void
     294      114227 : CommandLine::populateCommandLineParams(InputParameters & params)
     295             : {
     296             :   mooseAssert(!_command_line_params_populated, "Already populated");
     297             : 
     298             :   // Set the metadata for each command line parameter
     299             :   // We set this separately so that it can be used later to print usage
     300      114227 :   std::map<std::string, std::string> switch_param_map;
     301     5749698 :   for (const auto & name_value_pair : params)
     302             :   {
     303     5635472 :     const auto & name = name_value_pair.first;
     304     5635472 :     if (const auto metadata = params.queryCommandLineMetadata(name))
     305             :     {
     306     4557237 :       auto it_inserted_pair = _command_line_params.emplace(name, CommandLineParam());
     307     4557237 :       auto & option = it_inserted_pair.first->second;
     308             : 
     309     4557237 :       option.description = params.getDocString(name);
     310     4557237 :       option.metadata = *metadata;
     311             : 
     312             :       // Make sure that one switch isn't specified for multiple parameters
     313     9491081 :       for (const auto & cli_switch : option.metadata.switches)
     314     4933845 :         if (const auto it_inserted_pair = switch_param_map.emplace(cli_switch, name);
     315     4933845 :             !it_inserted_pair.second)
     316           1 :           mooseError("The command line options '",
     317           1 :                      it_inserted_pair.first->second,
     318             :                      "' and '",
     319             :                      name,
     320             :                      "' both declare the command line switch '",
     321             :                      cli_switch,
     322             :                      "'");
     323     5635472 :     }
     324             :   }
     325             : 
     326             :   // Set each paramter that we have a value for
     327     4671452 :   for (const auto & [name, param] : _command_line_params)
     328             :   {
     329     4557235 :     auto entry_it = findCommandLineParam(name);
     330     4557235 :     if (entry_it != _entries.end())
     331             :     {
     332      351469 :       auto & entry = *entry_it;
     333             :       mooseAssert(!entry.subapp_name, "Should not be for a subapp");
     334             : 
     335      351469 :       bool found = false;
     336             : #define trySetParameter(type)                                                                      \
     337             :   if (!found && params.have_parameter<type>(name))                                                 \
     338             :   {                                                                                                \
     339             :     static_assert(InputParameters::isValidCommandLineType<type>::value, "Not a supported value");  \
     340             :     auto & value = params.set<type>(name);                                                         \
     341             :     setCommandLineParam(entry_it, param, entry.name, value);                                       \
     342             :     found = true;                                                                                  \
     343             :   }
     344             : 
     345      351469 :       trySetParameter(std::string);
     346      351467 :       trySetParameter(std::vector<std::string>);
     347      351467 :       trySetParameter(Real);
     348      351466 :       trySetParameter(unsigned int);
     349      351465 :       trySetParameter(int);
     350      351464 :       trySetParameter(bool);
     351      351463 :       trySetParameter(MooseEnum);
     352             : #undef trySetParameter
     353             : 
     354             :       mooseAssert(found, "Should have been found");
     355             : 
     356             :       // If we found this parameter, that means we set it and we should mark in the
     357             :       // InputParameters that it is set so that isParamSetByUser() returns true for this param
     358      351461 :       params.commandLineParamSet(name, {});
     359             : 
     360             :       // If this parameter is global, mark its entry as global
     361      351461 :       if (param.metadata.global)
     362      194065 :         entry.global = true;
     363             : 
     364             :       // If the arg is of the form "--key=value", PETSc will recognize it as an unused
     365             :       // argument. That is, setting "--key" as a known command line argument is not
     366             :       // sufficient for PETSc to consider "--key=value" as known. Thus, we explicitly
     367             :       // add "--key=value" args as known when we come across them.
     368      351461 :       if (entry.value_separator && *entry.value_separator == "=")
     369             :       {
     370             :         mooseAssert(entry.raw_args.size() == 1, "Should have one value");
     371        2892 :         libMesh::add_command_line_name(entry.raw_args[0]);
     372             :       }
     373             :     }
     374             :     // If we didn't find it and it is required, we need to error
     375     4205766 :     else if (param.metadata.required)
     376           1 :       mooseError(
     377           1 :           "Missing required command-line parameter: ", name, "\nDoc string: ", param.description);
     378             :   }
     379             : 
     380      114217 :   _command_line_params_populated = true;
     381      114227 : }
     382             : 
     383             : std::string
     384          64 : CommandLine::getExecutableName() const
     385             : {
     386             :   mooseAssert(_entries.size() > 0, "Does not have any entries");
     387             : 
     388             :   // Grab the first item out of argv
     389          64 :   const auto & command = _entries.begin()->name;
     390          64 :   return command.substr(command.find_last_of("/\\") + 1);
     391             : }
     392             : 
     393             : std::string
     394          14 : CommandLine::getExecutableNameBase() const
     395             : {
     396          14 :   auto name = getExecutableName();
     397          14 :   name = name.substr(0, name.find_last_of("-"));
     398          14 :   if (name.find_first_of("/") != std::string::npos)
     399           0 :     name = name.substr(name.find_first_of("/") + 1, std::string::npos);
     400          14 :   return name;
     401           0 : }
     402             : 
     403             : void
     404          29 : CommandLine::printUsage() const
     405             : {
     406          29 :   Moose::out << "Usage: " << getExecutableName() << " [<options>]\n\n";
     407             : 
     408          29 :   std::size_t max_len = 0;
     409        2064 :   for (const auto & name_option_pair : _command_line_params)
     410        2035 :     max_len = std::max(max_len, name_option_pair.second.metadata.syntax.size());
     411             : 
     412        2151 :   const auto output_options = [this, &max_len](const bool global)
     413             :   {
     414          58 :     Moose::out << (global ? "Global " : "") << "Options:\n" << std::left;
     415        4128 :     for (const auto & name_option_pair : _command_line_params)
     416             :     {
     417        4070 :       const auto & option = name_option_pair.second;
     418        4070 :       if (option.metadata.syntax.empty() || option.metadata.global != global)
     419        2035 :         continue;
     420             : 
     421        4070 :       Moose::out << "  " << std::setw(max_len + 2) << option.metadata.syntax << option.description
     422        2035 :                  << "\n";
     423             :     }
     424          58 :     Moose::out << "\n";
     425          58 :   };
     426             : 
     427          29 :   output_options(false);
     428          29 :   output_options(true);
     429             : 
     430          29 :   Moose::out << "Solver Options:\n"
     431          29 :              << "  See PETSc manual for details" << std::endl;
     432             : 
     433             :   // If we get here, we are not running a simulation and should silence petsc's unused options
     434             :   // warning
     435          29 :   Moose::PetscSupport::setSinglePetscOption("-options_left", "0");
     436          29 : }
     437             : 
     438             : std::vector<std::string>
     439       57244 : CommandLine::unusedHitParams(const Parallel::Communicator & comm) const
     440             : {
     441             :   libmesh_parallel_only(comm);
     442             : 
     443       57244 :   std::vector<const Entry *> hit_params;
     444       57244 :   std::vector<std::size_t> use_count;
     445      492151 :   for (const auto & entry : getEntries())
     446      434907 :     if (entry.hit_param)
     447             :     {
     448       99024 :       hit_params.push_back(&entry);
     449       99024 :       use_count.push_back(entry.used ? 1 : 0);
     450             :     }
     451             : 
     452             :   mooseAssert(comm.verify(use_count.size()), "Inconsistent HIT params across procs");
     453       57244 :   comm.sum(use_count);
     454             : 
     455       57244 :   std::vector<std::string> unused;
     456      156268 :   for (const auto i : index_range(use_count))
     457       99024 :     if (use_count[i] == 0)
     458           4 :       unused.push_back(hit_params[i]->raw_args[0]);
     459      114488 :   return unused;
     460       57244 : }
     461             : 
     462             : std::list<CommandLine::Entry>::const_iterator
     463     4557245 : CommandLine::findCommandLineParam(const std::string & name) const
     464             : {
     465     4557245 :   const auto find_param = _command_line_params.find(name);
     466     4557245 :   if (find_param == _command_line_params.end())
     467           1 :     mooseError("CommandLine::findCommandLineParam(): The parameter '",
     468             :                name,
     469             :                "' is not a command line parameter");
     470             : 
     471             :   // Search for the last thing that matches a switch from this param
     472     4557244 :   const auto & param = find_param->second;
     473    37417813 :   for (auto rit = _entries.rbegin(); rit != _entries.rend(); ++rit)
     474    68377221 :     for (const auto & search_switch : param.metadata.switches)
     475    35516652 :       if (rit->name == search_switch)
     476      351477 :         return --(rit.base());
     477             : 
     478     4205767 :   return getEntries().end();
     479             : }
     480             : 
     481             : std::list<CommandLine::Entry>::iterator
     482     4557235 : CommandLine::findCommandLineParam(const std::string & name)
     483             : {
     484     4557235 :   const auto it = std::as_const(*this).findCommandLineParam(name);
     485             :   // Easy way to go from a const iterator -> non-const iterator
     486     9114470 :   return _entries.erase(it, it);
     487             : }
     488             : 
     489             : std::string
     490      479953 : CommandLine::formatEntry(const CommandLine::Entry & entry) const
     491             : {
     492      479953 :   std::stringstream oss;
     493      479953 :   oss << entry.name;
     494      479953 :   if (entry.value)
     495             :   {
     496      222131 :     const auto q = (*entry.value).find(" ") != std::string::npos ? "'" : "";
     497      222131 :     oss << *entry.value_separator << q << *entry.value << q;
     498             :   }
     499      959906 :   return oss.str();
     500      479953 : }

Generated by: LCOV version 1.14