Repository: NREL/OpenStudio (language: python)

Issue:
`labs` on Windows doesn't correctly process backward slashes
Issue overview
--------------

Thought I was going insane because the OS CLI `labs` was sometimes working and sometimes not. Finally figured out that it works when paths have forward slashes but not backward slashes.

On Windows, paths autocomplete using backward slashes, which doesn't run:
![image](https://user-images.githubusercontent.com/5861765/230650269-a203f4e3-ac81-4571-b4a3-2845970c9c96.png)

But if you replace the backward slashes with forward slashes, it runs correctly:
![image](https://user-images.githubusercontent.com/5861765/230650332-7500ba4e-5083-4749-8251-76f5b4d85e0e.png)

The standard (non-`labs`) workflow works correctly in both cases.

Tested using 5eaa0d8ebb.
`labs` on Windows doesn't correctly process backward slashes
Issue overview
--------------

Thought I was going insane because the OS CLI `labs` was sometimes working and sometimes not. Finally figured out that it works when paths have forward slashes but not backward slashes.

On Windows, paths autocomplete using backward slashes, which doesn't run:
![image](https://user-images.githubusercontent.com/5861765/230650269-a203f4e3-ac81-4571-b4a3-2845970c9c96.png)

But if you replace the backward slashes with forward slashes, it runs correctly:
![image](https://user-images.githubusercontent.com/5861765/230650332-7500ba4e-5083-4749-8251-76f5b4d85e0e.png)

The standard (non-`labs`) workflow works correctly in both cases.

Tested using 5eaa0d8ebb.
`labs` on Windows doesn't correctly process backward slashes
Issue overview
--------------

Thought I was going insane because the OS CLI `labs` was sometimes working and sometimes not. Finally figured out that it works when paths have forward slashes but not backward slashes.

On Windows, paths autocomplete using backward slashes, which doesn't run:
![image](https://user-images.githubusercontent.com/5861765/230650269-a203f4e3-ac81-4571-b4a3-2845970c9c96.png)

But if you replace the backward slashes with forward slashes, it runs correctly:
![image](https://user-images.githubusercontent.com/5861765/230650332-7500ba4e-5083-4749-8251-76f5b4d85e0e.png)

The standard (non-`labs`) workflow works correctly in both cases.

Tested using 5eaa0d8ebb.


Discussion / hints:
If you need to reproduce exactly what I've done:
1. Clone https://github.com/NREL/OpenStudio-HPXML/tree/os360
2. Run `openstudio.exe labs workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test`
3. Run `openstudio.exe labs workflow\run_simulation.rb -x workflow\sample_files\base.xml -o test`
If you need to reproduce exactly what I've done:
1. Clone https://github.com/NREL/OpenStudio-HPXML/tree/os360
2. Run `openstudio.exe labs workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test`
3. Run `openstudio.exe labs workflow\run_simulation.rb -x workflow\sample_files\base.xml -o test`
If you need to reproduce exactly what I've done:
1. Clone https://github.com/NREL/OpenStudio-HPXML/tree/os360
2. Run `openstudio.exe labs workflow/run_simulation.rb -x workflow/sample_files/base.xml -o test`
3. Run `openstudio.exe labs workflow\run_simulation.rb -x workflow\sample_files\base.xml -o test`

Fix patch:
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index f338d39d27b..21edfa17ec9 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -230,6 +230,95 @@ if(BUILD_TESTING)
   )
   set_tests_properties(OpenStudioCLI.Labs.Run_RubyPython PROPERTIES RESOURCE_LOCK "compact_osw")
 
+
+  # ============ #4856 - Forward a Path properly no matter the slashes employed ============
+
+  # Note: I picked the name `test_folder`on purpose, because `\t` is an escape sequence, so I could test this up too.
+  # `.\test_folder\hello.xml`
+  #   ~~          ##
+  #    |           |
+  #   escape       |
+  #                <-- not an escape sequence, neither in C, ruby or Python
+
+  # Test via optparse and check file exists
+  # Historical CLI versions
+    add_test(NAME OpenStudioCLI.execute_ruby_script.forward_flags.optparse.path_forwardslash
+    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/"
+  )
+  if (WIN32)
+    # Posix paths don't understand a backward slash anyways
+    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"
+      WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+    )
+  endif()
+  # Labs versions
+  add_test(NAME OpenStudioCLI.Labs.execute_ruby_script.forward_flags.optparse.path_forwardslash
+    COMMAND $<TARGET_FILE:openstudio> labs execute_ruby_script execute_ruby_script_optparse_path.rb -x "./test_folder/hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  if (WIN32)
+    add_test(NAME OpenStudioCLI.Labs.execute_ruby_script.forward_flags.optparse.path_backwardslash
+      COMMAND $<TARGET_FILE:openstudio> labs execute_ruby_script execute_ruby_script_optparse_path.rb -x ".\\test_folder\\hello.xml"
+      WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+    )
+  endif()
+
+  # Historical CLI versions
+  add_test(NAME OpenStudioCLI.execute_ruby_script.forward_flags.path_forwardslash
+    COMMAND $<TARGET_FILE:openstudio> execute_ruby_script execute_ruby_script_with_arguments_test.rb -x "./test_folder/hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.execute_ruby_script.forward_flags.path_forwardslash PROPERTIES
+      PASS_REGULAR_EXPRESSION "Hello from -x, at ./test_folder/hello.xml"
+  )
+
+  add_test(NAME OpenStudioCLI.execute_ruby_script.forward_flags.path_backwardslash
+    COMMAND $<TARGET_FILE:openstudio> execute_ruby_script execute_ruby_script_with_arguments_test.rb -x ".\\test_folder\\hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.execute_ruby_script.forward_flags.path_backwardslash PROPERTIES
+    # Cmake regexes require double escaping the \ escape, this tests that `Hello from -x, at .\test_folder\hello.xml` is printed to the console
+    PASS_REGULAR_EXPRESSION "Hello from -x, at .\\\\test_folder\\\\hello.xml"
+  )
+
+  # Labs versions
+  add_test(NAME OpenStudioCLI.Labs.execute_ruby_script.forward_flags.path_forwardslash
+    COMMAND $<TARGET_FILE:openstudio> labs execute_ruby_script execute_ruby_script_with_arguments_test.rb -x "./test_folder/hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.Labs.execute_ruby_script.forward_flags.path_forwardslash PROPERTIES
+      PASS_REGULAR_EXPRESSION "Hello from -x, at ./test_folder/hello.xml"
+  )
+
+  add_test(NAME OpenStudioCLI.Labs.execute_ruby_script.forward_flags.path_backwardslash
+    COMMAND $<TARGET_FILE:openstudio> labs execute_ruby_script execute_ruby_script_with_arguments_test.rb -x ".\\test_folder\\hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.Labs.execute_ruby_script.forward_flags.path_backwardslash PROPERTIES
+    PASS_REGULAR_EXPRESSION "Hello from -x, at .\\\\test_folder\\\\hello.xml"
+  )
+
+  add_test(NAME OpenStudioCLI.Labs.execute_python_script.forward_flags.path_forwardslash
+    COMMAND $<TARGET_FILE:openstudio> labs execute_python_script execute_python_script_with_arguments_test.py -x "./test_folder/hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.Labs.execute_python_script.forward_flags.path_forwardslash PROPERTIES
+      PASS_REGULAR_EXPRESSION "Hello from -x, at ./test_folder/hello.xml"
+  )
+
+  add_test(NAME OpenStudioCLI.Labs.execute_python_script.forward_flags.path_backwardslash
+    COMMAND $<TARGET_FILE:openstudio> labs execute_python_script execute_python_script_with_arguments_test.py -x ".\\test_folder\\hello.xml"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/test/"
+  )
+  set_tests_properties(OpenStudioCLI.Labs.execute_python_script.forward_flags.path_backwardslash PROPERTIES
+    PASS_REGULAR_EXPRESSION "Hello from -x, at .\\\\test_folder\\\\hello.xml"
+  )
+
+  # ============ EndForward a Path properly no matter the slashes employed ============
+
+
   file(GLOB RUBY_TEST_SRC
     # find all CLI test
     "test/test*.rb"
diff --git a/src/cli/UpdateCommand.cpp b/src/cli/UpdateCommand.cpp
index 19a82a49dd1..f7e12ad2bb9 100644
--- a/src/cli/UpdateCommand.cpp
+++ b/src/cli/UpdateCommand.cpp
@@ -90,8 +90,25 @@ namespace cli {
     for (const auto& arg : arguments) {
       cmd += fmt::format("ARGV << \"{}\"\n", arg);
     }
-    cmd += fmt::format("require '{}'", rubyScriptPath.generic_string());
-    rubyEngine->exec(cmd);
+    cmd += fmt::format(R"(
+begin
+  require '{}'
+rescue Exception => e
+  puts
+  puts "Error: #{{e.message}}"
+  puts "Backtrace:\n\t" + e.backtrace.join("\n\t")
+  raise
+end
+     )",
+                       rubyScriptPath.generic_string());
+    try {
+      rubyEngine->exec(cmd);
+    } catch (...) {
+      // Bail faster though ruby isn't slow like python
+      fmt::print(stderr, "Failed to execute '{}'\n", rubyScriptPath.generic_string());
+      exit(1);
+      // throw std::runtime_error(fmt::format("Failed to execute '{}'\n", rubyScriptPath.generic_string()));
+    }
   }
 
   void executePythonScriptCommand(openstudio::path pythonScriptPath, ScriptEngineInstance& pythonEngine, const std::vector<std::string>& arguments) {
@@ -117,7 +134,14 @@ spec.loader.exec_module(module)
 )python",
                        pythonScriptPath.generic_string());
     // fmt::print("{}\n", cmd);
-    pythonEngine->exec(cmd);
+    try {
+      pythonEngine->exec(cmd);
+    } catch (...) {
+      // Bail faster...
+      fmt::print(stderr, "Failed to execute '{}'\n", pythonScriptPath.generic_string());
+      exit(1);
+      // throw std::runtime_error(fmt::format("Failed to execute '{}'\n", pythonScriptPath.generic_string()));
+    }
   }
 
   void executeGemListCommand(ScriptEngineInstance& rubyEngine) {
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index 448c894ff94..74be9e37cb1 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -51,11 +51,21 @@ int main(int argc, char* argv[]) {
   const std::string programName = std::move(args.front());
   args.erase(args.begin());
 
+  const bool is_labs = !args.empty() && (args[0] == "labs");
+  if (is_labs) {
+    // Replace backward slashes with forward slashes... cf #4856
+    std::for_each(args.begin(), args.end(), [](auto& s) {
+      //std::replace(s.begin(), s.end(), '\\', '/');
+      boost::replace_all(s, "\\", "\\\\");
+    });
+    // fmt::print("Cleaned after slash replacement arguments: {}\n", args);
+  }
+
   // ScriptEngineInstance will delay load the engines
   openstudio::ScriptEngineInstance rubyEngine("rubyengine", args);
   openstudio::ScriptEngineInstance pythonEngine("pythonengine", args);
 
-  if (!args.empty() && (std::string_view(args[0]) == "labs")) {
+  if (is_labs) {
     CLI::App app{"openstudio"};
     app.name(programName);
 


Test patch:
diff --git a/src/cli/test/execute_ruby_script_optparse_path.rb b/src/cli/test/execute_ruby_script_optparse_path.rb
new file mode 100644
index 00000000000..62484dd24c3
--- /dev/null
+++ b/src/cli/test/execute_ruby_script_optparse_path.rb
@@ -0,0 +1,23 @@
+require 'optparse'
+require 'pathname'
+
+options = {}
+OptionParser.new do |opts|
+  opts.banner = "Usage: #{File.basename(__FILE__)} -x building.xml"
+
+  opts.on('-x', '--xml <FILE>', 'HPXML file') do |t|
+    options[:hpxml] = t
+  end
+end.parse!
+
+if not options[:hpxml]
+  fail "HPXML argument is required. Call #{File.basename(__FILE__)} -h for usage."
+end
+
+puts "options=#{options}"
+unless (Pathname.new options[:hpxml]).absolute?
+  options[:hpxml] = File.expand_path(options[:hpxml])
+end
+unless File.exist?(options[:hpxml]) && options[:hpxml].downcase.end_with?('.xml')
+  fail "'#{options[:hpxml]}' does not exist or is not an .xml file."
+end
diff --git a/src/cli/test/test_folder/hello.xml b/src/cli/test/test_folder/hello.xml
new file mode 100644
index 00000000000..e69de29bb2d