From 725e95b846c53812970b9704e8d1599c54bf0eb6 Mon Sep 17 00:00:00 2001 From: Taichi Ishitani Date: Thu, 23 Jan 2025 08:15:34 +0900 Subject: [PATCH] support adding feture variables after inheritance --- lib/rggen/core/base/feature.rb | 77 ++++++++++++++++++++ lib/rggen/core/output_base/feature.rb | 79 ++++++++++----------- spec/rggen/core/output_base/feature_spec.rb | 78 ++++++++++++++++++-- 3 files changed, 189 insertions(+), 45 deletions(-) diff --git a/lib/rggen/core/base/feature.rb b/lib/rggen/core/base/feature.rb index 141723f..c52b530 100644 --- a/lib/rggen/core/base/feature.rb +++ b/lib/rggen/core/base/feature.rb @@ -43,6 +43,83 @@ def define_helpers(&) def available?(&) define_method(:available?, &) end + + def feaure_scala_variable_get(name) + if instance_variable_defined?(name) + instance_variable_get(name) + else + parent_feature_variable_get(__method__, name) + end + end + + def feature_array_variable_get(name) + parent = parent_feature_variable_get(__method__, name) + own = instance_variable_get(name) + + if [parent, own] in [Array, Array] + [*parent, *own] + else + parent || own + end + end + + def feature_array_variable_push(name, value) + array = + instance_variable_get(name) || + instance_variable_set(name, []) + array << value + end + + def feature_hash_variable_get(name) + parent = parent_feature_variable_get(__method__, name) + own = instance_variable_get(name) + + if [parent, own] in [Hash, Hash] + parent.merge(own) + else + parent || own + end + end + + def feature_hash_variable_store(name, key, value) + hash = + instance_variable_get(name) || + instance_variable_set(name, {}) + hash[key] = value + end + + def feature_hash_array_variable_get(name) + parent = parent_feature_variable_get(__method__, name) + own = instance_variable_get(name) + + if [parent, own] in [Hash, Hash] + parent + .merge(own) { |_, parent_val, own_val| [*parent_val, *own_val] } + else + parent || own + end + end + + def feature_hash_array_variable_update(name, method, key, value) + hash = + instance_variable_get(name) || + instance_variable_set(name, Hash.new { |h, k| h[k] = [] }) + hash[key].__send__(method, value) + end + + def feature_hash_array_variable_push(name, key, value) + feature_hash_array_variable_update(name, :push, key, value) + end + + def feature_hash_array_variable_prepend(name, key, value) + feature_hash_array_variable_update(name, :prepend, key, value) + end + + def parent_feature_variable_get(method, name) + return unless superclass.respond_to?(method, true) + + superclass.__send__(method, name) + end end available? { true } diff --git a/lib/rggen/core/output_base/feature.rb b/lib/rggen/core/output_base/feature.rb index 53692a6..d53d9ae 100644 --- a/lib/rggen/core/output_base/feature.rb +++ b/lib/rggen/core/output_base/feature.rb @@ -8,34 +8,43 @@ class Feature < Base::Feature include RaiseError class << self - attr_reader :pre_builders - attr_reader :builders + def pre_builders + feature_array_variable_get(:@pre_builders) + end + + def builders + feature_array_variable_get(:@builders) + end - def code_generators - @code_generators ||= {} + def code_blocks(phase) + variable_name = :"@#{phase}_blocks" + feature_hash_array_variable_get(variable_name) end def template_engine(engine = nil) - @template_engine = engine.instance if engine - @template_engine + if engine + @template_engine = engine.instance + else + feaure_scala_variable_get(:@template_engine) + end end - attr_reader :file_writer + def file_writer + feaure_scala_variable_get(:@file_writer) + end def exported_methods - @exported_methods ||= [] + feature_array_variable_get(:@exported_methods) end private def pre_build(&body) - @pre_builders ||= [] - @pre_builders << body + feature_array_variable_push(:@pre_builders, body) end def build(&body) - @builders ||= [] - @builders << body + feature_array_variable_push(:@builders, body) end [:pre_code, :main_code, :post_code].each do |phase| @@ -53,8 +62,8 @@ def register_code_generator(phase, kind, **options, &body) else body end - (code_generators[phase] ||= CodeGenerator.new) - .register(kind, &block) + block && + feature_hash_array_variable_push(:"@#{phase}_blocks", kind, block) end def extract_template_path(options) @@ -68,26 +77,8 @@ def write_file(file_name_pattern, &) def export(*methods) methods.each do |method| - exported_methods.include?(method) || - (exported_methods << method) - end - end - end - - class << self - def inherited(subclass) - super - export_instance_variable(:@pre_builders, subclass, &:dup) - export_instance_variable(:@builders, subclass, &:dup) - export_instance_variable(:@template_engine, subclass) - export_instance_variable(:@file_writer, subclass) - export_instance_variable(:@exported_methods, subclass, &:dup) - copy_code_generators(subclass) - end - - def copy_code_generators(subclass) - @code_generators&.each do |phase, generator| - subclass.code_generators[phase] = generator.copy + exported_methods&.include?(method) || + feature_array_variable_push(:@exported_methods, method) end end end @@ -110,9 +101,9 @@ def build def export(*methods) methods.each do |method| - unless exported_methods(:class).include?(method) || - exported_methods(:object).include?(method) - exported_methods(:object) << method + unless exported_methods(:class)&.include?(method) || + exported_methods(:object)&.include?(method) + (@exported_methods ||= []) << method end end end @@ -121,13 +112,21 @@ def exported_methods(scope) if scope == :class self.class.exported_methods else - @exported_methods ||= [] + @exported_methods end end def generate_code(code, phase, kind) - generator = self.class.code_generators[phase] - generator&.generate(self, code, kind) + blocks = self.class.code_blocks(phase) + return unless blocks + + blocks[kind]&.each do |block| + if block.arity.zero? + code << instance_exec(&block) + else + instance_exec(code, &block) + end + end end def write_file(directory = nil) diff --git a/spec/rggen/core/output_base/feature_spec.rb b/spec/rggen/core/output_base/feature_spec.rb index b3f8428..b2ad986 100644 --- a/spec/rggen/core/output_base/feature_spec.rb +++ b/spec/rggen/core/output_base/feature_spec.rb @@ -51,6 +51,21 @@ def define_and_create_feature(super_class = nil, &block) expect(feature.instance_variable_get(:@foo)).to be component.foo expect(feature.instance_variable_get(:@bar)).to be component.bar + + parent_feature = define_feature {} + feature = define_and_create_feature(parent_feature) do + pre_build { @bar = component.bar } + end + parent_feature.class_eval do + pre_build { @foo = component.foo } + end + + allow(component).to receive(:foo).and_return('foo') + allow(component).to receive(:bar).and_return('bar') + feature.pre_build + + expect(feature.instance_variable_get(:@foo)).to be component.foo + expect(feature.instance_variable_get(:@bar)).to be component.bar end end @@ -94,6 +109,21 @@ def define_and_create_feature(super_class = nil, &block) expect(feature.instance_variable_get(:@foo)).to be component.foo expect(feature.instance_variable_get(:@bar)).to be component.bar + + parent_feature = define_feature + feature = define_and_create_feature(parent_feature) do + build { @bar = component.bar } + end + parent_feature.class_eval do + build { @foo = component.foo } + end + + allow(component).to receive(:foo).and_return('foo') + allow(component).to receive(:bar).and_return('bar') + feature.build + + expect(feature.instance_variable_get(:@foo)).to be component.foo + expect(feature.instance_variable_get(:@bar)).to be component.bar end end @@ -200,15 +230,29 @@ def render(context, template) context '継承された場合' do specify 'コード生成ブロックは継承される' do parent_feature = define_feature do - send(phase, :foo) { 'foo' } - send(phase, :bar) { 'bar' } + send(phase, :foo) { 'foo_0' } + send(phase, :bar) { |c| c << 'bar_0' } end feature = define_and_create_feature(parent_feature) - expect(code).to receive(:<<). with('foo') + expect(code).to receive(:<<).with('foo_0') + feature.generate_code(code, phase, :foo) + + expect(code).to receive(:<<).with('bar_0') + feature.generate_code(code, phase, :bar) + + feature = define_and_create_feature(parent_feature) + parent_feature.class_eval do + send(phase, :foo) { 'foo_1' } + send(phase, :bar) { |c| c << 'bar_1' } + end + + expect(code).to receive(:<<).with('foo_0') + expect(code).to receive(:<<).with('foo_1') feature.generate_code(code, phase, :foo) - expect(code).to receive(:<<). with('bar') + expect(code).to receive(:<<).with('bar_0') + expect(code).to receive(:<<).with('bar_1') feature.generate_code(code, phase, :bar) end @@ -224,7 +268,6 @@ def render(context, template) expect(code).to receive(:<<).with('foo_1') feature.generate_code(code, phase, :foo) - expect(code).to receive(:<<).with('foo_0') expect(code).not_to receive(:<<).with('foo_1') parent_feature.generate_code(code, phase, :foo) @@ -309,6 +352,20 @@ def file_content; "#{object_id} foo !"; end expect(File).to receive(:binwrite).with(match_string(feature.file_name), feature.file_content) feature.write_file + + parent_feature = define_feature(feature_base) + feature = define_and_create_feature(parent_feature) do + def file_name; "#{object_id}_bar.txt"; end + def file_content; "#{object_id} bar !"; end + end + parent_feature.class_eval do + write_file '<%= file_name %>' do |f| + f << file_content + end + end + + expect(File).to receive(:binwrite).with(match_string(feature.file_name), feature.file_content) + feature.write_file end end @@ -359,6 +416,17 @@ def file_content; "#{object_id} foo !"; end expect(foo_feature.exported_methods(:class)).to match [:foo] expect(bar_feature.exported_methods(:class)).to match [:foo, :bar] expect(baz_feature.exported_methods(:class)).to match [:foo, :bar, :baz] + + foo_feature = define_and_create_feature {} + bar_feature = define_and_create_feature(foo_feature.class) {} + baz_feature = define_and_create_feature(bar_feature.class) {} + foo_feature.class.class_eval { export :foo } + bar_feature.class.class_eval { export :bar } + baz_feature.class.class_eval { export :baz } + + expect(foo_feature.exported_methods(:class)).to match [:foo] + expect(bar_feature.exported_methods(:class)).to match [:foo, :bar] + expect(baz_feature.exported_methods(:class)).to match [:foo, :bar, :baz] end end end