hospodar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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