universe_compiler 0.2.11

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +183 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +4 -0
  6. data/CODE_OF_CONDUCT.md +49 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +318 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +10 -0
  12. data/bin/setup +8 -0
  13. data/lib/universe_compiler/entity/auto_named.rb +31 -0
  14. data/lib/universe_compiler/entity/conversion.rb +66 -0
  15. data/lib/universe_compiler/entity/field_binder.rb +36 -0
  16. data/lib/universe_compiler/entity/field_constraint_management.rb +95 -0
  17. data/lib/universe_compiler/entity/field_management.rb +57 -0
  18. data/lib/universe_compiler/entity/inheritance.rb +87 -0
  19. data/lib/universe_compiler/entity/inheritance_merge_policy.rb +19 -0
  20. data/lib/universe_compiler/entity/marshalling.rb +71 -0
  21. data/lib/universe_compiler/entity/overridden.rb +31 -0
  22. data/lib/universe_compiler/entity/persistence.rb +34 -0
  23. data/lib/universe_compiler/entity/reference.rb +77 -0
  24. data/lib/universe_compiler/entity/relations_management.rb +46 -0
  25. data/lib/universe_compiler/entity/type_management.rb +43 -0
  26. data/lib/universe_compiler/entity/validation.rb +92 -0
  27. data/lib/universe_compiler/entity.rb +64 -0
  28. data/lib/universe_compiler/error.rb +15 -0
  29. data/lib/universe_compiler/override.rb +24 -0
  30. data/lib/universe_compiler/package/bootstrap.rb +46 -0
  31. data/lib/universe_compiler/package.rb +17 -0
  32. data/lib/universe_compiler/persistence/basic_yaml_engine.rb +68 -0
  33. data/lib/universe_compiler/persistence/management.rb +30 -0
  34. data/lib/universe_compiler/universe/compile.rb +45 -0
  35. data/lib/universe_compiler/universe/duplication.rb +62 -0
  36. data/lib/universe_compiler/universe/entities.rb +52 -0
  37. data/lib/universe_compiler/universe/index.rb +40 -0
  38. data/lib/universe_compiler/universe/multiverse.rb +44 -0
  39. data/lib/universe_compiler/universe/persistence.rb +23 -0
  40. data/lib/universe_compiler/universe/query.rb +45 -0
  41. data/lib/universe_compiler/universe/validation.rb +30 -0
  42. data/lib/universe_compiler/universe.rb +38 -0
  43. data/lib/universe_compiler/utils/array_utils.rb +59 -0
  44. data/lib/universe_compiler/utils/basic_logger.rb +24 -0
  45. data/lib/universe_compiler/utils/deep_traverse.rb +61 -0
  46. data/lib/universe_compiler/utils/error_propagation.rb +20 -0
  47. data/lib/universe_compiler/utils/with_unique_name.rb +75 -0
  48. data/lib/universe_compiler/version.rb +3 -0
  49. data/lib/universe_compiler.rb +60 -0
  50. data/universe_compiler.gemspec +32 -0
  51. metadata +218 -0
@@ -0,0 +1,66 @@
1
+ require 'digest'
2
+
3
+ module UniverseCompiler
4
+ module Entity
5
+
6
+ module Conversion
7
+
8
+ def as_path
9
+ to_composite_key.map(&:to_s).join '/'
10
+ end
11
+
12
+ def to_composite_key
13
+ [type, name]
14
+ end
15
+
16
+ def to_uniq_id
17
+ Digest::SHA256.hexdigest to_composite_key.join ':'
18
+ end
19
+
20
+ def inspect
21
+ msg = "#<#{self.class.name}:#{object_id} composite_key=#{to_composite_key.inspect}"
22
+ msg << ", @universe='#{universe.name}'" unless universe.nil?
23
+ msg << '>'
24
+ msg
25
+ end
26
+
27
+ def ==(another_reference)
28
+ return false unless another_reference.respond_to? :to_composite_key
29
+ to_composite_key == another_reference.to_composite_key
30
+ end
31
+
32
+ def eql?(another_reference)
33
+ self == another_reference and universe == another_reference.universe
34
+ end
35
+
36
+ def to_hash
37
+ {
38
+ self.class.name => {
39
+ type: type,
40
+ fields: dereferenced_fields
41
+ }
42
+ }
43
+ end
44
+
45
+ def to_reference
46
+ UniverseCompiler::Entity::Reference.new_instance self
47
+ end
48
+
49
+ def encode_with(coder)
50
+ dereferenced_fields.each do |key, value|
51
+ coder[key] = value
52
+ end
53
+ end
54
+
55
+ def init_with(coder)
56
+ initialize
57
+ self.fully_resolved = false
58
+ coder.map.each do |key, value|
59
+ self[key] = value
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,36 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module FieldBinder
5
+
6
+ def field_accessor(*field_names)
7
+ field_names.each do |field_name|
8
+ field_reader field_name
9
+ field_writer field_name
10
+ end
11
+ end
12
+
13
+ def field_reader(*field_names)
14
+ field_names.each do |field_name|
15
+ self.class_eval do
16
+ define_method field_name do
17
+ self.fields[field_name]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def field_writer(*field_names)
24
+ field_names.each do |field_name|
25
+ self.class_eval do
26
+ define_method "#{field_name}=" do |val|
27
+ self.fields[field_name] = val
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,95 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module FieldConstraintManagement
5
+
6
+ BOOLEAN_CONSTRAINTS = %i(not_null not_empty is_array is_hash).freeze
7
+ PARAMETRIZED_CONSTRAINTS = %i(should_match class_name).freeze
8
+
9
+ INCOMPATIBLE_CONSTRAINTS = {
10
+ has_one: %i(has_many is_array is_hash),
11
+ has_many: %i(has_one is_hash),
12
+ is_array: %i(has_one is_hash),
13
+ is_hash: %i(has_one has_many is_array)
14
+ }.freeze
15
+
16
+ def fields_constraints
17
+ @fields_constraints ||= {}
18
+ end
19
+
20
+ # Dynamically created constraint methods
21
+ BOOLEAN_CONSTRAINTS.each do |constraint_name|
22
+ self.class_eval do
23
+ define_method constraint_name do |*field_names|
24
+ field_names.each do |field_name|
25
+ define_constraint field_name, constraint_name, true
26
+ end
27
+ end
28
+ end
29
+ end
30
+ PARAMETRIZED_CONSTRAINTS.each do |constraint_name|
31
+ self.class_eval do
32
+ define_method constraint_name do |field_name, param|
33
+ define_constraint field_name, constraint_name, param
34
+ end
35
+ end
36
+ end
37
+
38
+ def field(field_name, *options)
39
+ ['', '='].each do |suffix|
40
+ method_name = '%s%s' % [field_name, suffix]
41
+ if instance_methods.include? method_name
42
+ raise UniverseCompiler::Error,
43
+ "'#{method_name}' method cannot be defined on '#{self}' as it is already defined !"
44
+ end
45
+ end
46
+
47
+ define_constraint field_name if options.empty?
48
+
49
+ options.each do |option|
50
+ case option
51
+ when Symbol || String
52
+ if BOOLEAN_CONSTRAINTS.include? option.to_sym
53
+ send option, field_name
54
+ else
55
+ raise UniverseCompiler::Error, "Unknown field option '#{option}' !"
56
+ end
57
+ when Hash
58
+ option.each do |constraint_name, value|
59
+ if PARAMETRIZED_CONSTRAINTS.include? constraint_name.to_sym
60
+ send constraint_name, field_name, value
61
+ else
62
+ raise UniverseCompiler::Error, "Unknown field option '#{constraint_name}' !"
63
+ end
64
+ end
65
+ else
66
+ raise UniverseCompiler::Error, "Invalid option for '#{field_name}': #{option.class.name} => #{option.to_s}"
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ private
73
+
74
+ def define_constraint(field_name, constraint_name = nil, value = nil)
75
+ fields_constraints[field_name] ||= {}
76
+ check_constraints_incompatibilities(field_name, constraint_name)
77
+ unless constraint_name.nil? and value.nil?
78
+ fields_constraints[field_name][constraint_name] = value
79
+ end
80
+ end
81
+
82
+ def check_constraints_incompatibilities(field_name, constraint_name)
83
+ unless INCOMPATIBLE_CONSTRAINTS[constraint_name].nil?
84
+ INCOMPATIBLE_CONSTRAINTS[constraint_name].each do |incompatible_constraint|
85
+ if fields_constraints[field_name].keys.include? incompatible_constraint
86
+ raise UniverseCompiler::Error, "Cannot set constraint '#{constraint_name}' on field '#{field_name}', incompatible with existing ones !"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,57 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module FieldManagement
5
+
6
+ attr_accessor :auto_create_fields
7
+
8
+ def method_missing(method_name, *args)
9
+ if auto_create_fields
10
+ candidate_field_name = method_name
11
+ method_name.to_s.match (/^(?<field_name>[^=]+)\s*=?$/) do |md|
12
+ candidate_field_name = md['field_name'].to_sym
13
+ end
14
+ define_field_accessor candidate_field_name
15
+ raise "Invalid method '#{method_name}' in #{self}" unless self.respond_to? method_name
16
+ self.send method_name, *args
17
+ else
18
+ super(method_name, *args)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def define_known_fields_accessors
25
+ self.class.fields_constraints.each do |field_name, constraints|
26
+ define_field_accessor field_name
27
+ if fields[field_name].nil?
28
+ fields[field_name] = [] if constraints[:has_many] || constraints[:is_array]
29
+ fields[field_name] = {} if constraints[:is_hash]
30
+ end
31
+ end
32
+ end
33
+
34
+ def define_field_accessor(field_name)
35
+ metaclass = class << self; self ; end
36
+ UniverseCompiler.logger.debug 'Defining field accessor %s on class %s (%s)' % [field_name, metaclass, self.type]
37
+ if self.respond_to? field_name or self.respond_to? "#{field_name}="
38
+ UniverseCompiler.logger.warn "Cannot define '#{field_name}' or '#{field_name}=' which already exist(s) on class '#{metaclass}' !"
39
+ else
40
+ metaclass.instance_eval do
41
+ define_method field_name do
42
+ fields[field_name]
43
+ end
44
+ end
45
+ metaclass.instance_eval do
46
+ define_method "#{field_name}=" do |value|
47
+ fields[field_name] = value
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
57
+
@@ -0,0 +1,87 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module Inheritance
5
+
6
+ extend UniverseCompiler::Entity::FieldBinder
7
+
8
+ field_accessor :extends
9
+ attr_reader :compiled
10
+
11
+ def apply_inheritance
12
+ return unless compiled.nil?
13
+ unless extends.nil?
14
+ valid_for_inheritance? raise_error: true
15
+ @fields = fields_inheritance_result
16
+ end
17
+ ensure
18
+ @compiled = true
19
+ end
20
+
21
+ def valid_for_inheritance?(raise_error: false)
22
+ is_valid = true
23
+ unless extends.nil?
24
+ is_valid = false
25
+ if extends.respond_to? :type and extends.respond_to? :name
26
+ if extends.respond_to? :fields or extends.is_a? UniverseCompiler::Entity::Reference
27
+ is_valid = self.type == extends.type
28
+ end
29
+ end
30
+ end
31
+ return true if is_valid
32
+ false_or_raise "Invalid entity '#{to_composite_key}' ! Inheritance is invalid !", raise_error: raise_error
33
+ end
34
+
35
+ private
36
+
37
+ def fields_inheritance_result
38
+ stack = inheritance_stack(self).reverse
39
+ return stack.first if stack.size == 1
40
+ merge_engine = SuperStack::Manager.new
41
+ merge_engine.merge_policy = SuperStack::MergePolicies::InheritanceMergePolicy
42
+ stack.each { |entity| merge_engine << entity.fields.dup }
43
+ merge_engine[]
44
+ end
45
+
46
+ def inheritance_stack(entity, stack=[], visited=[])
47
+ if visited.include? entity
48
+ display_as_path = display_path(*visited.map(&:to_composite_key), entity.to_composite_key)
49
+ raise UniverseCompiler::Error, "Circular reference detected for '#{to_composite_key}' (#{display_as_path}) !"
50
+ end
51
+ visited << entity
52
+ stack << entity
53
+ return stack unless entity.extends
54
+ parent = entity.extends
55
+ if parent.nil?
56
+ display_as_path = display_path(*visited.map(&:to_composite_key), entity.extends.to_composite_key)
57
+ err_msg = "Invalid inheritance defined in '#{entity.to_composite_key}' extending '#{entity.to_composite_key}' (#{display_as_path}) !"
58
+ raise UniverseCompiler::Error, err_msg
59
+ end
60
+ stack = inheritance_stack parent, stack, visited
61
+ stack
62
+ end
63
+
64
+
65
+ def inherited_fields_stack(entity, stack=[], visited=[])
66
+ if visited.include? entity
67
+ raise UniverseCompiler::Error, "Circular reference detected for '#{self.to_composite_key}' (#{display_path(*visited.map(&:to_composite_key), entity.to_composite_key)}) !"
68
+ end
69
+ visited << entity
70
+ stack << entity.fields
71
+ return stack unless entity.extends
72
+ parent = entity.extends
73
+ if parent.nil?
74
+ raise UniverseCompiler::Error, "Invalid inheritance defined in '#{entity.to_composite_key}' extending '#{entity.to_composite_key}' (#{display_path(*visited.map(&:composite_key), entity.extends.to_composite_key)}) !"
75
+ end
76
+ stack = inherited_fields_stack parent, stack, visited
77
+ stack
78
+ end
79
+
80
+ def display_path(*names)
81
+ names.join ' -> '
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,19 @@
1
+ module SuperStack
2
+ module MergePolicies
3
+
4
+ module InheritanceMergePolicy
5
+
6
+ extend UniverseCompiler::Utils::DeepTraverse
7
+ extend UniverseCompiler::Entity::Marshalling
8
+
9
+ def self.merge(h1, h2)
10
+ h1_dereferenced = dereferenced_fields h1
11
+ h2_dereferenced = dereferenced_fields h2
12
+ merged = h1_dereferenced.deep_merge! h2_dereferenced
13
+ resolve_fields_references merged
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module Marshalling
5
+
6
+
7
+ def fully_resolved?
8
+ fully_resolved || false
9
+ end
10
+
11
+ def resolve_fields_references!(from_fields = fields)
12
+ UniverseCompiler.logger.debug "Starting resolution for '#{to_composite_key}'."
13
+ @fields = resolve_fields_references from_fields
14
+ UniverseCompiler.logger.debug "Completed resolution for '#{to_composite_key}'."
15
+ self
16
+ end
17
+
18
+ def resolve_fields_references(from_fields = fields)
19
+ self.fully_resolved = true
20
+ deep_map from_fields do |leaf|
21
+ case leaf
22
+ when UniverseCompiler::Entity::Reference
23
+ res = leaf.to_entity raise_error: false
24
+ if res
25
+ res
26
+ else
27
+ self.fully_resolved = false
28
+ leaf
29
+ end
30
+ else
31
+ leaf
32
+ end
33
+ end
34
+ end
35
+
36
+ def traverse_fields(fields_to_process = fields)
37
+ deep_traverse(fields_to_process) do |leaf|
38
+ yield leaf
39
+ end
40
+ end
41
+
42
+ def dereferenced_fields(fields_to_dereference = fields)
43
+ deep_map(fields_to_dereference) do |leaf|
44
+ case leaf
45
+ when UniverseCompiler::Entity::Base
46
+ leaf.to_reference
47
+ when Symbol, Numeric, NilClass, TrueClass, FalseClass
48
+ leaf
49
+ else
50
+ leaf.clone
51
+ end
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ attr_accessor :fully_resolved
58
+
59
+ def marshal_dump
60
+ { fields: dereferenced_fields, universe: universe.name }
61
+ end
62
+
63
+ def marshal_load(data)
64
+ stored_universe = UniverseCompiler::Universe::Base.universes[data[:universe]]
65
+ initialize fields: data[:fields], universe: stored_universe
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+
@@ -0,0 +1,31 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module Overridden
5
+
6
+ def overridden_by
7
+ @overridden_by ||= []
8
+ end
9
+
10
+ def apply_override(override_fields, overrider)
11
+ merge_engine = SuperStack::Manager.new
12
+ merge_engine.merge_policy = SuperStack::MergePolicies::InheritanceMergePolicy
13
+ merge_engine << fields.to_hash
14
+ merge_engine << override_fields
15
+ add_overrider overrider
16
+ @fields = merge_engine[]
17
+ merge_engine.clear_layers
18
+ @fields
19
+ end
20
+
21
+ private
22
+
23
+ def add_overrider(overrider)
24
+ raise "This object #{to_composite_key} is already overridden by #{overrider.to_composite_key}" if overridden_by.include? overrider
25
+ overridden_by << overrider
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ require 'fileutils'
2
+
3
+ module UniverseCompiler
4
+ module Entity
5
+
6
+ module Persistence
7
+
8
+ attr_accessor :source_uri
9
+
10
+ def self.load(uri)
11
+ entity = YAML.load_file uri
12
+ entity.source_uri = uri
13
+ entity
14
+ end
15
+
16
+ def save(uri = source_uri, raise_error: true)
17
+ valid? raise_error: raise_error
18
+ FileUtils.mkpath File.dirname(uri)
19
+ File.write uri, to_yaml, mode: 'w'
20
+ self.source_uri = uri
21
+ self
22
+ end
23
+
24
+ def delete
25
+ universe.delete self
26
+ unless self.source_uri.nil?
27
+ FileUtils.rm self.source_uri
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,77 @@
1
+ require 'digest'
2
+
3
+ module UniverseCompiler
4
+ module Entity
5
+
6
+ class Reference
7
+
8
+ class << self
9
+
10
+ def references
11
+ @references ||= {}
12
+ end
13
+
14
+ def new_instance(entity = nil)
15
+ k = entity_to_cache_key entity
16
+ return references[k] if references[k]
17
+ e = new entity
18
+ references[k] = e
19
+ e
20
+ end
21
+
22
+ def entity_to_cache_key(entity_or_reference)
23
+ universe_name = entity_or_reference.universe.nil? ? '' : entity_or_reference.universe.name
24
+ universe_name ||= ''
25
+ [entity_or_reference.type, entity_or_reference.name, universe_name]
26
+ end
27
+
28
+ private :new
29
+
30
+ end
31
+
32
+ yaml_tag '!psref'
33
+
34
+ include UniverseCompiler::Utils::ErrorPropagation
35
+ include UniverseCompiler::Entity::Conversion
36
+
37
+ attr_reader :type, :name, :universe
38
+
39
+ def initialize(entity = nil)
40
+ self.entity = entity unless entity.nil?
41
+ end
42
+
43
+ def entity=(entity)
44
+ @type = entity.type
45
+ @name = entity.name
46
+ self.universe = entity.universe
47
+ end
48
+
49
+ def universe=(another_universe)
50
+ k = self.class.entity_to_cache_key self
51
+ self.class.references.delete k
52
+ @universe = another_universe
53
+ self.class.entity_to_cache_key self
54
+ end
55
+
56
+ def to_entity(raise_error: true)
57
+ false_or_raise "Cannot get entity '#{to_composite_key.inspect}' if its universe is not defined!", raise_error: raise_error if universe.nil?
58
+ entity = universe.get_entity *to_composite_key
59
+ false_or_raise "Cannot find entity '#{to_composite_key.inspect}' in the universe '#{universe}'", raise_error: raise_error if entity.nil?
60
+ entity
61
+ end
62
+
63
+ def encode_with(coder)
64
+ coder['type'] = type
65
+ coder['name'] = name
66
+ end
67
+
68
+ def init_with(coder)
69
+ initialize
70
+ @type = coder['type']
71
+ @name = coder['name']
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,46 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module UniverseCompiler
4
+ module Entity
5
+
6
+ module RelationsManagement
7
+
8
+ def has_one(entity_type, name: nil)
9
+ case entity_type
10
+ when Class
11
+ name = name.nil? ? entity_type.entity_type : name.to_sym
12
+ define_constraint name, :has_one, entity_type.entity_type
13
+ when Symbol, String
14
+ name = name.nil? ? entity_type.to_sym : name.to_sym
15
+ define_constraint name, :has_one, entity_type.to_sym
16
+ end
17
+ end
18
+
19
+ def has_many(entity_type, name: nil)
20
+ name = case entity_type
21
+ when Class
22
+ name.nil? ? entity_type.entity_type : name.to_sym
23
+ when Symbol, String
24
+ name.nil? ? entity_type.to_sym : name.to_sym
25
+ end
26
+ field_name = name.to_s.pluralize.to_sym
27
+
28
+ case entity_type
29
+ when Class
30
+ define_constraint field_name, :has_many, entity_type.entity_type
31
+ when Symbol, String
32
+ define_constraint field_name, :has_many, entity_type.to_sym
33
+ end
34
+ end
35
+
36
+ def entity_type_relations
37
+ fields_constraints.select do |_, constraints|
38
+ constraints.keys.include? :has_one or constraints.keys.include? :has_many
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+
@@ -0,0 +1,43 @@
1
+ module UniverseCompiler
2
+ module Entity
3
+
4
+ module TypeManagement
5
+
6
+ module ClassMethods
7
+
8
+ def entity_type(value = nil)
9
+ if value.nil?
10
+ @entity_type || name.underscore
11
+ else
12
+ self.entity_type = value
13
+ end
14
+ end
15
+
16
+ def entity_type=(value)
17
+ raise UniverseCompiler::Error, "You cannot change an entity type for class '#{self.name}'" unless @entity_type.nil?
18
+ raise UniverseCompiler::Error, 'Only Symbol is supported for entity_type !' unless value.is_a? Symbol
19
+ @entity_type = value
20
+ end
21
+
22
+ end
23
+
24
+ def self.valid_for_type?(entity)
25
+ entity.respond_to? :type and entity.class.respond_to? :entity_type
26
+ end
27
+
28
+ def type
29
+ self.class.entity_type
30
+ end
31
+
32
+ def self.included(base)
33
+ base.extend(ClassMethods)
34
+ end
35
+
36
+ def self.extended(base)
37
+ included base.class
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end