From 1694c3d00e308dcba2faa0d61e019e09d6f794f3 Mon Sep 17 00:00:00 2001 From: Loren Segal Date: Tue, 3 Sep 2024 00:45:59 -0700 Subject: [PATCH] Replace ostruct with custom version Avoid future deprecations, behavior changes, and performance warnings. Closes #1545 See #1525 --- benchmarks/struct_vs_ostruct.rb | 23 +++++++ lib/yard/autoload.rb | 1 + lib/yard/code_objects/macro_object.rb | 1 - lib/yard/docstring_parser.rb | 1 - lib/yard/handlers/processor.rb | 1 - lib/yard/open_struct.rb | 66 ++++++++++++++++++++ lib/yard/parser/source_parser.rb | 1 - lib/yard/tags/directives.rb | 1 - lib/yard/templates/engine.rb | 1 - lib/yard/templates/template_options.rb | 1 - spec/cli/gems_spec.rb | 1 - spec/handlers/base_spec.rb | 1 - spec/handlers/dsl_handler_spec.rb | 1 - spec/handlers/processor_spec.rb | 4 +- spec/parser/source_parser_spec.rb | 2 +- spec/server/commands/library_command_spec.rb | 1 - spec/server/spec_helper.rb | 1 - spec/templates/helpers/html_helper_spec.rb | 1 - 18 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 benchmarks/struct_vs_ostruct.rb create mode 100644 lib/yard/open_struct.rb diff --git a/benchmarks/struct_vs_ostruct.rb b/benchmarks/struct_vs_ostruct.rb new file mode 100644 index 000000000..e4fe40237 --- /dev/null +++ b/benchmarks/struct_vs_ostruct.rb @@ -0,0 +1,23 @@ +require 'benchmark' +require 'ostruct' +require_relative '../lib/yard' + +n = 100000 +class MyStruct < Struct.new(:a, :b, :c); end +ostruct = OpenStruct.new +yostruct = YARD::OpenStruct.new +mystruct = MyStruct.new + +Benchmark.bmbm do |x| + x.report("Struct.new(args)") { n.times { MyStruct.new 1, 2, 3 } } + x.report("Struct (assign)") { n.times { mystruct.a = 1 } } + x.report("Struct (read)") { n.times { mystruct.a } } + x.report("OpenStruct.new(args)") { n.times { OpenStruct.new a: 1, b: 2, c: 3 } } + x.report("OpenStruct.new (blank)") { n.times { OpenStruct.new } } + x.report("OpenStruct (assign)") { n.times { ostruct.a = 1 } } + x.report("OpenStruct (read)") { n.times { ostruct.a } } + x.report("YARD::OpenStruct.new(args)") { n.times { YARD::OpenStruct.new a: 1, b: 2, c: 3 } } + x.report("YARD::OpenStruct.new (blank)") { n.times { YARD::OpenStruct.new } } + x.report("YARD::OpenStruct (assign)") { n.times { yostruct.a = 1 } } + x.report("YARD::OpenStruct (read)") { n.times { yostruct.a } } +end diff --git a/lib/yard/autoload.rb b/lib/yard/autoload.rb index 774a9c579..a026ba99d 100644 --- a/lib/yard/autoload.rb +++ b/lib/yard/autoload.rb @@ -298,6 +298,7 @@ module Markup # Namespace for markup providers autoload :DocstringParser, __p('docstring_parser') autoload :GemIndex, __p('gem_index') autoload :Logger, __p('logging') + autoload :OpenStruct, __p('open_struct') autoload :Options, __p('options') autoload :Registry, __p('registry') autoload :RegistryResolver, __p('registry_resolver') diff --git a/lib/yard/code_objects/macro_object.rb b/lib/yard/code_objects/macro_object.rb index 235de4207..d2f6e99ca 100644 --- a/lib/yard/code_objects/macro_object.rb +++ b/lib/yard/code_objects/macro_object.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD module CodeObjects diff --git a/lib/yard/docstring_parser.rb b/lib/yard/docstring_parser.rb index 40b9cb5ae..598534d94 100644 --- a/lib/yard/docstring_parser.rb +++ b/lib/yard/docstring_parser.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD # Parses text and creates a {Docstring} object to represent documentation diff --git a/lib/yard/handlers/processor.rb b/lib/yard/handlers/processor.rb index d6ea675ad..97e8660b3 100644 --- a/lib/yard/handlers/processor.rb +++ b/lib/yard/handlers/processor.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD module Handlers diff --git a/lib/yard/open_struct.rb b/lib/yard/open_struct.rb new file mode 100644 index 000000000..5926b2689 --- /dev/null +++ b/lib/yard/open_struct.rb @@ -0,0 +1,66 @@ +module YARD + # An OpenStruct compatible struct class that allows for basic access of attributes + # via +struct.attr_name+ and +struct.attr_name = value+. + class OpenStruct + def initialize(hash = {}) + @table = hash.each_pair { |k, v| [k.to_sym, v] } + end + + # @private + def method_missing(name, *args) + if name.to_s.end_with?('=') + varname = name.to_s[0..-2].to_sym + __cache_lookup__(varname) + self[varname] = args.first + else + __cache_lookup__(name) + self[name] + end + end + + def to_h + @table.dup + end + + def ==(other) + other.is_a?(self.class) && to_h == other.to_h + end + + def hash + @table.hash + end + + def dig(*keys) + @table.dig(*keys) + end + + def []=(key, value) + @table[key.to_sym] = value + end + + def [](key) + @table[key.to_sym] + end + + def each_pair(&block) + @table.each_pair(&block) + end + + def marshal_dump + @table + end + + def marshal_load(data) + @table = data + end + + private + + def __cache_lookup__(name) + instance_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name}; @table[:#{name}]; end + def #{name}=(v); @table[:#{name}] = v; end + RUBY + end + end +end diff --git a/lib/yard/parser/source_parser.rb b/lib/yard/parser/source_parser.rb index 94749dfd9..d42ea6f70 100644 --- a/lib/yard/parser/source_parser.rb +++ b/lib/yard/parser/source_parser.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require 'stringio' -require 'ostruct' module YARD module Parser diff --git a/lib/yard/tags/directives.rb b/lib/yard/tags/directives.rb index 8a56da801..c24ab4b36 100644 --- a/lib/yard/tags/directives.rb +++ b/lib/yard/tags/directives.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD module Tags diff --git a/lib/yard/templates/engine.rb b/lib/yard/templates/engine.rb index 1f5570700..4c7f57fa4 100644 --- a/lib/yard/templates/engine.rb +++ b/lib/yard/templates/engine.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD module Templates diff --git a/lib/yard/templates/template_options.rb b/lib/yard/templates/template_options.rb index 98cee3458..11a270e7d 100644 --- a/lib/yard/templates/template_options.rb +++ b/lib/yard/templates/template_options.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' module YARD module Templates diff --git a/spec/cli/gems_spec.rb b/spec/cli/gems_spec.rb index e77784980..a3441f49d 100644 --- a/spec/cli/gems_spec.rb +++ b/spec/cli/gems_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' require 'rubygems' RSpec.describe YARD::CLI::Gems do diff --git a/spec/handlers/base_spec.rb b/spec/handlers/base_spec.rb index d770b2823..0bfbdfc28 100644 --- a/spec/handlers/base_spec.rb +++ b/spec/handlers/base_spec.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require File.dirname(__FILE__) + '/spec_helper' -require 'ostruct' include Parser diff --git a/spec/handlers/dsl_handler_spec.rb b/spec/handlers/dsl_handler_spec.rb index 4d98253c9..faf2a48b6 100644 --- a/spec/handlers/dsl_handler_spec.rb +++ b/spec/handlers/dsl_handler_spec.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require File.dirname(__FILE__) + '/spec_helper' -require 'ostruct' RSpec.describe "YARD::Handlers::Ruby::#{LEGACY_PARSER ? "Legacy::" : ""}DSLHandler" do before(:all) { parse_file :dsl_handler_001, __FILE__ } diff --git a/spec/handlers/processor_spec.rb b/spec/handlers/processor_spec.rb index b47ab7a79..46c47ead7 100644 --- a/spec/handlers/processor_spec.rb +++ b/spec/handlers/processor_spec.rb @@ -3,7 +3,7 @@ RSpec.describe YARD::Handlers::Processor do before do - @proc = Handlers::Processor.new(OpenStruct.new(:parser_type => :ruby)) + @proc = Handlers::Processor.new(YARD::OpenStruct.new(:parser_type => :ruby)) end it "starts with public visibility" do @@ -19,7 +19,7 @@ end it "has a globals structure" do - expect(@proc.globals).to be_a(OpenStruct) + expect(@proc.globals).to be_a(YARD::OpenStruct) end it "ignores HandlerAborted exceptions (but print debug info)" do diff --git a/spec/parser/source_parser_spec.rb b/spec/parser/source_parser_spec.rb index 865e79e74..b0cc371ac 100644 --- a/spec/parser/source_parser_spec.rb +++ b/spec/parser/source_parser_spec.rb @@ -48,7 +48,7 @@ def after_file(&block) it "handles basic callback support" do before_list do |files, globals| expect(files).to eq ['foo.rb', 'bar.rb'] - expect(globals).to eq OpenStruct.new + expect(globals).to eq YARD::OpenStruct.new end parse_list ['foo.rb', 'foo!'], ['bar.rb', 'class Foo; end'] expect(Registry.at('Foo')).not_to be nil diff --git a/spec/server/commands/library_command_spec.rb b/spec/server/commands/library_command_spec.rb index ff78c9209..da3b4e100 100644 --- a/spec/server/commands/library_command_spec.rb +++ b/spec/server/commands/library_command_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' RSpec.describe YARD::Server::Commands::LibraryCommand do before do diff --git a/spec/server/spec_helper.rb b/spec/server/spec_helper.rb index 0f6dbf509..8ab793f9b 100644 --- a/spec/server/spec_helper.rb +++ b/spec/server/spec_helper.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'ostruct' include Server include Commands diff --git a/spec/templates/helpers/html_helper_spec.rb b/spec/templates/helpers/html_helper_spec.rb index b514fcacc..cbc43f11d 100644 --- a/spec/templates/helpers/html_helper_spec.rb +++ b/spec/templates/helpers/html_helper_spec.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require File.dirname(__FILE__) + "/shared_signature_examples" -require 'ostruct' RSpec.describe YARD::Templates::Helpers::HtmlHelper do include YARD::Templates::Helpers::BaseHelper