Skip to content

Commit

Permalink
support adding feture variables after inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
taichi-ishitani committed Jan 22, 2025
1 parent 9792ec8 commit 725e95b
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 45 deletions.
77 changes: 77 additions & 0 deletions lib/rggen/core/base/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
79 changes: 39 additions & 40 deletions lib/rggen/core/output_base/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
78 changes: 73 additions & 5 deletions spec/rggen/core/output_base/feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 725e95b

Please sign in to comment.