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.
- 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
|