Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ruby/bindings/InitRubyBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,17 +534,31 @@ if RbConfig::CONFIG['arch'] =~ /x64-mswin64/
end

# load embedded ruby gems
# This loads :/ruby/gems/3.2.0/specifications/default
require 'rubygems'
require 'rubygems/version'
Gem::Platform.local

# Note that we have three standard locations where we have gemspecs
#
# 1. This is the Ruby-built in stuff (eg; csv, fileutils, irb, json, reline, etc)
#':/ruby/gems/3.2.0/specifications/default',
#
# 2. This is the Ruby-built in stuff that's brought in via gems/bundled_gems (https://github.com/ruby/ruby/blob/f91480d7a671b1b114270a4b5e4d3c5aa6dabce9/gems/bundled_gems)
# (rbs, debug, matrix, rexml, etc)
# ':/ruby/gems/3.2.0/specifications',
#
# 3. Where we have the openstudio-gems gemspecs (openstudio-standards, etc)
#':/ruby/3.2.0/specifications',

if original_arch
RbConfig::CONFIG['arch'] = original_arch
end
)ruby";

openstudio::evalString(initScript);

#ifndef _WIN32 // no io/console available
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When LogLevel = Trace, don't pre-require IRB on windows, it fails

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes #5216?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah thanks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, so why no irb on windows? It fails due to no io/console or because of the logging bug?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no io/console

const std::string irbPatch = R"ruby(if $logger.trace?
require 'irb'
require 'irb/lc/error'
Expand Down Expand Up @@ -655,6 +669,7 @@ end
end)ruby";

openstudio::evalString(irbPatch);
#endif
}

void setupEmbeddedGemsClearEnvVars() {
Expand Down
90 changes: 59 additions & 31 deletions ruby/engine/embedded_help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def self.get_absolute_path(p)
end
absolute_path = ':' + absolute_path
else
absolute_path = File.expand_path p2
absolute_path = File.expand_path p
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weirdly enough, this has always been broken. p2 isn't defined here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we never called this function on a non embedded file?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct. We don't use it directly basically, and checks were doing prior to calling it.

end
return absolute_path
end
Expand Down Expand Up @@ -626,8 +626,10 @@ def self.[](*args)
end
end

def self.glob(pattern, *args, **options)
def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so strap yourself.

First off: In Ruby 3.x, especially post-3.1, many core methods have been migrated to use the new RubyVM Primitive interface — which acts as a bridge between Ruby and C-defined functions, often exposed via FFI-like syntax for performance and modularity.

https://github.com/ruby/ruby/blob/f91480d7a671b1b114270a4b5e4d3c5aa6dabce9/dir.rb#L219-L221

  def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true)
    Primitive.dir_s_glob(pattern, flags, base, sort)
  end

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note about what the heck that this:

def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true)

This is subtle Ruby trick used for backward compatibility with older versions of Ruby where Dir.glob accepted a positional second argument (flags) instead of keyword arguments.

_flags = 0      # Default value if the second positional argument isn't given
flags: _flags  # Keyword argument `:flags` defaults to the positional `_flags`

In other words:

  • Calling Dir.glob("*.rb", File::FNM_DOTMATCH) (using the old 2-argument form), _flags will be assigned File::FNM_DOTMATCH, and flags: will default to that.

  • Calling Dir.glob("*.rb", flags: File::FNM_DOTMATCH), the keyword argument will override the default.


debug = false
# debug = !base.nil? && base.start_with?(':/ruby/gems/3.2.0/specifications/default')
pattern_array = []
if pattern.is_a? String
pattern_array = [pattern]
Expand All @@ -637,45 +639,71 @@ def self.glob(pattern, *args, **options)
pattern_array = pattern
end

#puts "Dir.glob pattern = #{pattern}, pattern_array = #{pattern_array}, args = #{args}, options = #{options}"
override_args_extglob = false

result = []
pattern_array.each do |pattern|
pattern_has_embedded = pattern_array.any? {|p| p.to_s.chars.first == ':'}
base_has_embedded = (!base.nil? && base.to_s.chars.first == ':')
if !pattern_has_embedded && !base_has_embedded
# puts "Original glob"
return self.original_glob(pattern, flags: flags, base: base, sort: sort)
end
Comment on lines +642 to +647
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If nothing has an embedded thingy, just forward to the original method.


if pattern.to_s.chars.first == ':'
absolute_base = if base.nil?
nil
else
OpenStudio.get_absolute_path(base)
end
Comment on lines +649 to +653
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the base is provided, get_absolute_path it, which will just do File.expand_path base if base is not embedded. I could have used File.expand_path or File.absolute_path I guess, which we override.

if debug
puts "pattern_array=#{pattern_array}"
puts "base=#{base}"
puts "flags=#{flags}"
puts "pattern_has_embedded=#{pattern_has_embedded}"
puts "base_has_embedded=#{base_has_embedded}"
puts "absolute_base=#{absolute_base}"
end

# DLM: seems like this is needed for embedded paths, possibly due to leading ':' character?
override_args_extglob = true
# DLM: seems like this is needed for embedded paths, possibly due to leading ':' character?
# JM (2025): Seems like fnmatch behaves differently than Dir.glob
# fnmatch specifically needs EXTGLOB to allow patterns like '{a,b}' while
# glob seems to allow that directly
override_args_extglob = true

#puts "searching embedded files for #{pattern}"
absolute_pattern = OpenStudio.get_absolute_path(pattern)
#puts "absolute_pattern #{absolute_pattern}"
flags = flags | File::FNM_EXTGLOB if override_args_extglob
result = []
pattern_array.each do |pattern|

EmbeddedScripting::fileNames.each do |name|
absolute_path = OpenStudio.get_absolute_path(name)
absolute_pattern = if pattern.to_s.chars.first == ':'
OpenStudio.get_absolute_path(pattern)
elsif !base.nil?
File.expand_path(pattern, base)
else
pattern
end
if debug
puts "searching embedded files for #{pattern}"
puts "absolute_pattern #{absolute_pattern}"
end

if override_args_extglob
if File.fnmatch( absolute_pattern, absolute_path, File::FNM_EXTGLOB )
#puts "#{absolute_path} is a match!"
result << absolute_path
end
else
if File.fnmatch( absolute_pattern, absolute_path, *args, **options )
#puts "#{absolute_path} is a match!"
result << absolute_path
end
EmbeddedScripting::fileNames.each do |name|
absolute_path = OpenStudio.get_absolute_path(name)
if base_has_embedded
next unless absolute_path.start_with?(absolute_base)
if debug
puts "name=#{name}, absolute_path=#{absolute_path}"
puts "absolute_path.start_with?(absolute_base)=#{absolute_path.start_with?(absolute_base)}"
Comment on lines +685 to +691
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loop on the embedded fileNames (which all start with ':/'), skip if they don't match the base

end

end

else
if override_args_extglob
result.concat(self.original_glob(pattern, File::FNM_EXTGLOB))
else
result.concat(self.original_glob(pattern, *args, **options))
#if override_args_extglob
# if File.fnmatch( absolute_pattern, absolute_path, File::FNM_EXTGLOB )
if File.fnmatch(absolute_pattern, absolute_path, flags)
#puts "#{absolute_path} is a match!"
result << absolute_path
Comment on lines +695 to +699
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can fnmatch, afaik there's no need to override the flags.

end
end

end

if debug
puts result
end

if block_given?
Expand Down
4 changes: 4 additions & 0 deletions src/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:openstudio> ruby_version
)

add_test(NAME OpenStudioCLI.ruby_version_trace
COMMAND $<TARGET_FILE:openstudio> --loglevel Trace ruby_version
)

add_test(NAME OpenStudioCLI.python_version
COMMAND $<TARGET_FILE:openstudio> python_version
)
Expand Down