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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d8f12bd0894d16b195930e4bea3fa4e752483cb2c92f942b90318999951f0ff9
|
4
|
+
data.tar.gz: c50dc6947861850fcaa539690209b559dd74458f9d3a3dfe37e8e43ba4a39fea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 66f843c85fc7bed6d84c1fc333f3b7c9a1168a806ba589c8cf985d99b543522d9fb568ded67efa28375e5e1a393f263542db7f05a8a54f0fe9e744047d69cbc0
|
7
|
+
data.tar.gz: 573fa9341ef612207a5c97dbd06d5f8c116b9c438865b92f0d50ff05ac8f63f129566f12bd128dbf1490bc376fd221f185f240f8ece13bae9ab215d734008c8a
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Drym¹m² is for (¹)meta (²)mapping
|
2
|
+
|
3
|
+
Drymm represents entities provided by `Dry::Logic` & `Dry::Types` as a `Dry::Struct` classes under a `Drymm::Shapes` namespace.
|
4
|
+
|
5
|
+
The core feature of `Drymm::Shapes` is an ability to cast an AST produced by that entities and structurize it for the following serialization. Also it provides an interface to load serialized data and compile it back the Type or Logic entity.
|
6
|
+
|
7
|
+
The casts perform by declaring expecting shapes under a specific `Drymm::Shapes::Branch` without any conditional code but with a significant amount of recursion. Shapes composed into `Dry::Struct::Sum` and handled by `Concurrent::AtomicReference` which are in front of the casting behaviour.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
12
|
+
|
13
|
+
$ bundle add drymm
|
14
|
+
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
|
+
|
17
|
+
$ gem install drymm
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
|
22
|
+
## Development
|
23
|
+
|
24
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
25
|
+
|
26
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/estum/drymm. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/estum/drymm/blob/main/CODE_OF_CONDUCT.md).
|
31
|
+
|
32
|
+
## License
|
33
|
+
|
34
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
35
|
+
|
36
|
+
## Code of Conduct
|
37
|
+
|
38
|
+
Everyone interacting in the Drymm project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/estum/drymm/blob/main/CODE_OF_CONDUCT.md).
|
data/drymm.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/drymm/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "drymm"
|
7
|
+
spec.authors = ["estum"]
|
8
|
+
spec.email = ["anton.estum@gmail.com"]
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.version = Drymm::VERSION
|
11
|
+
|
12
|
+
spec.summary = "Universal meta mapper for dry-logic & dry-types."
|
13
|
+
spec.description = "Drymm maps entities from Dry::Logic & Dry::Types into structs for a serialization purpose."
|
14
|
+
spec.homepage = "https://github.com/estum/drymm"
|
15
|
+
spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "drymm.gemspec", "lib/**/*"]
|
16
|
+
spec.bindir = "bin"
|
17
|
+
spec.executables = []
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
22
|
+
spec.metadata["source_code_uri"] = "https://github.com/estum/drymm"
|
23
|
+
spec.metadata["changelog_uri"] = "https://github.com/estum/drymm/blob/main/CHANGELOG.md"
|
24
|
+
|
25
|
+
spec.required_ruby_version = ">= 2.6.0"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "concurrent-ruby"
|
28
|
+
spec.add_runtime_dependency "concurrent-ruby-ext"
|
29
|
+
spec.add_runtime_dependency "dry-core"
|
30
|
+
spec.add_runtime_dependency "dry-inflector"
|
31
|
+
spec.add_runtime_dependency "dry-logic"
|
32
|
+
spec.add_runtime_dependency "dry-monads"
|
33
|
+
spec.add_runtime_dependency "dry-struct"
|
34
|
+
spec.add_runtime_dependency "dry-types"
|
35
|
+
spec.add_runtime_dependency "dry-types-tuple", ">= 0.1.4"
|
36
|
+
spec.add_runtime_dependency "zeitwerk"
|
37
|
+
|
38
|
+
spec.add_development_dependency "bundler"
|
39
|
+
spec.add_development_dependency "rake"
|
40
|
+
spec.add_development_dependency "rspec"
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Logic
|
5
|
+
module Builder
|
6
|
+
# @api private
|
7
|
+
class Context
|
8
|
+
# Defines custom predicate
|
9
|
+
#
|
10
|
+
# @name [Symbol] Name of predicate
|
11
|
+
# @Context [Proc]
|
12
|
+
def predicate(name, context = nil, &block)
|
13
|
+
singleton_class.undef_method(name) if singleton_class.method_defined?(name)
|
14
|
+
|
15
|
+
predicate = Rule::Predicate.new(context || block)
|
16
|
+
|
17
|
+
define_singleton_method(name) do |*args|
|
18
|
+
predicate.curry(*args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Defines methods for operations and predicates
|
23
|
+
def initialize
|
24
|
+
Operations.constants(false).each do |name|
|
25
|
+
next if Dry::Logic::Builder::IGNORED_OPERATIONS.include?(name)
|
26
|
+
|
27
|
+
operation = Operations.const_get(name)
|
28
|
+
|
29
|
+
define_singleton_method(name.downcase) do |*args, **kwargs, &block|
|
30
|
+
operation.new(*call(&block), *args, **kwargs)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Predicates::Methods.instance_methods(false).each do |name|
|
35
|
+
predicate(name, Predicates[name]) unless Dry::Logic::Builder::IGNORED_PREDICATES.include?(name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/version"
|
4
|
+
|
5
|
+
module Drymm
|
6
|
+
# @api private
|
7
|
+
module Container
|
8
|
+
if Dry::Core::VERSION >= "1.0.0"
|
9
|
+
include Dry::Core::Container::Mixin
|
10
|
+
else
|
11
|
+
require "dry/container"
|
12
|
+
include Dry::Container::Mixin
|
13
|
+
end
|
14
|
+
|
15
|
+
# @private
|
16
|
+
def self.extended(base)
|
17
|
+
Dry::Core::Container::Mixin.extended(base)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def alias_item(new_name, old_name)
|
23
|
+
register new_name, memoize: true do
|
24
|
+
resolve(old_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/inflector"
|
4
|
+
|
5
|
+
module Drymm
|
6
|
+
# @api private
|
7
|
+
class Inflector < Dry::Inflector
|
8
|
+
def initialize(root_file)
|
9
|
+
super() do |inflections|
|
10
|
+
inflections.acronym "AST"
|
11
|
+
yield(inflections) if block_given?
|
12
|
+
end
|
13
|
+
namespace = File.basename(root_file, ".rb")
|
14
|
+
lib_dir = File.dirname(root_file)
|
15
|
+
@version_file = File.join(lib_dir, namespace, "version.rb")
|
16
|
+
end
|
17
|
+
|
18
|
+
def camelize(basename, abspath)
|
19
|
+
abspath == @version_file ? "VERSION" : super(basename)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
# @api private
|
5
|
+
module FnRepo
|
6
|
+
extend Container
|
7
|
+
|
8
|
+
register :ary_wrap do |input|
|
9
|
+
Drymm["rules.ary"][input] ? input : [input]
|
10
|
+
end
|
11
|
+
|
12
|
+
register :logic_builder do |fn|
|
13
|
+
Dry::Logic::Builder.call(&fn)
|
14
|
+
end
|
15
|
+
|
16
|
+
register :type_identifier do |name|
|
17
|
+
Drymm.inflector.underscore(Drymm.inflector.demodulize(name)).to_sym
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
# @api private
|
5
|
+
module RulesRepo
|
6
|
+
extend Container
|
7
|
+
|
8
|
+
register :const,
|
9
|
+
proc { key(name: 0) { case?(Drymm["types.const"]) } },
|
10
|
+
call: false
|
11
|
+
|
12
|
+
register :ary,
|
13
|
+
proc { array? },
|
14
|
+
call: false
|
15
|
+
|
16
|
+
register :node,
|
17
|
+
proc { array? & min_size?(1) & key(name: 0) { negation { array? } } },
|
18
|
+
call: false
|
19
|
+
|
20
|
+
register :as_ast,
|
21
|
+
proc { respond_to?(:to_ast) },
|
22
|
+
call: false
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
# @api private
|
5
|
+
module TypesRepo
|
6
|
+
extend Container
|
7
|
+
|
8
|
+
register :any, Dry::Types["any"]
|
9
|
+
|
10
|
+
register :ary, Dry::Types["array"]
|
11
|
+
|
12
|
+
register :bool, Dry::Types["bool"]
|
13
|
+
|
14
|
+
register :hash, Dry::Types["hash"]
|
15
|
+
|
16
|
+
register :opts, (Dry::Types["hash"].default { Drymm::EMPTY_OPTS })
|
17
|
+
alias_item :meta, :opts
|
18
|
+
|
19
|
+
register :sym, Dry::Types["coercible.symbol"]
|
20
|
+
|
21
|
+
register :str, Dry::Types["coercible.string"]
|
22
|
+
|
23
|
+
T = method def self.t(member)
|
24
|
+
return member if !member.is_a?(String) && !member.is_a?(Symbol)
|
25
|
+
|
26
|
+
resolve(member) { member }
|
27
|
+
end
|
28
|
+
|
29
|
+
Array = method def self.array(member)
|
30
|
+
t(:ary_wrap).of t(member)
|
31
|
+
end
|
32
|
+
|
33
|
+
Tuple = method def self.tuple(*members)
|
34
|
+
Dry::Types::Tuple.build_unsplat(members)
|
35
|
+
end
|
36
|
+
|
37
|
+
Constrained = method def self.constrained(member, rule)
|
38
|
+
Dry::Types::Constrained.new(t(member), rule: rule)
|
39
|
+
end
|
40
|
+
|
41
|
+
register "sum.types", proc { Shapes::Types.sum }, memoize: false
|
42
|
+
|
43
|
+
register "sum.rules", proc { Shapes::Logic.sum }, memoize: false
|
44
|
+
|
45
|
+
register "variadic.any", proc { Array[:any] }, memoize: true
|
46
|
+
|
47
|
+
register "variadic.types", proc { Array["sum.types"] }, memoize: false
|
48
|
+
|
49
|
+
register "variadic.rules", proc { Array["sum.rules"] }, memoize: false
|
50
|
+
|
51
|
+
register :fn, proc { Shapes::Fn.sum }, memoize: false
|
52
|
+
|
53
|
+
register :const, proc { Shapes::Const }, memoize: false
|
54
|
+
alias_item :class, :const
|
55
|
+
|
56
|
+
register :ast, proc { constrained(:any, Drymm["rules.as_ast"]) }, memoize: true
|
57
|
+
|
58
|
+
register :ary_wrap, proc { T[:ary] << Drymm["fn.ary_wrap"] }, memoize: true
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
module Shapes
|
5
|
+
# AST-related methods mixin
|
6
|
+
module ASTMethods
|
7
|
+
extend Mix
|
8
|
+
|
9
|
+
# Fold data back into the plain AST
|
10
|
+
# @return [Array]
|
11
|
+
def to_ast
|
12
|
+
type, *node = attributes.values_at(*self.class.keys_order)
|
13
|
+
node = recursive_ast(node)
|
14
|
+
node = node[0] if node.size == 1
|
15
|
+
[type, node]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compile an instance back to original object
|
19
|
+
# @see ClassMethods#compiler
|
20
|
+
# @see ClassMethods#compiler_registry
|
21
|
+
# @return [Object]
|
22
|
+
def compile
|
23
|
+
self.class.compiler.call([to_ast])[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def recursive_ast(node)
|
29
|
+
case node
|
30
|
+
when Array
|
31
|
+
node.map { |item| recursive_ast(item) }
|
32
|
+
when Hash
|
33
|
+
node.transform_values { |item| recursive_ast(item) }
|
34
|
+
when Drymm["types.ast"]
|
35
|
+
node.to_ast
|
36
|
+
else
|
37
|
+
node
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
module ClassMethods
|
43
|
+
# @abstract
|
44
|
+
# Should be overriden in subclasses to return specific compiler
|
45
|
+
# @param registry [container]
|
46
|
+
# @return [#call]
|
47
|
+
def compiler(registry = compiler_registry())
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
|
51
|
+
# @abstract
|
52
|
+
# Should be overriden in subclasses to return specific registry
|
53
|
+
# @return [container]
|
54
|
+
def compiler_registry
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
module Shapes
|
5
|
+
# @api private
|
6
|
+
class AtomicType < Concurrent::AtomicReference
|
7
|
+
include Dry::Types::Type
|
8
|
+
include Dry::Types::Builder
|
9
|
+
include Dry::Types::Decorator
|
10
|
+
include Dry::Equalizer(:type, inspect: false, immutable: false)
|
11
|
+
|
12
|
+
def initialize(initial_type = Drymm::Undefined)
|
13
|
+
Drymm::Undefined.map(initial_type) do
|
14
|
+
set(initial_type)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
alias type get
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
Dry::Types::PRINTER.(get) { get.to_s }
|
22
|
+
end
|
23
|
+
|
24
|
+
alias inspect to_s
|
25
|
+
|
26
|
+
def respond_to_missing?(method_name, include_private = true)
|
27
|
+
get.respond_to?(method_name, include_private) || super
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(method_name, *args, **opts, &blk)
|
31
|
+
value = get
|
32
|
+
|
33
|
+
if value.respond_to?(method_name, true)
|
34
|
+
value.send(method_name, *args, **opts, &blk)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
module Shapes
|
5
|
+
# {Drymm::Shapes}'s class interface mixin, designed to extend
|
6
|
+
# an abstract __subclass__ of {Drymm::Shapes::Node}, creating a kind of
|
7
|
+
# Shape's branch or, in other words, derivative, to enclose specific namespace.
|
8
|
+
module Branch
|
9
|
+
# @api private
|
10
|
+
# Defines {sum} class attribute when extended.
|
11
|
+
private_class_method def self.extended(base)
|
12
|
+
base.defines :tuple_right
|
13
|
+
base.tuple_right true
|
14
|
+
base.private_class_method :auto_tuple
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# Flattens the 1st level of the input array.
|
19
|
+
def coerce_tuple(input)
|
20
|
+
super(input.flatten(1))
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def auto_tuple(*keys)
|
25
|
+
keys_order(keys_order | keys)
|
26
|
+
index = schema.keys.map { |t| [t.name, t.type] }.to_h
|
27
|
+
key_type, *node_types = index.values_at(*keys_order)
|
28
|
+
if tuple_right
|
29
|
+
tuple Tuple(key_type, Tuple(*node_types))
|
30
|
+
else
|
31
|
+
tuple Tuple(key_type, *node_types)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def retuple
|
36
|
+
auto_tuple!(*keys_order)
|
37
|
+
end
|
38
|
+
|
39
|
+
def auto_tuple!(*keys)
|
40
|
+
remove_instance_variable(:@keys_order) if instance_variable_defined?(:@keys_order)
|
41
|
+
remove_instance_variable(:@tuple) if instance_variable_defined?(:@tuple)
|
42
|
+
keys_order []
|
43
|
+
auto_tuple(*keys)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Shorthand method to build a tuple
|
49
|
+
# @return [Dry::Types::Tuple]
|
50
|
+
def Tuple(*args)
|
51
|
+
Drymm::TypesRepo.tuple(*args)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
module Shapes
|
5
|
+
# The intermediary shape class to handle bidirectional
|
6
|
+
# serialization of classes & modules, referenced in the node's AST.
|
7
|
+
#
|
8
|
+
# @example Wraps
|
9
|
+
# Drymm::Shapes::Const[Dry]
|
10
|
+
# # => #<Drymm::Shapes::Const name='Dry'>
|
11
|
+
#
|
12
|
+
# Drymm::Shapes::Const[name: 'Dry'].to_literal
|
13
|
+
# # => Dry
|
14
|
+
class Const < Node
|
15
|
+
attribute :type, (type_identifier(:const).default { :const })
|
16
|
+
attribute :name, Drymm["types.str"]
|
17
|
+
|
18
|
+
include Dry.Equalizer(:name, immutable: true)
|
19
|
+
|
20
|
+
# Constantize by name
|
21
|
+
# @return [Class, Module]
|
22
|
+
def to_literal
|
23
|
+
::Object.const_get(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
alias to_ast to_literal
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# @!method call(input, &block)
|
30
|
+
# @overload call(class_or_module_literal)
|
31
|
+
# @param class_or_module_literal [Class, Module]
|
32
|
+
# @overload call(hash)
|
33
|
+
# @param hash [Hash { :type => :const, :name => String }]
|
34
|
+
# @return [self]
|
35
|
+
|
36
|
+
# @return [Class(Module)]
|
37
|
+
def primitive
|
38
|
+
::Module
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param input [Any]
|
42
|
+
# @return [true] if the input is an instance of {Module} class.
|
43
|
+
def primitive?(input)
|
44
|
+
input.is_a?(primitive)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def call_unsafe(input)
|
49
|
+
return super unless primitive?(input)
|
50
|
+
|
51
|
+
super(name: input)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def call_safe(input, &block)
|
56
|
+
return super unless primitive?(input)
|
57
|
+
|
58
|
+
super(name: input, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def try(input, &block)
|
63
|
+
return super unless primitive?(input)
|
64
|
+
|
65
|
+
super(name: input, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Drymm
|
4
|
+
module Shapes
|
5
|
+
# Namespace of function shapes, i.e. procs or methods, used by {Dry::Types}
|
6
|
+
module Fn
|
7
|
+
extend SumEnclosure
|
8
|
+
|
9
|
+
# @abstract
|
10
|
+
# Abstract node branch class for shapes of {Dry::Types::Constructor::Function}
|
11
|
+
class FnNode < Node
|
12
|
+
abstract
|
13
|
+
extend Summarize
|
14
|
+
|
15
|
+
attribute :type, type_enum(:id, :callable, :method)
|
16
|
+
|
17
|
+
def self.namespace
|
18
|
+
Fn
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [::Dry::Logic::Predicates]
|
22
|
+
def self.compiler_registry
|
23
|
+
::Dry::Types.container
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [::Dry::Logic::RuleCompiler]
|
27
|
+
def self.compiler(registry = compiler_registry())
|
28
|
+
::Dry::Types::Compiler.new(registry)
|
29
|
+
end
|
30
|
+
|
31
|
+
def compile
|
32
|
+
self.class.compiler.compile_fn(to_ast)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
sum.set FnNode
|
37
|
+
|
38
|
+
# Represents functions, stored in {Dry::Types::FnContainer}
|
39
|
+
class ID < FnNode
|
40
|
+
attribute :type, type_identifier(:id)
|
41
|
+
attribute :id, Drymm["types.str"]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Represent callable objects but not an internal procs
|
45
|
+
class Callable < FnNode
|
46
|
+
attribute :type, type_identifier(:callable)
|
47
|
+
attribute :callable, Drymm["types.any"]
|
48
|
+
|
49
|
+
# @!attribute callable [r]
|
50
|
+
# @return [#call]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Represents a method calls
|
54
|
+
class Method < FnNode
|
55
|
+
attribute :type, type_identifier(:method)
|
56
|
+
attribute :target, Drymm["types.const"] | Drymm["types.any"]
|
57
|
+
attribute :name, Drymm["types.sym"]
|
58
|
+
|
59
|
+
def to_ast
|
60
|
+
super.flatten(1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Drymm
|
6
|
+
module Shapes
|
7
|
+
# JSON serialization methods mixin.
|
8
|
+
module JSONMethods
|
9
|
+
extend Mix
|
10
|
+
|
11
|
+
# Dumps the instance to a JSON string.
|
12
|
+
# @param pretty [Hash]
|
13
|
+
# @return [String]
|
14
|
+
def to_json(pretty: nil)
|
15
|
+
if pretty
|
16
|
+
JSON.pretty_generate(to_hash, pretty)
|
17
|
+
else
|
18
|
+
JSON.fast_generate(to_hash)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
module ClassMethods
|
24
|
+
# Parse JSON to a shape node
|
25
|
+
def from_json(payload)
|
26
|
+
call(JSON.parse(payload, symbolize_names: true))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|