drymm 0.2.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.
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ module Shapes
5
+ # # Shapes namespace for Dry::Logic
6
+ module Logic
7
+ extend SumEnclosure
8
+
9
+ # @abstract
10
+ # Abstract node branch class for shapes of {Dry::Logic}
11
+ class Rule < Node
12
+ abstract
13
+ extend Branch
14
+ extend Summarize
15
+
16
+ def self.namespace
17
+ Logic
18
+ end
19
+
20
+ # @return [::Dry::Logic::Predicates]
21
+ def self.compiler_registry
22
+ ::Dry::Logic::Predicates
23
+ end
24
+
25
+ # @return [::Dry::Logic::RuleCompiler]
26
+ def self.compiler(registry = compiler_registry())
27
+ ::Dry::Logic::RuleCompiler.new(registry)
28
+ end
29
+ end
30
+
31
+ sum.set Rule
32
+
33
+ # @see Dry::Logic::Predicate::Rule
34
+ class Predicate < Rule
35
+ attribute :type, type_identifier
36
+ attribute :name, Drymm["types.sym"]
37
+ attribute :args, Drymm["types.variadic.any"]
38
+ end
39
+
40
+ # @see Dry::Logic::Operations::Check
41
+ class Check < Rule
42
+ attribute :type, type_identifier
43
+ attribute :keys, Drymm["types.variadic.any"]
44
+ attribute :rule, Drymm["types.sum.rules"]
45
+ end
46
+
47
+ # @see Dry::Logic::Operations::Unary
48
+ class Unary < Rule
49
+ tuple_right false
50
+ attribute :type, type_enum(:each, :not, :negation)
51
+ attribute :rule, Drymm["types.sum.rules"]
52
+
53
+ def self.coerce_tuple((type, rule))
54
+ { type: type, rule: rule }
55
+ end
56
+ end
57
+
58
+ # @see Dry::Logic::Operations::Attr
59
+ # @see Dry::Logic::Operations::Key
60
+ class RoutedUnary < Rule
61
+ attribute :type, type_enum(:attr, :key)
62
+ attribute :path, Drymm["types.any"]
63
+ attribute :rule, Drymm["types.sum.rules"]
64
+ end
65
+
66
+ # @see Dry::Logic::Operations::Binary
67
+ class Grouping < Rule
68
+ tuple_right false
69
+ attribute :type, type_enum(:and, :or, :xor, :set)
70
+ attribute :rules, Drymm["types.variadic.rules"]
71
+
72
+ def to_ast
73
+ [type, rules.map(&:to_ast)]
74
+ end
75
+
76
+ def self.coerce_tuple((type, rules))
77
+ { type: type, rules: rules }
78
+ end
79
+ end
80
+
81
+ # @see Dry::Logic::Operations::Implication
82
+ class BinaryComposition < Rule
83
+ attribute :type, type_enum(:implication)
84
+ attribute :left, Drymm["types.sum.rules"]
85
+ attribute :right, Drymm["types.sum.rules"]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ module Shapes
5
+ # @abstract
6
+ # {Dry::Struct} abstract subclass, extended with {::Dry::Tuple::StructClassInterface}
7
+ class Node < Dry::Struct
8
+ abstract
9
+
10
+ extend Dry::Tuple::StructClassInterface
11
+ include Dry::Core::Constants
12
+ include JSONMethods
13
+ include ASTMethods
14
+
15
+ class << self
16
+ # @overload type_identifier(klass)
17
+ # Generates identifier from the given class name
18
+ # @param klass [Class]
19
+ # @overload type_identifier(name)
20
+ # Coerces the given name to symbol and uses it as a node type identifier
21
+ # @param name [#to_sym]
22
+ # @overload type_identifier(list)
23
+ # Builds {Dry::Types::Enum} from the given list of values
24
+ # @param list [Array]
25
+ # @return [Dry::Types::Type]
26
+ # coercible symbol type with specific value — node type identifier
27
+ # (declared, for example, as `type` in {Drymm::Shape::LogicNode.inherited})
28
+ # @note
29
+ # To hook the assigned type identifier for specific subclass, just override
30
+ # this method.
31
+ # @example Hook the type value
32
+ # class Something < Drymm::S::Node
33
+ # def self.type_identifier(*)
34
+ # super(:hooked)
35
+ # end
36
+ # end
37
+ def type_identifier(input = self)
38
+ case input
39
+ when ::String, ::Symbol
40
+ Drymm["types.sym"].constrained(eql: input.to_sym)
41
+ when ::Array
42
+ Drymm["types.sym"].enum(*input)
43
+ when ::Module
44
+ type_identifier(Drymm["fn.type_identifier"][input.name])
45
+ else
46
+ raise ArgumentError
47
+ end
48
+ end
49
+
50
+ # Shorthand to declare type as a enum
51
+ # @param input [Array<Symbol>]
52
+ # @return [Dry::Types[Enum<Symbol>]]
53
+ def type_enum(*input)
54
+ type_identifier(input)
55
+ end
56
+
57
+ # @api private
58
+ # Invokes Dry::Tuple::StructClassInterface#auto_tuple each time an
59
+ # attribute is declared.
60
+ def define_accessors(keys)
61
+ super
62
+ auto_tuple(*keys)
63
+ end
64
+ end
65
+
66
+ private_class_method :define_accessors
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ module Shapes
5
+ # @api private
6
+ module SumEnclosure
7
+ include Dry::Types::Type
8
+ include Dry::Types::Meta
9
+
10
+ # @api private
11
+ def sum
12
+ @sum ||= AtomicType.new
13
+ end
14
+
15
+ # @!method call(input, &block)
16
+ # @overload call(type: :id, id:)
17
+ # @param id [Symbol, String]
18
+ # @overload call(type: :callable, callable:)
19
+ # @param callable [#call]
20
+ # @overload call(type: :method, target:, name:)
21
+ # @param target [#(name)] namespace
22
+ # @param name [Symbol, String]
23
+ # @return [self]
24
+
25
+ # @api private
26
+ def call_unsafe(input)
27
+ @sum.call(input)
28
+ end
29
+
30
+ # @api private
31
+ def call_safe(input, &block)
32
+ @sum.call(input, &block)
33
+ end
34
+
35
+ # @api private
36
+ def try(input, &block)
37
+ @sum.try(input, &block)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ module Shapes
5
+ # @api private
6
+ module Summarize
7
+ private
8
+
9
+ # @api private
10
+ def inherited(subclass)
11
+ super
12
+ namespace.sum.try_update! do |current|
13
+ if current == abstract_class
14
+ subclass
15
+ else
16
+ current | subclass
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ module Shapes
5
+ # # Shapes namespace for Dry::Types
6
+ module Types
7
+ extend SumEnclosure
8
+
9
+ # @abstract
10
+ # Abstract node branch class for shapes of {Dry::Types}
11
+ class Type < Node
12
+ abstract
13
+ extend Branch
14
+ extend Summarize
15
+
16
+ def self.namespace
17
+ Types
18
+ end
19
+
20
+ # @return [::Dry::Logic::Predicates]
21
+ def self.compiler_registry
22
+ ::Dry::Types
23
+ end
24
+
25
+ # @return [::Dry::Logic::RuleCompiler]
26
+ def self.compiler(registry = compiler_registry())
27
+ ::Dry::Types::Compiler.new(registry)
28
+ end
29
+
30
+ def compile
31
+ self.class.compiler.call(to_ast)
32
+ end
33
+ end
34
+
35
+ sum.set Type
36
+
37
+ # @see Dry::Types::Any
38
+ class Any < Type
39
+ tuple_right false
40
+ attribute :type, type_identifier
41
+ attribute :meta, Drymm["types.meta"]
42
+ end
43
+
44
+ # @see Dry::Types::Array
45
+ class Array < Type
46
+ attribute :type, type_identifier
47
+ attribute :member, Drymm["types.sum.types"]
48
+ attribute :meta, Drymm["types.meta"]
49
+ end
50
+
51
+ # @see Dry::Types::JSON
52
+ # @see Dry::Types::Params
53
+ class CoercibleArray < Type
54
+ attribute :type, type_enum(:json_array, :params_array)
55
+ attribute :member, Drymm["types.sum.types"]
56
+ end
57
+
58
+ # @see Dry::Types::JSON
59
+ # @see Dry::Types::Params
60
+ class CoercibleHash < Type
61
+ attribute :type, type_enum(:json_hash, :params_hash)
62
+ attribute :keys, Drymm["types.variadic.types"]
63
+ attribute :meta, Drymm["types.meta"]
64
+ end
65
+
66
+ # @see Dry::Types::Composition
67
+ class Composition < Type
68
+ attribute :type, type_enum(:implication, :intersection, :transition, :sum)
69
+ attribute :left, Drymm["types.sum.types"]
70
+ attribute :right, Drymm["types.sum.types"]
71
+ attribute :meta, Drymm["types.meta"]
72
+ end
73
+
74
+ # @see Dry::Types::Constrained
75
+ class Constrained < Type
76
+ attribute :type, type_identifier
77
+ attribute :base, Drymm["types.sum.types"]
78
+ attribute :rule, Drymm["types.sum.rules"]
79
+ end
80
+
81
+ # @see Dry::Types::Constructor
82
+ class Constructor < Type
83
+ attribute :type, type_identifier
84
+ attribute :base, Drymm["types.sum.types"]
85
+ attribute :fn, Drymm["types.fn"]
86
+ end
87
+
88
+ # @see Dry::Types::Enum
89
+ class Enum < Type
90
+ attribute :type, type_identifier
91
+ attribute :base, Drymm["types.sum.types"]
92
+ attribute :mapping, Drymm["types.hash"]
93
+ end
94
+
95
+ # @see Dry::Types::Hash
96
+ class Hash < Type
97
+ attribute :type, type_identifier
98
+ attribute :options, Drymm["types.opts"]
99
+ attribute :meta, Drymm["types.meta"]
100
+ end
101
+
102
+ # @see Dry::Types::Schema::Key
103
+ class Key < Type
104
+ attribute :type, type_identifier
105
+ attribute :name, Drymm["types.any"]
106
+ attribute :required, Drymm["types.bool"]
107
+ attribute :base, Drymm["types.sum.types"]
108
+ end
109
+
110
+ # @see Dry::Types::Lax
111
+ class Lax < Type
112
+ tuple_right false
113
+ attribute :type, type_identifier
114
+ attribute :base, Drymm["types.sum.types"]
115
+
116
+ def self.coerce_tuple((type, base))
117
+ { type: type, base: base }
118
+ end
119
+ end
120
+
121
+ # @see Dry::Types::Map
122
+ class Map < Type
123
+ attribute :type, type_identifier
124
+ attribute :key_type, Drymm["types.sum.types"]
125
+ attribute :value_type, Drymm["types.sum.types"]
126
+ attribute :meta, Drymm["types.meta"]
127
+ end
128
+
129
+ # @see Dry::Types::Nominal
130
+ class Nominal < Type
131
+ attribute :type, type_identifier
132
+ attribute :base, Drymm["types.const"]
133
+ attribute :meta, Drymm["types.meta"]
134
+ end
135
+
136
+ # @see Dry::Types::Schema
137
+ class Schema < Type
138
+ attribute :type, type_identifier
139
+ attribute :keys, Drymm["types.variadic.types"]
140
+ attribute :options, Drymm["types.opts"]
141
+ attribute :meta, Drymm["types.meta"]
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ # The core Shapes namespace
5
+ module Shapes
6
+ include Dry::Core::Constants
7
+
8
+ # @api private
9
+ module Mix
10
+ private
11
+
12
+ def included(base)
13
+ base.extend(const_get(:ClassMethods))
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def sum
19
+ Drymm["sum"]
20
+ end
21
+
22
+ def call(input)
23
+ sum[input]
24
+ end
25
+
26
+ alias [] call
27
+ end
28
+
29
+ extend JSONMethods::ClassMethods
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Drymm
4
+ VERSION = "0.2.0"
5
+ end
data/lib/drymm.rb ADDED
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "drymm/version"
4
+ require "zeitwerk"
5
+ require "dry/core/constants"
6
+ require "dry/logic"
7
+ require "dry/logic/predicates"
8
+ require "dry/logic/builder"
9
+ require "dry/types"
10
+ require "dry/struct"
11
+ require "dry/monads/all"
12
+ require "dry/tuple"
13
+
14
+ # Load patches for dry-rb gems if need
15
+
16
+ unless Dry::Types::Printer.method_defined?(:visit_sum_constructors)
17
+ require_relative "dry/types/printer/visit_sum_constructors"
18
+ end
19
+
20
+ unless Dry::Logic::Builder::Context.instance_method(:predicate).parameters.include?(%i[opt context])
21
+ require_relative "dry/logic/builder/context_predicate_name"
22
+ end
23
+
24
+ require_relative "drymm/inflector"
25
+
26
+ # # Drym¹m² is for (¹)meta (²)mapping
27
+ #
28
+ # Drymm represents entities provided by {Dry::Logic} {Dry::Types}
29
+ # as a {Dry::Struct} classes under a {Drymm::Shapes} namespace.
30
+ #
31
+ # The core feature of Drymm::Shapes is an ability to cast an AST produced
32
+ # by that entities and structurize it for the following serialization (for example,
33
+ # into JSON). Also it provides an interface to load serialized data and compile it back
34
+ # to the Type or Logic entity.
35
+ #
36
+ # The casts perform by declaring expecting shapes under a specific {Drymm::Shapes::Branch}
37
+ # namespace without any conditional code but with a significant amount of recursion.
38
+ # Declared shapes composed into {Dry::Struct::Sum} which are in front of the casting behaviour.
39
+ module Drymm
40
+ class << self
41
+ # @!visibility private
42
+
43
+ def loader
44
+ @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
45
+ if $PROGRAM_NAME == "bin/console"
46
+ loader.enable_reloading
47
+ loader.log!
48
+ end
49
+ loader.tag = "drymm"
50
+ loader.inflector = Drymm::Inflector.new(__FILE__)
51
+ loader.ignore "#{__dir__}/dry"
52
+ loader.collapse "#{__dir__}/drymm/repos"
53
+ end
54
+ end
55
+
56
+ # @return [Drymm::Inflector]
57
+ def inflector
58
+ loader.inflector
59
+ end
60
+ end
61
+
62
+ loader.setup
63
+
64
+ include Dry::Core::Constants
65
+
66
+ extend Container
67
+
68
+ # @!method self.[](key)
69
+ # @param [String, Symbol] key
70
+ # @return [mixed]
71
+
72
+ # @!method self.resolve(key)
73
+ # @param [String, Symbol] key
74
+ # @return [mixed]
75
+
76
+ merge Drymm::RulesRepo, namespace: :rules
77
+ merge Drymm::FnRepo, namespace: :fn
78
+ merge Drymm::TypesRepo, namespace: :types
79
+
80
+ logic_builder = self["fn.logic_builder"]
81
+
82
+ keys.grep(/^rules/).each do |key|
83
+ decorate key, with: logic_builder
84
+ end
85
+
86
+ register :sum, memoize: true do
87
+ (
88
+ resolve("types.sum.rules") |
89
+ resolve("types.sum.types")
90
+ ).meta(recursive: "poly")
91
+ end
92
+ end