Repository: NREL/OpenStudio (language: python)

Issue:
OS 3.7 CLI regression for `openstudio foo.rb --help`
Issue overview
--------------
As tested with OpenStudio-HPXML, the `--help` argument is not correctly passed to the ruby script that is being executed.

## Current Behavior

With OpenStudio 3.7, the CLI itself processes the `--help` argument:
```
$ openstudio workflow/run_simulation.rb --help
Executes a ruby file
Usage: openstudio execute_ruby_script [OPTIONS] path

Positionals:
  path TEXT:FILE REQUIRED          Path to ruby file

Options:
  -h,--help                        Print this help message and exit

You can pass extra arguments after the ruby file, they will be forwarded.
```

## Expected Behavior

I'd expect it to work the same way it did in OpenStudio 3.6 where our ruby script receives the `--help` argument.
```
$ openstudio workflow/run_simulation.rb --help
Usage: run_simulation.rb -x building.xml
    -x, --xml <FILE>                 HPXML file
    -o, --output-dir <DIR>           Output directory
        --output-format TYPE         Output file format type (csv, json, msgpack, csv_dview)
        --hourly TYPE                Request hourly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --daily TYPE                 Request daily output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --monthly TYPE               Request monthly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --timestep TYPE              Request timestep output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
    -s, --skip-validation            Skip Schema/Schematron validation for faster performance
        --add-component-loads        Add heating/cooling component loads calculation
        --add-stochastic-schedules   Add detailed stochastic occupancy schedules
    -t NAME,                         Add timeseries output variable; can be called multiple times
        --add-timeseries-output-variable
        --ep-input-format TYPE       EnergyPlus input file format (idf, epjson)
    -b, --building-id ID             ID of Building to simulate (required when multiple HPXML Building elements); use "ALL" to simulate a single whole MF building
    -v, --version                    Reports the version
    -d, --debug                      Generate additional debug output/files
    -h, --help                       Display help
```

## Possible Solution
I would think that any argument after foo.rb should be forwarded to, and processed by, the ruby script.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Ubuntu 20.04
 - OpenStudio 3.7.0

OS 3.7 CLI regression for `openstudio foo.rb --help`
Issue overview
--------------
As tested with OpenStudio-HPXML, the `--help` argument is not correctly passed to the ruby script that is being executed.

## Current Behavior

With OpenStudio 3.7, the CLI itself processes the `--help` argument:
```
$ openstudio workflow/run_simulation.rb --help
Executes a ruby file
Usage: openstudio execute_ruby_script [OPTIONS] path

Positionals:
  path TEXT:FILE REQUIRED          Path to ruby file

Options:
  -h,--help                        Print this help message and exit

You can pass extra arguments after the ruby file, they will be forwarded.
```

## Expected Behavior

I'd expect it to work the same way it did in OpenStudio 3.6 where our ruby script receives the `--help` argument.
```
$ openstudio workflow/run_simulation.rb --help
Usage: run_simulation.rb -x building.xml
    -x, --xml <FILE>                 HPXML file
    -o, --output-dir <DIR>           Output directory
        --output-format TYPE         Output file format type (csv, json, msgpack, csv_dview)
        --hourly TYPE                Request hourly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --daily TYPE                 Request daily output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --monthly TYPE               Request monthly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --timestep TYPE              Request timestep output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
    -s, --skip-validation            Skip Schema/Schematron validation for faster performance
        --add-component-loads        Add heating/cooling component loads calculation
        --add-stochastic-schedules   Add detailed stochastic occupancy schedules
    -t NAME,                         Add timeseries output variable; can be called multiple times
        --add-timeseries-output-variable
        --ep-input-format TYPE       EnergyPlus input file format (idf, epjson)
    -b, --building-id ID             ID of Building to simulate (required when multiple HPXML Building elements); use "ALL" to simulate a single whole MF building
    -v, --version                    Reports the version
    -d, --debug                      Generate additional debug output/files
    -h, --help                       Display help
```

## Possible Solution
I would think that any argument after foo.rb should be forwarded to, and processed by, the ruby script.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Ubuntu 20.04
 - OpenStudio 3.7.0

OS 3.7 CLI regression for `openstudio foo.rb --help`
Issue overview
--------------
As tested with OpenStudio-HPXML, the `--help` argument is not correctly passed to the ruby script that is being executed.

## Current Behavior

With OpenStudio 3.7, the CLI itself processes the `--help` argument:
```
$ openstudio workflow/run_simulation.rb --help
Executes a ruby file
Usage: openstudio execute_ruby_script [OPTIONS] path

Positionals:
  path TEXT:FILE REQUIRED          Path to ruby file

Options:
  -h,--help                        Print this help message and exit

You can pass extra arguments after the ruby file, they will be forwarded.
```

## Expected Behavior

I'd expect it to work the same way it did in OpenStudio 3.6 where our ruby script receives the `--help` argument.
```
$ openstudio workflow/run_simulation.rb --help
Usage: run_simulation.rb -x building.xml
    -x, --xml <FILE>                 HPXML file
    -o, --output-dir <DIR>           Output directory
        --output-format TYPE         Output file format type (csv, json, msgpack, csv_dview)
        --hourly TYPE                Request hourly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --daily TYPE                 Request daily output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --monthly TYPE               Request monthly output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
        --timestep TYPE              Request timestep output type (ALL, total, fuels, enduses, systemuses, emissions, emissionfuels, emissionenduses, hotwater, loads, componentloads, unmethours, temperatures, airflows, weather, resilience); can be called multiple times
    -s, --skip-validation            Skip Schema/Schematron validation for faster performance
        --add-component-loads        Add heating/cooling component loads calculation
        --add-stochastic-schedules   Add detailed stochastic occupancy schedules
    -t NAME,                         Add timeseries output variable; can be called multiple times
        --add-timeseries-output-variable
        --ep-input-format TYPE       EnergyPlus input file format (idf, epjson)
    -b, --building-id ID             ID of Building to simulate (required when multiple HPXML Building elements); use "ALL" to simulate a single whole MF building
    -v, --version                    Reports the version
    -d, --debug                      Generate additional debug output/files
    -h, --help                       Display help
```

## Possible Solution
I would think that any argument after foo.rb should be forwarded to, and processed by, the ruby script.

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Ubuntu 20.04
 - OpenStudio 3.7.0



Discussion / hints:
After discussing with @jmarrec, he's going to see if we can allow `openstudio execute_ruby_script --help` to continue working like it does, but forward the --help argument when a ruby script is included in the command (e.g. `openstudio execute_ruby_script foo.rb --help`). He says it might require disabling CLI11's default --help and handling it conditionally, but should be doable.
After discussing with @jmarrec, he's going to see if we can allow `openstudio execute_ruby_script --help` to continue working like it does, but forward the --help argument when a ruby script is included in the command (e.g. `openstudio execute_ruby_script foo.rb --help`). He says it might require disabling CLI11's default --help and handling it conditionally, but should be doable.
After discussing with @jmarrec, he's going to see if we can allow `openstudio execute_ruby_script --help` to continue working like it does, but forward the --help argument when a ruby script is included in the command (e.g. `openstudio execute_ruby_script foo.rb --help`). He says it might require disabling CLI11's default --help and handling it conditionally, but should be doable.

Fix patch:
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index 9843551852e..6d61fabdfb9 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -297,10 +297,17 @@ if(BUILD_TESTING)
 
   # Test via optparse and check file exists
   # Historical CLI versions
-    add_test(NAME OpenStudioCLI.Classic.execute_ruby_script.forward_flags.optparse.path_forwardslash
+  add_test(NAME OpenStudioCLI.Classic.execute_ruby_script.forward_flags.optparse.path_forwardslash
     COMMAND $<TARGET_FILE:openstudio> classic execute_ruby_script execute_ruby_script_optparse_path.rb -x "./test_folder/hello.xml"
     WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
   )
+  add_test(NAME OpenStudioCLI.Classic.execute_ruby_script.forward_flags.forward_help
+    COMMAND $<TARGET_FILE:openstudio> classic execute_ruby_script ${CMAKE_CURRENT_SOURCE_DIR}/test/execute_ruby_script_optparse_path.rb --help
+  )
+  set_tests_properties(OpenStudioCLI.Classic.execute_ruby_script.forward_flags.forward_help PROPERTIES
+    PASS_REGULAR_EXPRESSION "The Ruby help description."
+  )
+
   if (WIN32)
     # Posix paths don't understand a backward slash anyways
     add_test(NAME OpenStudioCLI.Classic.execute_ruby_script.forward_flags.optparse.path_backwardslash
@@ -313,6 +320,21 @@ if(BUILD_TESTING)
     COMMAND $<TARGET_FILE:openstudio> execute_ruby_script execute_ruby_script_optparse_path.rb -x "./test_folder/hello.xml"
     WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
   )
+
+  add_test(NAME OpenStudioCLI.execute_ruby_script.forward_flags.forward_help
+    COMMAND $<TARGET_FILE:openstudio> execute_ruby_script ${CMAKE_CURRENT_SOURCE_DIR}/test/execute_ruby_script_optparse_path.rb --help
+  )
+  set_tests_properties(OpenStudioCLI.execute_ruby_script.forward_flags.forward_help PROPERTIES
+    PASS_REGULAR_EXPRESSION "The Ruby help description."
+  )
+
+  add_test(NAME OpenStudioCLI.execute_python_script.forward_flags.forward_help
+    COMMAND $<TARGET_FILE:openstudio> execute_python_script ${CMAKE_CURRENT_SOURCE_DIR}/test/execute_python_script_argparse_path.py --help
+  )
+  set_tests_properties(OpenStudioCLI.execute_python_script.forward_flags.forward_help PROPERTIES
+    PASS_REGULAR_EXPRESSION "The Python help description."
+  )
+
   if (WIN32)
     add_test(NAME OpenStudioCLI.execute_ruby_script.forward_flags.optparse.path_backwardslash
       COMMAND $<TARGET_FILE:openstudio> execute_ruby_script execute_ruby_script_optparse_path.rb -x ".\\test_folder\\hello.xml"
diff --git a/src/cli/UpdateCommand.cpp b/src/cli/UpdateCommand.cpp
index ba65fb8be6f..d63c9d2334a 100644
--- a/src/cli/UpdateCommand.cpp
+++ b/src/cli/UpdateCommand.cpp
@@ -69,6 +69,8 @@ namespace cli {
     cmd += fmt::format(R"(
 begin
   require '{}'
+rescue SystemExit
+  # puts "help was called"
 rescue Exception => e
   puts
   puts "Error: #{{e.message}}"
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index 606030436ef..a4453fc6301 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -244,30 +244,32 @@ int main(int argc, char* argv[]) {
     // {
     auto* execute_ruby_scriptCommand = app.add_subcommand("execute_ruby_script", "Executes a ruby file");
     openstudio::filesystem::path rubyScriptPath;
-    execute_ruby_scriptCommand->add_option("path", rubyScriptPath, "Path to ruby file")->required(true)->check(CLI::ExistingFile);
-    // We can't do this because that means we can't pass extra **flags**
-    // std::vector<std::string> executeRubyScriptCommandArgs;
-    // execute_ruby_scriptCommand->add_option("arguments", executeRubyScriptCommandArgs, "Arguments to pass to the ruby file")
-    //   ->required(false)
-    //   ->option_text("args");
-    execute_ruby_scriptCommand->allow_extras(true);
-    execute_ruby_scriptCommand->footer("You can pass extra arguments after the ruby file, they will be forwarded.");
-
-    execute_ruby_scriptCommand->callback([&rubyScriptPath, &rubyEngine, &execute_ruby_scriptCommand] {
-      openstudio::cli::executeRubyScriptCommand(rubyScriptPath, rubyEngine, execute_ruby_scriptCommand->remaining());
-    });
+    std::vector<std::string> ruby_fwd_args;
+    execute_ruby_scriptCommand->add_option("path", rubyScriptPath, "Path to Ruby file")
+      ->option_text("RUBY_SCRIPT")
+      ->required(true)
+      ->check(CLI::ExistingFile);
+    execute_ruby_scriptCommand->add_option("args", ruby_fwd_args, "Extra Arguments forwarded to the Ruby script")->option_text("ARG ...");
+    execute_ruby_scriptCommand->positionals_at_end(true);
+    execute_ruby_scriptCommand->footer("Any additional arguments passed after the Ruby file are forwarded");
+    execute_ruby_scriptCommand->callback(
+      [&rubyScriptPath, &rubyEngine, &ruby_fwd_args] { openstudio::cli::executeRubyScriptCommand(rubyScriptPath, rubyEngine, ruby_fwd_args); });
     // }
 
     // {
     auto* execute_python_scriptCommand = app.add_subcommand("execute_python_script", "Executes a python file");
     openstudio::filesystem::path pythonScriptPath;
-    execute_python_scriptCommand->add_option("path", pythonScriptPath, "Path to python file")->required(true)->check(CLI::ExistingFile);
-
-    execute_python_scriptCommand->allow_extras(true);
-    execute_python_scriptCommand->footer("You can pass extra arguments after the python file, they will be forwarded.");
-
-    execute_python_scriptCommand->callback([&pythonScriptPath, &pythonEngine, &execute_python_scriptCommand] {
-      openstudio::cli::executePythonScriptCommand(pythonScriptPath, pythonEngine, execute_python_scriptCommand->remaining());
+    std::vector<std::string> python_fwd_args;
+    execute_python_scriptCommand->add_option("path", pythonScriptPath, "Path to Python file")
+      ->option_text("PYTHON_SCRIPT")
+      ->required(true)
+      ->check(CLI::ExistingFile);
+    execute_python_scriptCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG ...");
+    execute_python_scriptCommand->positionals_at_end(true);
+    execute_python_scriptCommand->footer("You can pass extra arguments after the Python file, they will be forwarded.");
+
+    execute_python_scriptCommand->callback([&pythonScriptPath, &pythonEngine, &python_fwd_args] {
+      openstudio::cli::executePythonScriptCommand(pythonScriptPath, pythonEngine, python_fwd_args);
     });
     // }
 


Test patch:
diff --git a/src/cli/test/execute_python_script_argparse_path.py b/src/cli/test/execute_python_script_argparse_path.py
new file mode 100644
index 00000000000..8827b3444ee
--- /dev/null
+++ b/src/cli/test/execute_python_script_argparse_path.py
@@ -0,0 +1,21 @@
+import argparse
+from pathlib import Path
+
+
+def validate_file(arg):
+    if (filepath := Path(arg)).is_file():
+        return filepath
+    else:
+        raise FileNotFoundError(arg)
+
+def validate_xml_file(arg):
+    filepath = validate_file(arg)
+    if (filepath.suffix != '.xml'):
+        raise FileNotFoundError(f"{arg} is not a .xml file")
+    return filepath
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="The Python help description.")
+    parser.add_argument("-x,--xml", metavar='FILE', required=True, type=validate_xml_file, help="HPXML file")
+    args = parser.parse_args()
+    print(args)
diff --git a/src/cli/test/execute_ruby_script_optparse_path.rb b/src/cli/test/execute_ruby_script_optparse_path.rb
index 62484dd24c3..1585e6f8a60 100644
--- a/src/cli/test/execute_ruby_script_optparse_path.rb
+++ b/src/cli/test/execute_ruby_script_optparse_path.rb
@@ -4,6 +4,7 @@
 options = {}
 OptionParser.new do |opts|
   opts.banner = "Usage: #{File.basename(__FILE__)} -x building.xml"
+  opts.banner += "\n\nThe Ruby help description.\n\n"
 
   opts.on('-x', '--xml <FILE>', 'HPXML file') do |t|
     options[:hpxml] = t