drymm 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +38 -0
- data/drymm.gemspec +41 -0
- data/lib/dry/logic/builder/context_predicate_name.rb +41 -0
- data/lib/dry/types/printer/visit_sum_constructors.rb +10 -0
- data/lib/drymm/container.rb +28 -0
- data/lib/drymm/inflector.rb +22 -0
- data/lib/drymm/repos/fn_repo.rb +20 -0
- data/lib/drymm/repos/rules_repo.rb +24 -0
- data/lib/drymm/repos/types_repo.rb +60 -0
- data/lib/drymm/shapes/ast_methods.rb +60 -0
- data/lib/drymm/shapes/atomic_type.rb +41 -0
- data/lib/drymm/shapes/branch.rb +55 -0
- data/lib/drymm/shapes/const.rb +70 -0
- data/lib/drymm/shapes/fn.rb +65 -0
- data/lib/drymm/shapes/json_methods.rb +31 -0
- data/lib/drymm/shapes/logic.rb +89 -0
- data/lib/drymm/shapes/node.rb +69 -0
- data/lib/drymm/shapes/sum_enclosure.rb +41 -0
- data/lib/drymm/shapes/summarize.rb +22 -0
- data/lib/drymm/shapes/types.rb +145 -0
- data/lib/drymm/shapes.rb +31 -0
- data/lib/drymm/version.rb +5 -0
- data/lib/drymm.rb +92 -0
- metadata +253 -0
@@ -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
|
data/lib/drymm/shapes.rb
ADDED
@@ -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
|
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
|