hospodar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +9 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +80 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +12 -0
  8. data/Gemfile.lock +86 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +49 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/hospodar.gemspec +47 -0
  15. data/lib/hospodar/builder/assembler.rb +96 -0
  16. data/lib/hospodar/builder/error.rb +15 -0
  17. data/lib/hospodar/builder/exeptional.rb +20 -0
  18. data/lib/hospodar/builder/flatten.rb +36 -0
  19. data/lib/hospodar/builder/helpers.rb +21 -0
  20. data/lib/hospodar/builder/id.rb +15 -0
  21. data/lib/hospodar/builder/instantiation_error.rb +24 -0
  22. data/lib/hospodar/builder/nested.rb +18 -0
  23. data/lib/hospodar/builder/proxy.rb +34 -0
  24. data/lib/hospodar/builder/strategies/enumerate.rb +26 -0
  25. data/lib/hospodar/builder/strategies/execute.rb +76 -0
  26. data/lib/hospodar/builder/strategies/init.rb +55 -0
  27. data/lib/hospodar/builder/strategies/inject.rb +35 -0
  28. data/lib/hospodar/builder/strategies/link.rb +31 -0
  29. data/lib/hospodar/builder/strategies/mount.rb +56 -0
  30. data/lib/hospodar/builder/strategies/translate.rb +60 -0
  31. data/lib/hospodar/builder/wrapped.rb +40 -0
  32. data/lib/hospodar/builder.rb +113 -0
  33. data/lib/hospodar/dsl.rb +84 -0
  34. data/lib/hospodar/factories.rb +28 -0
  35. data/lib/hospodar/group_definition.rb +22 -0
  36. data/lib/hospodar/inheritance_helpers.rb +36 -0
  37. data/lib/hospodar/module_builder.rb +88 -0
  38. data/lib/hospodar/registry.rb +34 -0
  39. data/lib/hospodar/subclassing_helpers.rb +29 -0
  40. data/lib/hospodar/version.rb +5 -0
  41. data/lib/hospodar.rb +54 -0
  42. metadata +255 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ # Error for signaling creating error
6
+ class InstantiationError < StandardError
7
+ attr_reader :init_attrs, :step_id
8
+
9
+ def initialize(id, init_attrs)
10
+ @init_attrs = init_attrs
11
+ @step_id = id
12
+ super(error_message)
13
+ end
14
+
15
+ private
16
+
17
+ def error_message
18
+ msg = "Can't create an instance of #{step_id} class"
19
+ msg += "with the folloving attributes: #{init_attrs}" unless init_attrs.empty?
20
+ msg
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ # Implements nest(&block) strategy in schemas
6
+ class Nested < Wrapped
7
+ def on_planing(receiver)
8
+ execution_plan(receiver, reverse: true, &dsl_block)
9
+ end
10
+
11
+ private
12
+
13
+ def type
14
+ :nested
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ # User accessible objects for firing up building process
6
+ class Proxy
7
+ def initialize(target)
8
+ @target = target
9
+ @condition = {}
10
+ end
11
+
12
+ def call
13
+ return @target.call if @condition.empty?
14
+
15
+ type, block = @condition.to_a.first
16
+ @target.call(type, &block)
17
+ end
18
+
19
+ def do_while(&block)
20
+ return if @condition.one?
21
+
22
+ @condition[:do_while] = block
23
+ self
24
+ end
25
+
26
+ def do_until(&block)
27
+ return if @condition.one?
28
+
29
+ @condition[:do_until] = block
30
+ self
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Does final steps after completing building process for nested structures
7
+ class Enumerate < Execute
8
+ attr_reader :delegate
9
+
10
+ def initialize(strategy, target, factory, delegate)
11
+ super(strategy, target, factory)
12
+ @delegate = delegate
13
+ end
14
+
15
+ private
16
+
17
+ def prepare_top
18
+ return target if strategy.object.nil?
19
+
20
+ Hospodar::Builder.def_accessor(strategy.last_step, on: target, to: strategy.object, delegate: delegate)
21
+ target
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Runs building process
7
+ class Execute
8
+ attr_reader :strategy, :target, :factory
9
+
10
+ def initialize(strategy, target, factory)
11
+ @strategy = strategy
12
+ @target = target
13
+ @factory = factory
14
+ end
15
+
16
+ def call(type = nil, &block)
17
+ run_with_error_handling { process(type, &block) }
18
+ prepare_top
19
+ end
20
+
21
+ private
22
+
23
+ def process(type = nil, &block)
24
+ build_plan.take_while do |object, id|
25
+ next true unless block
26
+
27
+ run_build_step(object, id, type, &block)
28
+ end
29
+ end
30
+
31
+ def run_build_step(object, id, type)
32
+ run_with_error_handling(return_value: false) do
33
+ res = yield(object, id)
34
+ type == :do_while ? res : !res
35
+ end
36
+ end
37
+
38
+ def run_with_error_handling(return_value: nil)
39
+ yield
40
+ rescue StandardError => e
41
+ handle_exception(e)
42
+ return_value
43
+ end
44
+
45
+ def build_plan
46
+ @build_plan ||= strategy.call
47
+ end
48
+
49
+ def handle_exception(exc)
50
+ target.on_exception ||= factory.on_exception
51
+ target.exception = wrap_error(exc)
52
+ exetute_exception_strategy(target.exception, target.on_exception, target)
53
+ end
54
+
55
+ def wrap_error(exc)
56
+ return exc if exc.is_a?(Hospodar::Builder::InstantiationError)
57
+
58
+ Hospodar::Builder.create_error(exc, strategy.step_id)
59
+ end
60
+
61
+ def exetute_exception_strategy(exc, on_exception, target)
62
+ case on_exception
63
+ when Proc
64
+ on_exception.call(exc, target)
65
+ when :raise
66
+ raise(exc)
67
+ end
68
+ end
69
+
70
+ def prepare_top
71
+ target
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Creates objects for nest and wrap strategies
7
+ class Init
8
+ attr_reader :strategy, :pipe, :target
9
+
10
+ def initialize(strategy, pipe, target)
11
+ @strategy = strategy
12
+ @pipe = pipe
13
+ @target = target
14
+ end
15
+
16
+ def call
17
+ Enumerator.new do |yielder|
18
+ instantiate_objects(yielder)
19
+ end.lazy
20
+ end
21
+
22
+ private
23
+
24
+ def instantiate_objects(yielder)
25
+ pipe.each do |title, init_proc, id|
26
+ object = create_object(init_proc, id)
27
+ next if object.nil? && target.ignore_exceptions?
28
+
29
+ yielder << [title, create_object(init_proc, id), id]
30
+ end
31
+ end
32
+
33
+ def create_object(init_proc, id)
34
+ init_proc.call(*init_attrs_for(id))
35
+ rescue StandardError => e
36
+ handle_exception(e, id, init_attrs_for(id))
37
+ end
38
+
39
+ def init_attrs_for(id)
40
+ Array(init_params[id.to_sym])
41
+ end
42
+
43
+ def init_params
44
+ @init_params ||= strategy.call
45
+ end
46
+
47
+ def handle_exception(exc, id, init_attrs)
48
+ exception = Hospodar::Builder.create_init_error(exc, id, init_attrs)
49
+ target.exception = exception
50
+ raise exception unless target.ignore_exceptions?
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Allows user to pass parameters for creating new objects via method calls
7
+ class Inject
8
+ # User use it as configuration DSL for builder
9
+ class Builder
10
+ attr_reader :__result__, :__methods_allowlist__
11
+
12
+ def initialize(list)
13
+ @__result__ = {}
14
+ @__methods_allowlist__ = list
15
+ list.each do |method_name|
16
+ define_singleton_method(method_name) do |*attrs|
17
+ @__result__[method_name] = attrs
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def initialize(list, &block)
24
+ @builder = Builder.new(list.map(&:to_sym))
25
+ @block = block
26
+ end
27
+
28
+ def call
29
+ @block&.call(@builder)
30
+ @builder.__result__
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ Link = Struct.new(:strategy, :delegate) do
7
+ attr_reader :object, :step_id
8
+
9
+ def call
10
+ Enumerator.new do |yielder|
11
+ strategy.call.inject(nil) do |object, (title, layer_object, id)|
12
+ @object = layer_object
13
+ @step_id = id
14
+ if title.nil?
15
+ yielder << [layer_object, id]
16
+ next layer_object
17
+ end
18
+ Hospodar::Builder.def_accessor(title, on: layer_object, to: object, delegate: delegate)
19
+ yielder << [layer_object, id]
20
+ layer_object
21
+ end
22
+ end
23
+ end
24
+
25
+ def last_step
26
+ step_id.title
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Creates objects and provides readers on target objects
7
+ class Mount
8
+ attr_reader :strategy, :pipe, :target, :step, :object, :step_id
9
+
10
+ def initialize(strategy, pipe, target, step)
11
+ @strategy = strategy
12
+ @pipe = pipe
13
+ @target = target
14
+ @step = step
15
+ end
16
+
17
+ def call
18
+ Enumerator.new do |yielder|
19
+ instantiate_objects(yielder)
20
+ end
21
+ end
22
+
23
+ def instantiate_objects(yielder)
24
+ pipe.each do |id, init_proc|
25
+ @step_id = id
26
+ object = create_object(init_proc, id)
27
+ next if object.nil? && target.ignore_exceptions?
28
+
29
+ target.define_singleton_method(id.title.to_sym) { object }
30
+ yielder << [object, id]
31
+ end
32
+ end
33
+
34
+ def create_object(init_proc, id)
35
+ init_proc.call(*init_attrs_for(id))
36
+ rescue StandardError => e
37
+ handle_exception(e, id, init_attrs_for(id))
38
+ end
39
+
40
+ def init_attrs_for(id)
41
+ Array(init_params[id.to_sym])
42
+ end
43
+
44
+ def init_params
45
+ @init_params ||= strategy.call
46
+ end
47
+
48
+ def handle_exception(exc, id, init_attrs)
49
+ exception = Hospodar::Builder.create_init_error(exc, id, init_attrs)
50
+ target.exception = exception
51
+ raise exception unless target.ignore_exceptions?
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ module Strategies
6
+ # Singleton for parsing schemas
7
+ module Translate
8
+ # Collects method calls in a given block
9
+ Tracer = Struct.new(:yielder) do
10
+ def method_missing(method_name, *attrs)
11
+ yielder << [method_name, attrs.first]
12
+ end
13
+
14
+ def respond_to_missing?
15
+ true
16
+ end
17
+ end
18
+
19
+ ID_CLASS = Hospodar::Builder::Id
20
+
21
+ ExecutionPlanMatrix = Struct.new(:collection) do
22
+ def with_creation_procs(factory)
23
+ self.collection = collection.map do |group, title|
24
+ [group, title, factory.mother_ship_assembler_new_instance(group, title)]
25
+ end
26
+ self
27
+ end
28
+
29
+ def to_nested_form(builder_params)
30
+ data = collection
31
+ data.unshift(builder_params.to_a) unless builder_params.object.nil?
32
+ intermediate_form = data.transpose
33
+ tobe_assembled = intermediate_form[1..-1]
34
+ keys = intermediate_form[0..1].transpose.map { |g, e| ID_CLASS.new(g, e) }
35
+ tobe_assembled.first.pop
36
+ tobe_assembled.first.unshift(nil)
37
+ tobe_assembled << keys
38
+ self.collection = tobe_assembled.transpose
39
+ self
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def call(reverse: false, &block)
45
+ enum = trace(&block)
46
+ return ExecutionPlanMatrix.new(enum) unless reverse
47
+
48
+ ExecutionPlanMatrix.new(enum.reverse)
49
+ end
50
+
51
+ private
52
+
53
+ def trace(&block)
54
+ Tracer.new([]).tap { |tracer| tracer.instance_eval(&block) }.yielder
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ module Builder
5
+ # Implements wrap(&block) strategy in schemas
6
+ class Wrapped < Assembler
7
+ def initialize(factory, on_exception, delegate)
8
+ super(factory, on_exception)
9
+ @delegate = delegate
10
+ end
11
+
12
+ def on_planing(receiver)
13
+ execution_plan(receiver, &dsl_block)
14
+ end
15
+
16
+ def on_building(receiver, plan, &on_create)
17
+ building_steps(receiver, plan, &on_create)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :delegate
23
+
24
+ def type
25
+ :wrapped
26
+ end
27
+
28
+ def execution_plan(receiver, reverse: false)
29
+ creation_matrix_from_dsl(receiver, reverse: reverse, &dsl_block).to_nested_form(builder_params).collection
30
+ end
31
+
32
+ def building_steps(receiver, plan, &on_create)
33
+ inject = Strategies::Inject.new(plan.map(&:last), &on_create)
34
+ init = Strategies::Init.new(inject, plan, target)
35
+ link = Strategies::Link.new(init, delegate)
36
+ Strategies::Enumerate.new(link, target, receiver, delegate)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hospodar/builder/id'
4
+ require 'hospodar/builder/proxy'
5
+ require 'hospodar/builder/assembler'
6
+ require 'hospodar/builder/flatten'
7
+ require 'hospodar/builder/wrapped'
8
+ require 'hospodar/builder/nested'
9
+ require 'hospodar/builder/helpers'
10
+ require 'hospodar/builder/instantiation_error'
11
+ require 'hospodar/builder/error'
12
+ require 'hospodar/builder/exeptional'
13
+
14
+ module Hospodar
15
+ # Introduces possibilty to build complex objects by schema
16
+ module Builder
17
+ # Handles method delegation
18
+ module MethodMissingDelegation
19
+ def method_missing(name, *attrs, &block)
20
+ return super unless methods.detect { |m| m == :ms_predecessor }
21
+
22
+ public_send(ms_predecessor).public_send(name, *attrs, &block)
23
+ end
24
+
25
+ def respond_to_missing?(method_name, _include_private = false)
26
+ return super unless methods.detect { |m| m == :ms_predecessor }
27
+
28
+ public_send(ms_predecessor).respond_to?(method_name)
29
+ end
30
+ end
31
+
32
+ private_constant :Assembler
33
+ private_constant :Proxy
34
+ private_constant :Helpers
35
+ private_constant :Exceptional
36
+ private_constant :Strategies
37
+ private_constant :MethodMissingDelegation
38
+
39
+ class Error < StandardError; end
40
+
41
+ def self.create_init_error(exc, id, init_attrs)
42
+ new_error = InstantiationError.new(id, init_attrs)
43
+ new_error.set_backtrace(exc.backtrace)
44
+ new_error
45
+ end
46
+
47
+ def self.create_error(exc, id)
48
+ new_error = Error.new(exc.message, id)
49
+ new_error.set_backtrace(exc.backtrace)
50
+ new_error
51
+ end
52
+
53
+ def self.def_accessor(accessor, on:, to:, delegate: false)
54
+ on.define_singleton_method(:ms_predecessor) { accessor }
55
+ on.define_singleton_method(accessor) { to }
56
+ on.extend(MethodMissingDelegation) if delegate
57
+ end
58
+
59
+ # DSL for describing objects schemas
60
+ module ClassMethods
61
+ class << self
62
+ attr_accessor :__mf_assembler_name__
63
+ end
64
+
65
+ def self.included(receiver)
66
+ receiver.extend self
67
+ end
68
+
69
+ def self.extended(receiver)
70
+ receiver.produces :flatten_structs, :wrapped_structs, :nested_structs
71
+ receiver.extend Helpers
72
+ end
73
+
74
+ def wrap(title, base_class: Class.new(Object), init: nil, delegate: false, on_exception: nil, &block)
75
+ wrapped_struct(title, base_class: base_class.include(Exceptional), init: init)
76
+ class_eval(&block)
77
+ Wrapped.new(self, on_exception, delegate).for(title, &block).inject_method
78
+ end
79
+
80
+ def nest(title, base_class: Class.new(Object), init: nil, delegate: false, on_exception: nil, &block)
81
+ nested_struct(title, base_class: base_class.include(Exceptional), init: init)
82
+ class_eval(&block)
83
+ Nested.new(self, on_exception, delegate).for(title, &block).inject_method
84
+ end
85
+
86
+ def flat(title, base_class: Class.new(Object), init: nil, on_exception: nil, &block)
87
+ flatten_struct(title, base_class: base_class.include(Exceptional), init: init)
88
+ class_eval(&block)
89
+ Flatten.new(self, on_exception).for(title, &block).inject_method
90
+ end
91
+
92
+ def on_exception(&block)
93
+ block ? @on_exception = block : @on_exception
94
+ end
95
+ end
96
+
97
+ # Copies on_exception callback definition to subclass
98
+ module InheritanceHelpers
99
+ def inherited(subclass)
100
+ super
101
+ subclass.on_exception(&on_exception)
102
+ end
103
+ end
104
+
105
+ def self.included(receiver)
106
+ receiver.extend ClassMethods
107
+ receiver.extend InheritanceHelpers
108
+ receiver.on_exception do |e, _result|
109
+ raise e
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ # Core logic for creating objects
5
+ module DSL
6
+ def define_component_store_method(receiver, title)
7
+ mod = self
8
+ receiver.define_singleton_method(mod.store_method_name(title)) do |klass|
9
+ base_class = public_send(:"#{mod.components_storage_name}")[title]
10
+ mother_ship_check_inheritance!(klass, base_class)
11
+ send(mod.simple_store_method_name(title), klass)
12
+ end
13
+ end
14
+
15
+ def define_component_simple_store_method(receiver, title)
16
+ mod = self
17
+ receiver.define_singleton_method(mod.simple_store_method_name(title)) do |klass|
18
+ send(:"write_#{mod.component_class_reader(title)}", klass)
19
+ public_send(:"#{mod.components_storage_name}")[title] = klass
20
+ end
21
+ receiver.private_class_method mod.simple_store_method_name(title)
22
+ end
23
+
24
+ def define_component_activation_method
25
+ mod = self
26
+ define_method(mod.activation_method_name) do |title, base_class, klass, init = nil, &block|
27
+ raise(ArgumentError, 'please provide a block or class') if klass.nil? && block.nil?
28
+
29
+ mother_ship_check_inheritance!(klass, base_class)
30
+
31
+ target_class = klass || base_class
32
+
33
+ patched_class = mother_ship_patch_class(target_class, &block)
34
+ mother_ship_define_init(patched_class, &init)
35
+ public_send(mod.store_method_name(title), patched_class)
36
+ end
37
+ end
38
+
39
+ def define_component_new_instance_method(title)
40
+ mod = self
41
+ define_method mod.new_instance_method_name(title) do |*args|
42
+ klass = public_send(mod.component_class_reader(title))
43
+ klass.__ms_init__(klass, *args)
44
+ end
45
+ end
46
+
47
+ def define_component_configure_method(title)
48
+ mod = self
49
+ define_method mod.configure_component_method_name(title) do |klass = nil, init: nil, &block|
50
+ base_class = public_send(:"#{mod.component_class_reader(title)}")
51
+ public_send(mod.activation_method_name, title, base_class, klass, init, &block)
52
+ end
53
+ private mod.configure_component_method_name(title)
54
+ end
55
+
56
+ def define_component_adding_method
57
+ mod = self
58
+ define_method(component_name) do |title, base_class: nil, init: nil|
59
+ singleton_class.class_eval do
60
+ reader_name = mod.component_class_reader(title)
61
+ attr_accessor reader_name
62
+ alias_method :"write_#{reader_name}", :"#{reader_name}="
63
+ private :"write_#{reader_name}"
64
+ end
65
+ klass = base_class || mod.default_base_class || Class.new(Object)
66
+ mother_ship_define_init(klass, &(init || mod.default_init))
67
+ mod.define_component_store_method(self, title)
68
+ mod.define_component_simple_store_method(self, title)
69
+ send(mod.simple_store_method_name(title), klass)
70
+ mod.define_component_configure_method(title)
71
+ mod.define_component_new_instance_method(title)
72
+ end
73
+ end
74
+
75
+ def define_components_registry
76
+ mod = self
77
+ module_eval <<-METHOD, __FILE__, __LINE__ + 1
78
+ def #{mod.components_storage_name} # def parts
79
+ @#{mod.components_storage_name} ||= {} # @parts ||= {}
80
+ end # end
81
+ METHOD
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hospodar
4
+ # Singleton that collects generted modudes
5
+ module Factories
6
+ class << self
7
+ def add_module(components_name, **attrs)
8
+ registry.resolve(components_name, **attrs)
9
+ end
10
+
11
+ def memoized_modules
12
+ registry.registered_modules
13
+ end
14
+
15
+ private
16
+
17
+ def build_module
18
+ lambda do |components_name, **attrs|
19
+ ModuleBuilder.call(components_name, **attrs)
20
+ end
21
+ end
22
+
23
+ def registry
24
+ @registry ||= Registry.new(on_missing_key: build_module)
25
+ end
26
+ end
27
+ end
28
+ end