LCOV - code coverage report
Current view: top level - EnergyPlus - CommandLineInterface.cc (source / functions) Coverage Total Hit
Test: lcov.output.filtered Lines: 61.8 % 456 282
Test Date: 2025-05-22 16:09:37 Functions: 83.3 % 6 5

            Line data    Source code
       1              : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
       2              : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3              : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4              : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5              : // contributors. All rights reserved.
       6              : //
       7              : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8              : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9              : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10              : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11              : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12              : //
      13              : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14              : // provided that the following conditions are met:
      15              : //
      16              : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17              : //     conditions and the following disclaimer.
      18              : //
      19              : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20              : //     conditions and the following disclaimer in the documentation and/or other materials
      21              : //     provided with the distribution.
      22              : //
      23              : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24              : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25              : //     used to endorse or promote products derived from this software without specific prior
      26              : //     written permission.
      27              : //
      28              : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29              : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30              : //     reference solely to the software portion of its product, Licensee must refer to the
      31              : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32              : //     obtained under this License and may not use a different name for the software. Except as
      33              : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34              : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35              : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36              : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37              : //
      38              : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39              : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40              : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41              : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42              : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43              : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44              : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45              : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46              : // POSSIBILITY OF SUCH DAMAGE.
      47              : 
      48              : // C++ Headers
      49              : #include <thread>
      50              : 
      51              : // CLI Headers
      52              : #include <CLI/CLI11.hpp>
      53              : 
      54              : // Project headers
      55              : #include <EnergyPlus/CommandLineInterface.hh>
      56              : #include <EnergyPlus/Data/EnergyPlusData.hh>
      57              : #include <EnergyPlus/DataStringGlobals.hh>
      58              : #include <EnergyPlus/DisplayRoutines.hh>
      59              : #include <EnergyPlus/EnergyPlus.hh>
      60              : #include <EnergyPlus/FileSystem.hh>
      61              : #include <EnergyPlus/PluginManager.hh>
      62              : #include <EnergyPlus/UtilityRoutines.hh>
      63              : 
      64              : #if LINK_WITH_PYTHON
      65              : #    include <EnergyPlus/PythonEngine.hh>
      66              : #endif
      67              : 
      68              : namespace EnergyPlus {
      69              : 
      70              : namespace CommandLineInterface {
      71              : 
      72           55 :     int ProcessArgs(EnergyPlusData &state, const std::vector<std::string> &args)
      73              :     {
      74              :         using size_type = std::string::size_type;
      75              : 
      76              :         // Expand long-name options using "=" sign in to two arguments
      77              :         // and expand multiple short options into separate arguments
      78           55 :         std::vector<std::string> arguments;
      79              : 
      80           55 :         std::string const dash("-");
      81              : 
      82          318 :         for (const auto &inputArg : args) {
      83              : 
      84          263 :             size_type const doubleDashPosition = inputArg.find("--");
      85          263 :             size_type const equalsPosition = inputArg.find("=");
      86              : 
      87          263 :             if (doubleDashPosition == 0 && equalsPosition != std::string::npos) { // --option=value
      88            0 :                 arguments.push_back(inputArg.substr(0, equalsPosition));
      89            0 :                 arguments.push_back(inputArg.substr(equalsPosition + 1, inputArg.size() - 1));
      90          263 :             } else if (doubleDashPosition == 0 && inputArg.size() == 2) {
      91              :                 // Filter it out, it's a bash-like separator (end of the command options, before positionals arguments are accepted)
      92          263 :             } else if ((inputArg.size() > 2) && (inputArg[0] == '-') && (inputArg[1] != '-')) { // -abc style
      93            0 :                 for (size_type c = 1; c < inputArg.size(); ++c) {
      94            0 :                     arguments.push_back(dash + inputArg[c]);
      95              :                 }
      96              :             } else { // ?
      97          263 :                 arguments.push_back(inputArg);
      98              :             }
      99              :         }
     100              : 
     101              :         // erase the first element, which is the name of the program
     102           55 :         const std::string programName = std::move(arguments.front());
     103           55 :         arguments.erase(arguments.begin());
     104              : 
     105           55 :         size_type const argCount = arguments.size();
     106           55 :         bool const legacyMode = (argCount == 0);
     107              : 
     108              :         // Set path of EnergyPlus program path (if we aren't overriding it)
     109           55 :         if (!state.dataGlobal->installRootOverride) {
     110           41 :             state.dataStrGlobals->exeDirectoryPath = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath()));
     111              :         }
     112              : 
     113          110 :         CLI::App app{"energyplus", programName};
     114              :         // opt.add("", false, 0, 0, "Display version information", "-v", "--version");
     115          165 :         app.set_version_flag("-v,--version", EnergyPlus::DataStringGlobals::VerString);
     116              : 
     117              :         std::string const description = fmt::format(R"({}
     118              : PythonLinkage: {}
     119              : Built on Platform: {}
     120              : )",
     121           55 :                                                     state.dataStrGlobals->VerStringVar,
     122           55 :                                                     PluginManagement::pythonStringForUsage(state),
     123           55 :                                                     DataStringGlobals::BuildPlatformString);
     124           55 :         app.description(description);
     125              : 
     126          220 :         auto *annualOpt = app.add_flag("-a,--annual", state.dataGlobal->AnnualSimulation, "Force annual simulation");
     127              : 
     128          220 :         app.add_flag("-D,--design-day", state.dataGlobal->DDOnlySimulation, "Force design-day-only simulation")->excludes(annualOpt);
     129              : 
     130           55 :         app.add_option("-d,--output-directory", state.dataStrGlobals->outDirPath, "Output directory path (default: current directory)")
     131          330 :             ->option_text("DIR")
     132           55 :             ->required(false);
     133              :         // ->check(CLI::ExistingDirectory) // We don't require it to exist, we make it if needed
     134              : 
     135           55 :         if (legacyMode) {
     136            2 :             state.dataStrGlobals->inputIddFilePath = "Energy+.idd";
     137              :         } else {
     138           53 :             state.dataStrGlobals->inputIddFilePath = state.dataStrGlobals->exeDirectoryPath / "Energy+.idd";
     139              :         }
     140          165 :         app.add_option(
     141           55 :                "-i,--idd", state.dataStrGlobals->inputIddFilePath, "Input data dictionary path (default: Energy+.idd in executable directory)")
     142              :             ->required(false)
     143              :             ->option_text("IDD")
     144          275 :             ->check(CLI::ExistingFile);
     145              : 
     146           55 :         bool runEPMacro = false;
     147          220 :         app.add_flag("-m,--epmacro", runEPMacro, "Run EPMacro prior to simulation");
     148              : 
     149          110 :         std::string prefixOutName = "eplus";
     150          330 :         app.add_option("-p,--output-prefix", prefixOutName, "Prefix for output file names (default: eplus)")->required(false)->option_text("PRE");
     151              : 
     152          220 :         app.add_flag("-r,--readvars", state.dataGlobal->runReadVars, "Run ReadVarsESO after simulation");
     153              : 
     154          220 :         app.add_flag("-c,--convert", state.dataGlobal->outputEpJSONConversion, "Output IDF->epJSON or epJSON->IDF, dependent on input file type");
     155              : 
     156          220 :         app.add_flag("--convert-only",
     157           55 :                      state.dataGlobal->outputEpJSONConversionOnly,
     158              :                      "Only convert IDF->epJSON or epJSON->IDF, dependent on input file type. No simulation");
     159              : 
     160          110 :         std::string suffixType = "L";
     161           55 :         std::string const suffixHelp = R"help(Suffix style for output file names (default: L)
     162              :    L: Legacy (e.g., eplustbl.csv)
     163              :    C: Capital (e.g., eplusTable.csv)
     164              :    D: Dash (e.g., eplus-table.csv))help";
     165              :         app.add_option("-s,--output-suffix", suffixType, suffixHelp)
     166          275 :             ->option_text("SUFFIX")
     167              :             ->required(false)
     168          165 :             ->check(CLI::IsMember({"L", "C", "D"}, CLI::ignore_case));
     169              : 
     170              :         // TODO: maybe delay validation to output a better error message?
     171           55 :         const int MAX_N = static_cast<int>(std::thread::hardware_concurrency());
     172              :         app.add_option("-j,--jobs",
     173           55 :                        state.dataGlobal->numThread,
     174              :                        "Multi-thread with N threads; 1 thread with no arg. (Currently only for G-Function generation)")
     175              :             ->option_text("N")
     176              :             // ->check(CLI::Range(1, MAX_N)  // Tempted to just do that... much simpler
     177              :             // ->check(CLI::Number)
     178          495 :             ->transform([MAX_N, &state](std::string input) -> std::string {
     179            6 :                 int number_of_threads = -1;
     180            6 :                 bool const converted = CLI::detail::lexical_cast(input, number_of_threads);
     181            6 :                 if (!converted) {
     182              :                     // CLI::ValidationError
     183            0 :                     return fmt::format("Argument should be an integer, not '{}'", input);
     184              :                 }
     185            6 :                 if (number_of_threads <= 0) {
     186            2 :                     DisplayString(state, "Invalid value for -j arg. Defaulting to 1.");
     187            4 :                     return "1";
     188              :                 }
     189            4 :                 if (number_of_threads > MAX_N) {
     190            2 :                     DisplayString(state,
     191            4 :                                   fmt::format("Invalid value for -j arg. Value exceeds num available. Defaulting to num available. -j {}", MAX_N));
     192            2 :                     return std::to_string(MAX_N);
     193              :                 }
     194            2 :                 return input;
     195              :             });
     196              : 
     197           55 :         state.files.inputWeatherFilePath.filePath = "in.epw";
     198              :         // Note: we can't do check(CLI::ExistingFile) here since passing a non-existing /path/to/myfile.epw file
     199              :         // when there exists a /path/to/myfile.stat would mean the Weather File Statistics are still parsed and reported to the tabular output
     200              :         // We still report a message + fail later if DDOnlySimulation is false
     201              :         auto *weatherPathOpt =
     202          165 :             app.add_option("-w,--weather", state.files.inputWeatherFilePath.filePath, "Weather file path (default: in.epw in current directory)")
     203              :                 ->required(false)
     204          165 :                 ->option_text("EPW");
     205              : 
     206           55 :         bool runExpandObjects = false;
     207          165 :         app.add_flag("-x,--expandobjects", runExpandObjects, "Run ExpandObjects prior to simulation");
     208              : 
     209              :         // Positional
     210           55 :         state.dataStrGlobals->inputFilePath = "in.idf";
     211          165 :         app.add_option("input_file", state.dataStrGlobals->inputFilePath, "Input file (default: in.idf in current directory)")
     212              :             ->required(false)
     213          165 :             ->check(CLI::ExistingFile);
     214              : 
     215              :         // Catching it myself, so I can print the arguments vector before it's mutated
     216          318 :         bool debugCLI = std::any_of(args.begin(), args.end(), [](const auto &arg) { return arg == "--debug-cli"; });
     217           55 :         if (debugCLI) {
     218              :             {
     219            0 :                 fmt::print("ProcessArgs: received args\n");
     220            0 :                 int na = 0;
     221            0 :                 for (const auto &a : args) {
     222            0 :                     fmt::print("* {}: '{}'\n", na++, a);
     223              :                 }
     224              :             }
     225              :             {
     226            0 :                 fmt::print("\nAfter massaging/expanding of args\n");
     227            0 :                 int na = 0;
     228            0 :                 for (const auto &a : arguments) {
     229            0 :                     fmt::print("* {}: '{}'\n", na++, a);
     230              :                 }
     231              :             }
     232            0 :             fmt::print("\n");
     233              :         }
     234              :         // bool debugCLI = false;
     235          330 :         app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it
     236              : 
     237              : #if LINK_WITH_PYTHON
     238              : #    ifdef PYTHON_CLI
     239              :         auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools");
     240              :         auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args?
     241              : 
     242              :         std::vector<std::string> python_fwd_args;
     243              :         auto *epLaunchSubCommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EnergyPlus Launch");
     244              :         epLaunchSubCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to EnergyPlus Launch")->option_text("ARG ...");
     245              :         epLaunchSubCommand->positionals_at_end(true);
     246              :         epLaunchSubCommand->footer("You can pass extra arguments after the eplaunch keyword, they will be forwarded to EnergyPlus Launch.");
     247              : 
     248              :         epLaunchSubCommand->callback([&state, &python_fwd_args] {
     249              :             EnergyPlus::Python::PythonEngine engine(state);
     250              :             // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever
     251              :             std::string cmd = Python::PythonEngine::getTclPreppedPreamble(python_fwd_args);
     252              :             cmd += R"python(
     253              : from eplaunch.tk_runner import main_gui
     254              : main_gui(True)
     255              : )python";
     256              :             engine.exec(cmd);
     257              :             exit(0);
     258              :         });
     259              : 
     260              :         auto *updaterSubCommand = auxiliaryToolsSubcommand->add_subcommand("updater", "IDF Version Updater");
     261              :         updaterSubCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to IDF Version Updater")->option_text("ARG ...");
     262              :         updaterSubCommand->positionals_at_end(true);
     263              :         updaterSubCommand->footer("You can pass extra arguments after the updater keyword, they will be forwarded to IDF Version Updater.");
     264              : 
     265              :         updaterSubCommand->callback([&state, &python_fwd_args] {
     266              :             EnergyPlus::Python::PythonEngine engine(state);
     267              :             // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever
     268              :             std::string cmd = Python::PythonEngine::getTclPreppedPreamble(python_fwd_args);
     269              :             cmd += R"python(
     270              : from energyplus_transition.runner import main_gui
     271              : main_gui(True)
     272              : )python";
     273              :             engine.exec(cmd);
     274              :             exit(0);
     275              :         });
     276              : #    endif
     277              : #endif
     278              : 
     279           55 :         app.footer("Example: energyplus -w weather.epw -r input.idf");
     280              : 
     281           55 :         const bool eplusRunningViaAPI = state.dataGlobal->eplusRunningViaAPI;
     282              : 
     283              :         try {
     284              :             // app.parse(argc, argv);
     285              :             // CLI11 when passing argc, argv creates a vector<string> but **in reverse** order:
     286              :             // https://github.com/CLIUtils/CLI11/blob/291c58789c031208f08f4f261a858b5b7083e8e2/include/CLI/impl/App_inl.hpp#L476-L488
     287           55 :             std::reverse(arguments.begin(), arguments.end());
     288           55 :             app.parse(arguments);
     289            6 :         } catch (const CLI::Success &e) {
     290            3 :             int const return_code = app.exit(e);
     291            3 :             if (eplusRunningViaAPI) {
     292            3 :                 return static_cast<int>(ReturnCodes::SuccessButHelper);
     293              :             } else {
     294            0 :                 exit(return_code);
     295              :             }
     296            6 :         } catch (const CLI::ParseError &e) {
     297            3 :             int const return_code = app.exit(e);
     298            3 :             if (eplusRunningViaAPI) {
     299            3 :                 return static_cast<int>(ReturnCodes::Failure);
     300              :             } else {
     301            0 :                 exit(return_code);
     302              :             }
     303            3 :         }
     304              : 
     305           49 :         if (debugCLI) {
     306            0 :             fmt::print(stderr,
     307              :                        R"debug(
     308              : state.dataGlobal->AnnualSimulation = {},
     309              : state.dataGlobal->DDOnlySimulation = {},
     310              : state.dataStrGlobals->outDirPath = '{:g}',
     311              : state.dataStrGlobals->inputIddFilePath= '{:g}',
     312              : 
     313              : runEPMacro = {},
     314              : prefixOutName = {},
     315              : 
     316              : state.dataGlobal->runReadVars={},
     317              : state.dataGlobal->outputEpJSONConversion={},
     318              : state.dataGlobal->outputEpJSONConversionOnly={},
     319              : 
     320              : suffixType={},
     321              : 
     322              : state.dataGlobal->numThread={},
     323              : state.files.inputWeatherFilePath.filePath='{:g}',
     324              : state.dataStrGlobals->inputFilePath='{:g}',
     325              : )debug",
     326            0 :                        state.dataGlobal->AnnualSimulation,
     327            0 :                        state.dataGlobal->DDOnlySimulation,
     328            0 :                        state.dataStrGlobals->outDirPath,
     329            0 :                        state.dataStrGlobals->inputIddFilePath,
     330              : 
     331              :                        runEPMacro,
     332              :                        prefixOutName,
     333            0 :                        state.dataGlobal->runReadVars,
     334            0 :                        state.dataGlobal->outputEpJSONConversion,
     335            0 :                        state.dataGlobal->outputEpJSONConversionOnly,
     336              :                        suffixType,
     337            0 :                        state.dataGlobal->numThread,
     338            0 :                        state.files.inputWeatherFilePath.filePath,
     339            0 :                        state.dataStrGlobals->inputFilePath);
     340              : 
     341            0 :             fmt::print(stderr, "--debug-cli passed: exiting early\n");
     342              : 
     343            0 :             exit(0);
     344              :         }
     345              : 
     346              :         // Convert all paths to native paths
     347           49 :         state.dataStrGlobals->inputFilePath = FileSystem::makeNativePath(state.dataStrGlobals->inputFilePath);
     348           49 :         state.files.inputWeatherFilePath.filePath = FileSystem::makeNativePath(state.files.inputWeatherFilePath.filePath);
     349           49 :         state.dataStrGlobals->inputIddFilePath = FileSystem::makeNativePath(state.dataStrGlobals->inputIddFilePath);
     350           49 :         state.dataStrGlobals->outDirPath = FileSystem::makeNativePath(state.dataStrGlobals->outDirPath);
     351              : 
     352           49 :         state.dataStrGlobals->inputFilePathNameOnly = FileSystem::getFileName(state.dataStrGlobals->inputFilePath);
     353           49 :         state.dataStrGlobals->inputDirPath = FileSystem::getParentDirectoryPath(state.dataStrGlobals->inputFilePath);
     354              : 
     355              :         {
     356           49 :             FileSystem::FileTypes const fileType = FileSystem::getFileType(state.dataStrGlobals->inputFilePath);
     357           49 :             state.dataGlobal->isEpJSON = FileSystem::is_all_json_type(fileType);
     358           49 :             switch (fileType) {
     359           49 :             case FileSystem::FileTypes::IDF:
     360              :             case FileSystem::FileTypes::IMF:
     361              :             case FileSystem::FileTypes::EpJSON:
     362              :             case FileSystem::FileTypes::JSON:
     363           49 :                 break;
     364            0 :             case FileSystem::FileTypes::CBOR:
     365            0 :                 DisplayString(state, "CBOR input format is experimental and unsupported.");
     366            0 :                 break;
     367            0 :             case FileSystem::FileTypes::MsgPack:
     368            0 :                 DisplayString(state, "MsgPack input format is experimental and unsupported.");
     369            0 :                 break;
     370            0 :             case FileSystem::FileTypes::UBJSON:
     371            0 :                 DisplayString(state, "UBJSON input format is experimental and unsupported.");
     372            0 :                 break;
     373            0 :             case FileSystem::FileTypes::BSON:
     374            0 :                 DisplayString(state, "BSON input format is experimental and unsupported.");
     375            0 :                 break;
     376            0 :             default:
     377            0 :                 DisplayString(state,
     378            0 :                               fmt::format("ERROR: Input file must have IDF, IMF, or epJSON extension: {:g}", state.dataStrGlobals->inputFilePath));
     379            0 :                 if (eplusRunningViaAPI) {
     380            0 :                     return static_cast<int>(ReturnCodes::Failure);
     381              :                 } else {
     382            0 :                     exit(EXIT_FAILURE);
     383              :                 }
     384              :             }
     385              :         }
     386              : 
     387           49 :         if (!state.dataStrGlobals->outDirPath.empty()) {
     388              :             // Create directory if it doesn't already exist
     389           25 :             FileSystem::makeDirectory(state.dataStrGlobals->outDirPath);
     390              :         }
     391              : 
     392              :         // File naming scheme
     393           98 :         fs::path outputFilePrefixFullPath = state.dataStrGlobals->outDirPath / prefixOutName;
     394              : 
     395           98 :         fs::path outputEpmdetFilePath;
     396           98 :         fs::path outputEpmidfFilePath;
     397              : 
     398           98 :         fs::path outputExpidfFilePath;
     399           98 :         fs::path outputExperrFilePath;
     400              : 
     401           98 :         std::string normalSuffix;
     402           98 :         std::string tableSuffix;
     403           98 :         std::string mapSuffix;
     404           98 :         std::string zszSuffix;
     405           98 :         std::string spszSuffix;
     406           98 :         std::string sszSuffix;
     407           98 :         std::string meterSuffix;
     408           98 :         std::string sqliteSuffix;
     409           98 :         std::string adsSuffix;
     410           98 :         std::string screenSuffix;
     411           98 :         std::string shdSuffix;
     412              : 
     413           98 :         std::string const errorFollowUp = "Type 'energyplus --help' for usage.";
     414              :         {
     415           49 :             std::transform(suffixType.begin(), suffixType.end(), suffixType.begin(), ::toupper);
     416              : 
     417           49 :             if (suffixType == "L") {
     418              : 
     419           47 :                 normalSuffix = "out";
     420           47 :                 tableSuffix = "tbl";
     421           47 :                 mapSuffix = "map";
     422           47 :                 zszSuffix = "zsz";
     423           47 :                 spszSuffix = "spsz";
     424           47 :                 sszSuffix = "ssz";
     425           47 :                 meterSuffix = "mtr";
     426           47 :                 sqliteSuffix = "sqlite";
     427           47 :                 adsSuffix = "ADS";
     428           47 :                 screenSuffix = "screen";
     429           47 :                 shdSuffix = "shading";
     430              : 
     431            2 :             } else if (suffixType == "D") {
     432              : 
     433            1 :                 normalSuffix = "";
     434            1 :                 tableSuffix = "-table";
     435            1 :                 mapSuffix = "-map";
     436            1 :                 zszSuffix = "-zsz";
     437            1 :                 spszSuffix = "-spsz";
     438            1 :                 sszSuffix = "-ssz";
     439            1 :                 meterSuffix = "-meter";
     440            1 :                 sqliteSuffix = "-sqlite";
     441            1 :                 adsSuffix = "-ads";
     442            1 :                 screenSuffix = "-screen";
     443            1 :                 shdSuffix = "-shading";
     444              : 
     445            1 :             } else if (suffixType == "C") {
     446              : 
     447            1 :                 normalSuffix = "";
     448            1 :                 tableSuffix = "Table";
     449            1 :                 mapSuffix = "Map";
     450            1 :                 zszSuffix = "Zsz";
     451            1 :                 spszSuffix = "Spsz";
     452            1 :                 sszSuffix = "Ssz";
     453            1 :                 meterSuffix = "Meter";
     454            1 :                 sqliteSuffix = "Sqlite";
     455            1 :                 adsSuffix = "Ads";
     456            1 :                 screenSuffix = "Screen";
     457            1 :                 shdSuffix = "Shading";
     458              :             }
     459              :             // No else needed, this is validated at CLI level above
     460              :         }
     461              : 
     462              :         // Helper to construct output file path
     463         3726 :         auto composePath = [&outputFilePrefixFullPath](const std::string &suffix) -> fs::path {
     464         3726 :             return FileSystem::appendSuffixToPath(outputFilePrefixFullPath, suffix);
     465           49 :         };
     466              : 
     467              :         // EnergyPlus files
     468           49 :         state.files.audit.filePath = composePath(normalSuffix + ".audit");
     469           49 :         state.files.bnd.filePath = composePath(normalSuffix + ".bnd");
     470           49 :         state.files.dxf.filePath = composePath(normalSuffix + ".dxf");
     471           49 :         state.files.eio.filePath = composePath(normalSuffix + ".eio");
     472           49 :         state.files.endFile.filePath = composePath(normalSuffix + ".end");
     473           49 :         state.files.outputErrFilePath = composePath(normalSuffix + ".err");
     474           49 :         state.files.eso.filePath = composePath(normalSuffix + ".eso");
     475              : 
     476           49 :         state.files.json.outputJsonFilePath = composePath(normalSuffix + ".json");
     477           49 :         state.files.json.outputTSZoneJsonFilePath = composePath(normalSuffix + "_detailed_zone.json");
     478           49 :         state.files.json.outputTSHvacJsonFilePath = composePath(normalSuffix + "_detailed_HVAC.json");
     479           49 :         state.files.json.outputTSJsonFilePath = composePath(normalSuffix + "_timestep.json");
     480           49 :         state.files.json.outputYRJsonFilePath = composePath(normalSuffix + "_yearly.json");
     481           49 :         state.files.json.outputMNJsonFilePath = composePath(normalSuffix + "_monthly.json");
     482           49 :         state.files.json.outputDYJsonFilePath = composePath(normalSuffix + "_daily.json");
     483           49 :         state.files.json.outputHRJsonFilePath = composePath(normalSuffix + "_hourly.json");
     484           49 :         state.files.json.outputSMJsonFilePath = composePath(normalSuffix + "_runperiod.json");
     485           49 :         state.files.json.outputCborFilePath = composePath(normalSuffix + ".cbor");
     486           49 :         state.files.json.outputTSZoneCborFilePath = composePath(normalSuffix + "_detailed_zone.cbor");
     487           49 :         state.files.json.outputTSHvacCborFilePath = composePath(normalSuffix + "_detailed_HVAC.cbor");
     488           49 :         state.files.json.outputTSCborFilePath = composePath(normalSuffix + "_timestep.cbor");
     489           49 :         state.files.json.outputYRCborFilePath = composePath(normalSuffix + "_yearly.cbor");
     490           49 :         state.files.json.outputMNCborFilePath = composePath(normalSuffix + "_monthly.cbor");
     491           49 :         state.files.json.outputDYCborFilePath = composePath(normalSuffix + "_daily.cbor");
     492           49 :         state.files.json.outputHRCborFilePath = composePath(normalSuffix + "_hourly.cbor");
     493           49 :         state.files.json.outputSMCborFilePath = composePath(normalSuffix + "_runperiod.cbor");
     494           49 :         state.files.json.outputMsgPackFilePath = composePath(normalSuffix + ".msgpack");
     495           49 :         state.files.json.outputTSZoneMsgPackFilePath = composePath(normalSuffix + "_detailed_zone.msgpack");
     496           49 :         state.files.json.outputTSHvacMsgPackFilePath = composePath(normalSuffix + "_detailed_HVAC.msgpack");
     497           49 :         state.files.json.outputTSMsgPackFilePath = composePath(normalSuffix + "_timestep.msgpack");
     498           49 :         state.files.json.outputYRMsgPackFilePath = composePath(normalSuffix + "_yearly.msgpack");
     499           49 :         state.files.json.outputMNMsgPackFilePath = composePath(normalSuffix + "_monthly.msgpack");
     500           49 :         state.files.json.outputDYMsgPackFilePath = composePath(normalSuffix + "_daily.msgpack");
     501           49 :         state.files.json.outputHRMsgPackFilePath = composePath(normalSuffix + "_hourly.msgpack");
     502           49 :         state.files.json.outputSMMsgPackFilePath = composePath(normalSuffix + "_runperiod.msgpack");
     503              : 
     504           49 :         state.files.mtd.filePath = composePath(normalSuffix + ".mtd");
     505           49 :         state.files.mdd.filePath = composePath(normalSuffix + ".mdd");
     506           49 :         state.files.mtr.filePath = composePath(normalSuffix + ".mtr");
     507           49 :         state.files.rdd.filePath = composePath(normalSuffix + ".rdd");
     508           49 :         state.dataStrGlobals->outputShdFilePath = composePath(normalSuffix + ".shd");
     509           49 :         state.files.dfs.filePath = composePath(normalSuffix + ".dfs");
     510           49 :         state.dataStrGlobals->outputGLHEFilePath = composePath(normalSuffix + ".glhe");
     511           49 :         state.files.edd.filePath = composePath(normalSuffix + ".edd");
     512           49 :         state.dataStrGlobals->outputIperrFilePath = composePath(normalSuffix + ".iperr");
     513           49 :         state.files.sln.filePath = composePath(normalSuffix + ".sln");
     514           49 :         state.files.sci.filePath = composePath(normalSuffix + ".sci");
     515           49 :         state.files.wrl.filePath = composePath(normalSuffix + ".wrl");
     516           49 :         state.dataStrGlobals->outputSqlFilePath = composePath(normalSuffix + ".sql");
     517           49 :         state.files.debug.filePath = composePath(normalSuffix + ".dbg");
     518           49 :         state.dataStrGlobals->outputPerfLogFilePath = composePath(normalSuffix + "_perflog.csv");
     519           49 :         state.dataStrGlobals->outputTblCsvFilePath = composePath(tableSuffix + ".csv");
     520           49 :         state.dataStrGlobals->outputTblHtmFilePath = composePath(tableSuffix + ".htm");
     521           49 :         state.dataStrGlobals->outputTblTabFilePath = composePath(tableSuffix + ".tab");
     522           49 :         state.dataStrGlobals->outputTblTxtFilePath = composePath(tableSuffix + ".txt");
     523           49 :         state.dataStrGlobals->outputTblXmlFilePath = composePath(tableSuffix + ".xml");
     524           49 :         state.files.outputMapTabFilePath = composePath(mapSuffix + ".tab");
     525           49 :         state.files.outputMapCsvFilePath = composePath(mapSuffix + ".csv");
     526           49 :         state.files.outputMapTxtFilePath = composePath(mapSuffix + ".txt");
     527           49 :         state.files.outputZszCsvFilePath = composePath(zszSuffix + ".csv");
     528           49 :         state.files.outputZszTabFilePath = composePath(zszSuffix + ".tab");
     529           49 :         state.files.outputZszTxtFilePath = composePath(zszSuffix + ".txt");
     530           49 :         state.files.outputSpszCsvFilePath = composePath(spszSuffix + ".csv");
     531           49 :         state.files.outputSpszTabFilePath = composePath(spszSuffix + ".tab");
     532           49 :         state.files.outputSpszTxtFilePath = composePath(spszSuffix + ".txt");
     533           49 :         state.files.outputSszCsvFilePath = composePath(sszSuffix + ".csv");
     534           49 :         state.files.outputSszTabFilePath = composePath(sszSuffix + ".tab");
     535           49 :         state.files.outputSszTxtFilePath = composePath(sszSuffix + ".txt");
     536           49 :         state.dataStrGlobals->outputAdsFilePath = composePath(adsSuffix + ".out");
     537           49 :         state.files.shade.filePath = composePath(shdSuffix + ".csv");
     538           49 :         if (suffixType == "L") {
     539              :             // out/sqlite.err
     540           47 :             state.dataStrGlobals->outputSqliteErrFilePath = state.dataStrGlobals->outDirPath / fs::path{sqliteSuffix + ".err"};
     541              :         } else {
     542              :             // if 'D':  out/eplus-sqlite.err
     543            2 :             state.dataStrGlobals->outputSqliteErrFilePath = composePath(sqliteSuffix + ".err");
     544              :         }
     545              : 
     546           49 :         state.files.screenCsv.filePath = composePath(screenSuffix + ".csv");
     547              :         // TODO, why are these relative paths?
     548           49 :         state.files.delightIn.filePath = "eplusout.delightin";
     549           49 :         state.dataStrGlobals->outputDelightOutFilePath = "eplusout.delightout";
     550              : 
     551              :         // TODO: why is this relative?
     552           49 :         state.files.iniFile.filePath = "Energy+.ini";
     553              : 
     554              :         // Stat next to epw
     555              :         // Careful! fs::path::replace_extension will **mutate the original object**
     556           49 :         state.files.inStatFilePath.filePath = state.files.inputWeatherFilePath.filePath;
     557           49 :         state.files.inStatFilePath.filePath.replace_extension(".stat");
     558              :         // Or is it better to provide a helper that does not mutate like this?
     559              :         // state.files.inStatFilePath.filePath = FileSystem::replaceFileExtension(state.inputWeatherFilePath.filePath, ".stat");
     560              : 
     561           49 :         state.dataStrGlobals->eplusADSFilePath = state.dataStrGlobals->inputDirPath / "eplusADS.inp";
     562              : 
     563              :         // Readvars files
     564           49 :         state.files.csv.filePath = composePath(normalSuffix + ".csv");
     565           49 :         state.files.mtr_csv.filePath = composePath(meterSuffix + ".csv");
     566           49 :         state.dataStrGlobals->outputRvauditFilePath = composePath(normalSuffix + ".rvaudit");
     567              : 
     568              :         // EPMacro files
     569           49 :         outputEpmdetFilePath = composePath(normalSuffix + ".epmdet");
     570           49 :         outputEpmidfFilePath = composePath(normalSuffix + ".epmidf");
     571              : 
     572              :         // ExpandObjects files
     573           49 :         outputExpidfFilePath = composePath(normalSuffix + ".expidf");
     574           49 :         outputExperrFilePath = composePath(normalSuffix + ".experr");
     575              : 
     576              :         // Read path from INI file if it exists
     577              : 
     578              :         // Check for IDD and IDF files
     579           49 :         if (FileSystem::fileExists(state.files.iniFile.filePath)) {
     580            0 :             EnergyPlus::InputFile iniFile = state.files.iniFile.try_open();
     581            0 :             if (!iniFile.good()) {
     582            0 :                 DisplayString(state, fmt::format("ERROR: Could not open file {} for input (read).", iniFile.filePath));
     583            0 :                 if (eplusRunningViaAPI) {
     584            0 :                     return static_cast<int>(ReturnCodes::Failure);
     585              :                 } else {
     586            0 :                     exit(EXIT_FAILURE);
     587              :                 }
     588              :             }
     589            0 :             state.dataStrGlobals->CurrentWorkingFolder = iniFile.filePath;
     590              :             // Relying on compiler to supply full path name here
     591              :             // TODO: not sure I understand this block
     592              :             // const int TempIndx = index(state.dataStrGlobals->CurrentWorkingFolder, state.dataStrGlobals->pathChar, true);
     593              :             // if (TempIndx == std::string::npos) {
     594              :             // state.dataStrGlobals->CurrentWorkingFolder = "";
     595              :             //} else {
     596              :             // state.dataStrGlobals->CurrentWorkingFolder.erase(TempIndx + 1);
     597              :             //}
     598              :             //       Get directories from ini file
     599            0 :             std::string programPathStr;
     600            0 :             ReadINIFile(iniFile, "program", "dir", programPathStr);
     601            0 :             state.dataStrGlobals->ProgramPath = fs::path(programPathStr);
     602              : 
     603            0 :             state.dataStrGlobals->inputIddFilePath = state.dataStrGlobals->ProgramPath / "Energy+.idd";
     604            0 :         }
     605              : 
     606              :         // Check if specified files exist
     607           49 :         if (!FileSystem::fileExists(state.dataStrGlobals->inputFilePath)) {
     608            0 :             DisplayString(
     609            0 :                 state, fmt::format("ERROR: Could not find input data file: {}.", FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath)));
     610            0 :             DisplayString(state, errorFollowUp);
     611            0 :             if (eplusRunningViaAPI) {
     612            0 :                 return static_cast<int>(ReturnCodes::Failure);
     613              :             } else {
     614            0 :                 exit(EXIT_FAILURE);
     615              :             }
     616              :         }
     617              : 
     618           49 :         if ((weatherPathOpt->count() > 0) && !state.dataGlobal->DDOnlySimulation) {
     619            3 :             if (!FileSystem::fileExists(state.files.inputWeatherFilePath.filePath)) {
     620            2 :                 DisplayString(
     621              :                     state,
     622            4 :                     fmt::format("ERROR: Could not find weather file: {}.", FileSystem::getAbsolutePath(state.files.inputWeatherFilePath.filePath)));
     623            2 :                 DisplayString(state, errorFollowUp);
     624            2 :                 if (eplusRunningViaAPI) {
     625            2 :                     return static_cast<int>(ReturnCodes::Failure);
     626              :                 } else {
     627            0 :                     exit(EXIT_FAILURE);
     628              :                 }
     629              :             }
     630              :         }
     631              : 
     632              :         // TODO: might be able to convert epJSON->IDF, run preprocessors, then go back IDF->epJSON
     633              : 
     634              :         // Preprocessors (These will likely move to a new file)
     635           47 :         if (runEPMacro) {
     636            0 :             fs::path epMacroPath = (state.dataStrGlobals->exeDirectoryPath / "EPMacro").replace_extension(FileSystem::exeExtension);
     637            0 :             if (!FileSystem::fileExists(epMacroPath)) {
     638            0 :                 DisplayString(state, fmt::format("ERROR: Could not find EPMacro executable: {}.", FileSystem::getAbsolutePath(epMacroPath)));
     639            0 :                 if (eplusRunningViaAPI) {
     640            0 :                     return static_cast<int>(ReturnCodes::Failure);
     641              :                 } else {
     642            0 :                     exit(EXIT_FAILURE);
     643              :                 }
     644              :             }
     645            0 :             std::string epMacroCommand = "\"" + FileSystem::toString(epMacroPath) + "\"";
     646            0 :             bool inputFilePathdIn = (FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath) == FileSystem::getAbsolutePath("in.imf"));
     647              : 
     648            0 :             if (!inputFilePathdIn) {
     649            0 :                 FileSystem::linkFile(state.dataStrGlobals->inputFilePath, "in.imf");
     650              :             }
     651            0 :             DisplayString(state, "Running EPMacro...");
     652            0 :             FileSystem::systemCall(epMacroCommand);
     653            0 :             if (!inputFilePathdIn) {
     654            0 :                 FileSystem::removeFile("in.imf");
     655              :             }
     656            0 :             FileSystem::moveFile("audit.out", outputEpmdetFilePath);
     657            0 :             FileSystem::moveFile("out.idf", outputEpmidfFilePath);
     658            0 :             state.dataStrGlobals->inputFilePath = outputEpmidfFilePath;
     659            0 :         }
     660              : 
     661           47 :         if (runExpandObjects) {
     662              :             fs::path expandObjectsPath =
     663            0 :                 (state.dataStrGlobals->exeDirectoryPath / fs::path("ExpandObjects")).replace_extension(FileSystem::exeExtension);
     664            0 :             if (!FileSystem::fileExists(expandObjectsPath)) {
     665            0 :                 DisplayString(state,
     666            0 :                               fmt::format("ERROR: Could not find ExpandObjects executable: {}.", FileSystem::getAbsolutePath(expandObjectsPath)));
     667            0 :                 if (eplusRunningViaAPI) {
     668            0 :                     return static_cast<int>(ReturnCodes::Failure);
     669              :                 } else {
     670            0 :                     exit(EXIT_FAILURE);
     671              :                 }
     672              :             }
     673            0 :             std::string expandObjectsCommand = "\"" + FileSystem::toString(expandObjectsPath) + "\"";
     674            0 :             bool inputFilePathdIn = (FileSystem::getAbsolutePath(state.dataStrGlobals->inputFilePath) == FileSystem::getAbsolutePath("in.idf"));
     675              : 
     676              :             // check if IDD actually exists since ExpandObjects still requires it
     677            0 :             if (!FileSystem::fileExists(state.dataStrGlobals->inputIddFilePath)) {
     678            0 :                 DisplayString(state,
     679            0 :                               fmt::format("ERROR: Could not find input data dictionary: {}.",
     680            0 :                                           FileSystem::getAbsolutePath(state.dataStrGlobals->inputIddFilePath)));
     681            0 :                 DisplayString(state, errorFollowUp);
     682            0 :                 if (eplusRunningViaAPI) {
     683            0 :                     return static_cast<int>(ReturnCodes::Failure);
     684              :                 } else {
     685            0 :                     exit(EXIT_FAILURE);
     686              :                 }
     687              :             }
     688              : 
     689              :             bool iddFilePathdEnergy =
     690            0 :                 (FileSystem::getAbsolutePath(state.dataStrGlobals->inputIddFilePath) == FileSystem::getAbsolutePath("Energy+.idd"));
     691              : 
     692            0 :             if (!inputFilePathdIn) {
     693            0 :                 FileSystem::linkFile(state.dataStrGlobals->inputFilePath, "in.idf");
     694              :             }
     695            0 :             if (!iddFilePathdEnergy) {
     696            0 :                 FileSystem::linkFile(state.dataStrGlobals->inputIddFilePath, "Energy+.idd");
     697              :             }
     698              : 
     699            0 :             FileSystem::systemCall(expandObjectsCommand);
     700            0 :             if (!inputFilePathdIn) {
     701            0 :                 FileSystem::removeFile("in.idf");
     702              :             }
     703            0 :             if (!iddFilePathdEnergy) {
     704            0 :                 FileSystem::removeFile("Energy+.idd");
     705              :             }
     706              : 
     707            0 :             FileSystem::moveFile("expandedidf.err", outputExperrFilePath);
     708            0 :             if (FileSystem::fileExists("expanded.idf")) {
     709            0 :                 FileSystem::moveFile("expanded.idf", outputExpidfFilePath);
     710            0 :                 state.dataStrGlobals->inputFilePath = outputExpidfFilePath;
     711              :             }
     712            0 :         }
     713              : 
     714           47 :         return static_cast<int>(ReturnCodes::Success);
     715           55 :     }
     716              : 
     717              :     // Fix This is Fortranic code that needs to be brought up to C++ style
     718              :     //     All the index and len and strip should be eliminated and replaced by string calls only where needed
     719              :     //     I/o with std::string should not be pulling in trailing blanks so stripping should not be needed, etc.
     720              :     //     Rewinding is a big performance hit and should be avoided if possible
     721              :     //     Case-insensitive comparison is much faster than converting strings to upper or lower case
     722              :     //     Each strip and case conversion is a heap hit and should be avoided if possible
     723            0 :     void ReadINIFile(InputFile &inputFile,               // Unit number of the opened INI file
     724              :                      std::string const &Heading,         // Heading for the parameters ('[heading]')
     725              :                      std::string const &KindofParameter, // Kind of parameter to be found (String)
     726              :                      std::string &DataOut                // Output from the retrieval
     727              :     )
     728              :     {
     729              : 
     730              :         // SUBROUTINE INFORMATION:
     731              :         //       AUTHOR         Linda K. Lawrie
     732              :         //       DATE WRITTEN   September 1997
     733              :         //       MODIFIED       na
     734              :         //       RE-ENGINEERED  na
     735              : 
     736              :         // PURPOSE OF THIS SUBROUTINE:
     737              :         // This routine reads the .ini file and retrieves
     738              :         // the path names for the files from it.
     739              : 
     740              :         // METHODOLOGY EMPLOYED:
     741              :         // Duplicate the kind of reading the Windows "GetINISetting" would
     742              :         // do.
     743              : 
     744              :         // Using/Aliasing
     745              :         using namespace EnergyPlus;
     746              :         using namespace DataStringGlobals;
     747              : 
     748            0 :         std::string Param;
     749              :         std::string::size_type ILB;
     750              :         std::string::size_type IRB;
     751              :         std::string::size_type IEQ;
     752              :         std::string::size_type IPAR;
     753              :         std::string::size_type IPOS;
     754              : 
     755              :         // Formats
     756              : 
     757            0 :         DataOut.clear();
     758              : 
     759              :         // I tried ADJUSTL(TRIM(KindofParameter)) and got an internal compiler error
     760              : 
     761            0 :         Param = KindofParameter;
     762            0 :         strip(Param);
     763            0 :         inputFile.rewind();
     764            0 :         bool Found = false;
     765            0 :         bool NewHeading = false;
     766              : 
     767            0 :         while (inputFile.good() && !Found) {
     768            0 :             EnergyPlus::InputFile::ReadResult const readResult = inputFile.readLine();
     769              : 
     770            0 :             if (readResult.eof) {
     771            0 :                 break;
     772              :             }
     773              : 
     774            0 :             if (readResult.data.empty()) {
     775            0 :                 continue;
     776              :             } // Ignore Blank Lines
     777              : 
     778            0 :             std::string LINEOut;
     779            0 :             ConvertCaseToLower(readResult.data, LINEOut); // Turn line into lower case
     780              :             //        LINE=LINEOut
     781              : 
     782            0 :             if (!has(LINEOut, Heading)) continue;
     783              : 
     784              :             //                                  See if [ and ] are on line
     785            0 :             ILB = index(LINEOut, '[');
     786            0 :             IRB = index(LINEOut, ']');
     787            0 :             if (ILB == std::string::npos && IRB == std::string::npos) continue;
     788            0 :             if (!has(LINEOut, '[' + Heading + ']')) continue; // Must be really correct heading line
     789              : 
     790              :             //                                  Heading line found, now looking for Kind
     791            0 :             while (inputFile.good() && !NewHeading) {
     792            0 :                 const auto innerReadResult = inputFile.readLine(); // readLine returns a ReadResult<std::string>, hence no & (THIS_AUTO_OK)
     793            0 :                 if (innerReadResult.eof) {
     794            0 :                     break;
     795              :                 }
     796              : 
     797            0 :                 std::string line = innerReadResult.data;
     798            0 :                 strip(line);
     799              : 
     800            0 :                 if (line.empty()) continue; // Ignore Blank Lines
     801              : 
     802            0 :                 ConvertCaseToLower(line, LINEOut); // Turn line into lower case
     803              :                 //         LINE=LINEOut
     804              : 
     805            0 :                 ILB = index(LINEOut, '[');
     806            0 :                 IRB = index(LINEOut, ']');
     807            0 :                 NewHeading = (ILB != std::string::npos && IRB != std::string::npos);
     808              : 
     809              :                 //                                  Should be a parameter line
     810              :                 //                                  KindofParameter = string
     811            0 :                 IEQ = index(LINEOut, '=');
     812            0 :                 IPAR = index(LINEOut, Param);
     813            0 :                 if (IEQ == std::string::npos) continue;
     814            0 :                 if (IPAR == std::string::npos) continue;
     815            0 :                 if (IPAR != 0) continue;
     816            0 :                 if (!has(LINEOut, Param + '=')) continue; // needs to be param=
     817              : 
     818              :                 //                                  = found and parameter found.
     819            0 :                 if (IPAR > IEQ) continue;
     820              : 
     821              :                 //                                  parameter = found
     822              :                 //                                  Set output string to start with non-blank character
     823              : 
     824            0 :                 DataOut = stripped(line.substr(IEQ + 1));
     825            0 :                 Found = true;
     826            0 :                 break;
     827            0 :             }
     828            0 :         }
     829              : 
     830            0 :         if (Param == "dir") {
     831            0 :             IPOS = len(DataOut);
     832            0 :             if (IPOS != 0) {
     833              :                 // Non-blank make sure last position is valid path character
     834              :                 //  (Set in DataStringGlobals)
     835              : 
     836            0 :                 if (DataOut[IPOS - 1] != pathChar) {
     837            0 :                     DataOut += pathChar;
     838              :                 }
     839              :             }
     840              :         }
     841            0 :     }
     842              : 
     843            3 :     int runReadVarsESO(EnergyPlusData &state)
     844              :     {
     845            3 :         fs::path readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
     846              : 
     847            3 :         if (!FileSystem::fileExists(readVarsPath)) {
     848            0 :             readVarsPath = (state.dataStrGlobals->exeDirectoryPath / "PostProcess" / "ReadVarsESO").replace_extension(FileSystem::exeExtension);
     849            0 :             if (!FileSystem::fileExists(readVarsPath)) {
     850              :                 // should report the error differently if the user is calling into E+ through EXE or DLL
     851            0 :                 if (state.dataGlobal->eplusRunningViaAPI) {
     852            0 :                     DisplayString(
     853              :                         state,
     854              :                         "ERROR: Could not find ReadVarsESO executable.  When calling through C API, make sure to call setEnergyPlusRootDirectory");
     855              :                 } else {
     856            0 :                     DisplayString(state, fmt::format("ERROR: Could not find ReadVarsESO executable: {}.", FileSystem::getAbsolutePath(readVarsPath)));
     857              :                 }
     858            0 :                 return static_cast<int>(ReturnCodes::Failure);
     859              :             }
     860              :         }
     861              : 
     862            3 :         fs::path const RVIfile = (state.dataStrGlobals->inputDirPath / state.dataStrGlobals->inputFilePathNameOnly).replace_extension(".rvi");
     863            3 :         fs::path const MVIfile = (state.dataStrGlobals->inputDirPath / state.dataStrGlobals->inputFilePathNameOnly).replace_extension(".mvi");
     864              : 
     865            3 :         const bool rviFileExists = FileSystem::fileExists(RVIfile);
     866            3 :         if (!rviFileExists) {
     867            3 :             std::ofstream ofs{RVIfile};
     868            3 :             if (!ofs.good()) {
     869            0 :                 ShowFatalError(state, format("EnergyPlus: Could not open file \"{}\" for output (write).", RVIfile));
     870              :             } else {
     871            3 :                 ofs << FileSystem::toString(state.files.eso.filePath) << '\n';
     872            3 :                 ofs << FileSystem::toString(state.files.csv.filePath) << '\n';
     873              :             }
     874            3 :         }
     875              : 
     876            3 :         const bool mviFileExists = FileSystem::fileExists(MVIfile);
     877            3 :         if (!mviFileExists) {
     878            3 :             std::ofstream ofs{MVIfile};
     879            3 :             if (!ofs.good()) {
     880            0 :                 ShowFatalError(state, format("EnergyPlus: Could not open file \"{}\" for output (write).", RVIfile));
     881              :             } else {
     882            3 :                 ofs << FileSystem::toString(state.files.mtr.filePath) << '\n';
     883            3 :                 ofs << FileSystem::toString(state.files.mtr_csv.filePath) << '\n';
     884              :             }
     885            3 :         }
     886              : 
     887              :         // We quote the paths in case we have spaces
     888              :         // "/Path/to/ReadVarEso" "/Path/to/folder with spaces/file.rvi" unlimited
     889            3 :         std::string const readVarsRviCommand = "\"" + FileSystem::toString(readVarsPath) + "\" \"" + FileSystem::toString(RVIfile) + "\" unlimited";
     890            3 :         std::string const readVarsMviCommand = "\"" + FileSystem::toString(readVarsPath) + "\" \"" + FileSystem::toString(MVIfile) + "\" unlimited";
     891              : 
     892              :         // systemCall will be responsible to handle to above command on Windows versus Unix
     893            3 :         FileSystem::systemCall(readVarsRviCommand);
     894            3 :         FileSystem::systemCall(readVarsMviCommand);
     895              : 
     896            3 :         if (!rviFileExists) {
     897            3 :             FileSystem::removeFile(RVIfile);
     898              :         }
     899              : 
     900            3 :         if (!mviFileExists) {
     901            3 :             FileSystem::removeFile(MVIfile);
     902              :         }
     903              : 
     904            3 :         FileSystem::moveFile("readvars.audit", state.dataStrGlobals->outputRvauditFilePath);
     905            3 :         return static_cast<int>(ReturnCodes::Success);
     906            3 :     }
     907              : 
     908              : } // namespace CommandLineInterface
     909              : } // namespace EnergyPlus
        

Generated by: LCOV version 2.0-1