universe_compiler 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
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