low_type 0.1.0 → 0.2.1
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 +4 -4
- data/lib/low_type.rb +10 -99
- data/lib/param_proxy.rb +12 -0
- data/lib/parser.rb +83 -0
- data/lib/type_expression.rb +2 -2
- data/lib/version.rb +1 -1
- metadata +6 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76a73e683c2e961c41eef7d14d5f41ac3e7252f9c22cc2b4b807c6ede92b6a89
|
|
4
|
+
data.tar.gz: 0a264a10f5c070c2b08160e0c94d69cf403c86a2961a0b72eab069b5e47c33af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd2e3b8caf03f7575d765eb573b66035706f2cc82fecea20fb806902e94aae5a7040d3eac4afdb3888b49359a035903e5b714a4662dd1cc4b25b28d79d40797b
|
|
7
|
+
data.tar.gz: 56c33ae15714520170d9cc85df710ce20b5a50dd1162705341938129ab95f0c6556baec3cd19c3371523c3f3994ed549d024a17937c83688ec91879e8e36e6b0
|
data/lib/low_type.rb
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'redefiner'
|
|
3
4
|
require_relative 'type_expression'
|
|
4
5
|
|
|
5
6
|
module LowType
|
|
6
|
-
class InvalidTypeError < StandardError; end;
|
|
7
|
-
class RequiredValueError < StandardError; end;
|
|
8
|
-
|
|
9
7
|
# We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
|
|
10
|
-
def self.included(
|
|
11
|
-
class <<
|
|
8
|
+
def self.included(klass)
|
|
9
|
+
class << klass
|
|
12
10
|
def low_params
|
|
13
11
|
@low_params ||= {}
|
|
14
12
|
end
|
|
@@ -24,43 +22,13 @@ module LowType
|
|
|
24
22
|
alias_method :low_value, :value
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Module.new do
|
|
32
|
-
# TODO: Use an AST.
|
|
33
|
-
File.readlines(file_path).each do |file_line|
|
|
34
|
-
method_line = file_line.strip
|
|
35
|
-
next unless method_line.start_with?('def ') && method_line.include?('(')
|
|
36
|
-
|
|
37
|
-
method_name, args = method_line.delete_prefix('def ').split(/[()]/)
|
|
38
|
-
method_name = method_name.to_sym
|
|
39
|
-
|
|
40
|
-
proxy_method = eval("-> (#{args}) {}")
|
|
41
|
-
required_args, required_kwargs = LowType.required_args(proxy_method)
|
|
42
|
-
|
|
43
|
-
klass.low_params[method_name] = LowType.type_expressions_from_params(proxy_method, args, required_args, required_kwargs)
|
|
44
|
-
|
|
45
|
-
define_method(method_name) do |*args, **kwargs|
|
|
46
|
-
klass.low_params[method_name].each do |param_proxy|
|
|
47
|
-
arg = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
48
|
-
arg = param_proxy.type_expression.default_value if arg.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
|
|
49
|
-
param_proxy.type_expression.validate!(arg:, name: param_proxy.name)
|
|
50
|
-
param_proxy.position ? args[param_proxy.position] = arg : kwargs[param_proxy.name] = arg
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
super(*args, **kwargs)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
25
|
+
parser = Parser.new(file_path: LowType.file_path(klass:))
|
|
26
|
+
private_start_line = parser.private_start_line
|
|
27
|
+
klass.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.instance_methods, private_start_line:, klass:)
|
|
28
|
+
klass.singleton_class.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.class_methods, private_start_line:, klass:)
|
|
57
29
|
end
|
|
58
30
|
|
|
59
31
|
class << self
|
|
60
|
-
def methods(klass)
|
|
61
|
-
klass.public_instance_methods(false) + klass.protected_instance_methods(false) + klass.private_instance_methods(false)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
32
|
def file_path(klass:)
|
|
65
33
|
caller.find { |callee| callee.end_with?("<class:#{klass}>'") }.split(':').first
|
|
66
34
|
end
|
|
@@ -72,67 +40,10 @@ module LowType
|
|
|
72
40
|
def value?(expression)
|
|
73
41
|
!expression.respond_to?(:new) && expression != Integer
|
|
74
42
|
end
|
|
75
|
-
|
|
76
|
-
def required_args(proxy_method)
|
|
77
|
-
required_args = []
|
|
78
|
-
required_kwargs = {}
|
|
79
|
-
|
|
80
|
-
proxy_method.parameters.each do |param|
|
|
81
|
-
param_type, param_name = param
|
|
82
|
-
|
|
83
|
-
case param_type
|
|
84
|
-
when :req
|
|
85
|
-
required_args << nil
|
|
86
|
-
when :keyreq
|
|
87
|
-
required_kwargs[param_name] = nil
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
[required_args, required_kwargs]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def type_expressions_from_params(proxy_method, args, required_args, required_kwargs)
|
|
95
|
-
typed_method = eval(
|
|
96
|
-
<<~RUBY
|
|
97
|
-
-> (#{args}) {
|
|
98
|
-
param_proxies = []
|
|
99
|
-
|
|
100
|
-
proxy_method.parameters.each_with_index do |param, position|
|
|
101
|
-
type, name = param
|
|
102
|
-
position = nil unless [:opt, :req, :rest].include?(type)
|
|
103
|
-
|
|
104
|
-
expression = binding.local_variable_get(name)
|
|
105
|
-
|
|
106
|
-
if expression.class == TypeExpression
|
|
107
|
-
param_proxies << ParamProxy.new(type_expression: expression, name:, type:, position:)
|
|
108
|
-
elsif ::LowType.type?(expression)
|
|
109
|
-
param_proxies << ParamProxy.new(type_expression: TypeExpression.new(type: expression), name:, type:, position:)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
param_proxies
|
|
114
|
-
}
|
|
115
|
-
RUBY
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# Call method with only its required args to evaluate type expressions (which are stored as default values).
|
|
119
|
-
typed_method.call(*required_args, **required_kwargs)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
class ParamProxy
|
|
124
|
-
attr_reader :type_expression, :name, :type, :position
|
|
125
|
-
|
|
126
|
-
def initialize(type_expression:, name:, type:, position: nil)
|
|
127
|
-
@type_expression = type_expression
|
|
128
|
-
@name = name
|
|
129
|
-
@type = type
|
|
130
|
-
@position = position
|
|
131
|
-
end
|
|
132
43
|
end
|
|
133
44
|
|
|
134
45
|
class ValueExpression; end
|
|
135
|
-
|
|
136
|
-
class
|
|
137
|
-
class
|
|
46
|
+
class Boolean; end # TrueClass or FalseClass
|
|
47
|
+
class KeyValue; end # KeyValue[String => Hash]
|
|
48
|
+
class MixedTypes; end # MixedTypes[String | Integer]
|
|
138
49
|
end
|
data/lib/param_proxy.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module LowType
|
|
2
|
+
class ParamProxy
|
|
3
|
+
attr_reader :type_expression, :name, :type, :position
|
|
4
|
+
|
|
5
|
+
def initialize(type_expression:, name:, type:, position: nil)
|
|
6
|
+
@type_expression = type_expression
|
|
7
|
+
@name = name
|
|
8
|
+
@type = type
|
|
9
|
+
@position = position
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/parser.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'prism'
|
|
2
|
+
|
|
3
|
+
module LowType
|
|
4
|
+
class Parser
|
|
5
|
+
attr_reader :parent_map, :instance_methods, :class_methods, :private_start_line
|
|
6
|
+
|
|
7
|
+
def initialize(file_path:)
|
|
8
|
+
root_node = Prism.parse_file(file_path).value
|
|
9
|
+
|
|
10
|
+
parent_mapper = ParentMapper.new
|
|
11
|
+
parent_mapper.visit(root_node)
|
|
12
|
+
@parent_map = parent_mapper.parent_map
|
|
13
|
+
|
|
14
|
+
method_visitor = MethodVisitor.new(@parent_map)
|
|
15
|
+
root_node.accept(method_visitor)
|
|
16
|
+
|
|
17
|
+
@instance_methods = method_visitor.instance_methods
|
|
18
|
+
@class_methods = method_visitor.class_methods
|
|
19
|
+
@private_start_line = method_visitor.private_start_line
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class MethodVisitor < Prism::Visitor
|
|
24
|
+
attr_reader :class_methods, :instance_methods, :private_start_line
|
|
25
|
+
|
|
26
|
+
def initialize(parent_map)
|
|
27
|
+
@parent_map = parent_map
|
|
28
|
+
|
|
29
|
+
@instance_methods = []
|
|
30
|
+
@class_methods = []
|
|
31
|
+
@private_start_line = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def visit_def_node(node)
|
|
35
|
+
if class_method?(node)
|
|
36
|
+
@class_methods << node
|
|
37
|
+
else
|
|
38
|
+
@instance_methods << node
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super # Continue walking the tree.
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def visit_call_node(node)
|
|
45
|
+
@private_start_line = node.start_line if node.name == :private
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def class_method?(node)
|
|
51
|
+
return true if node.is_a?(::Prism::DefNode) && node.receiver.class == Prism::SelfNode # self.method_name
|
|
52
|
+
return true if node.is_a?(::Prism::SingletonClassNode) # class << self
|
|
53
|
+
|
|
54
|
+
if (parent_node = @parent_map[node])
|
|
55
|
+
return class_method?(parent_node)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class ParentMapper < Prism::Visitor
|
|
63
|
+
attr_reader :parent_map
|
|
64
|
+
|
|
65
|
+
def initialize
|
|
66
|
+
@parent_map = {}
|
|
67
|
+
@current_parent = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def visit(node)
|
|
71
|
+
@parent_map[node] = @current_parent
|
|
72
|
+
|
|
73
|
+
old_parent = @current_parent
|
|
74
|
+
@current_parent = node
|
|
75
|
+
|
|
76
|
+
node.compact_child_nodes.each do |n|
|
|
77
|
+
visit(n)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@current_parent = old_parent
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/type_expression.rb
CHANGED
|
@@ -26,10 +26,10 @@ module LowType
|
|
|
26
26
|
|
|
27
27
|
def validate!(arg:, name:)
|
|
28
28
|
if arg.nil? && required?
|
|
29
|
-
raise
|
|
29
|
+
raise ArgumentError, "Missing required argument of type '#{@types.join(', ')}' for '#{name}'"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
raise
|
|
32
|
+
raise TypeError, "Invalid type '#{arg.class}' for '#{name}'" unless @types.include?(arg.class)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: low_type
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: An elegant and simple way to define types in Ruby, only when you need
|
|
14
13
|
them.
|
|
@@ -19,6 +18,8 @@ extensions: []
|
|
|
19
18
|
extra_rdoc_files: []
|
|
20
19
|
files:
|
|
21
20
|
- lib/low_type.rb
|
|
21
|
+
- lib/param_proxy.rb
|
|
22
|
+
- lib/parser.rb
|
|
22
23
|
- lib/type_expression.rb
|
|
23
24
|
- lib/version.rb
|
|
24
25
|
homepage: https://codeberg.org/low_ruby/low_type
|
|
@@ -26,7 +27,6 @@ licenses: []
|
|
|
26
27
|
metadata:
|
|
27
28
|
homepage_uri: https://codeberg.org/low_ruby/low_type
|
|
28
29
|
source_code_uri: https://codeberg.org/low_ruby/low_type/src/branch/main
|
|
29
|
-
post_install_message:
|
|
30
30
|
rdoc_options: []
|
|
31
31
|
require_paths:
|
|
32
32
|
- lib
|
|
@@ -34,15 +34,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
34
34
|
requirements:
|
|
35
35
|
- - ">="
|
|
36
36
|
- !ruby/object:Gem::Version
|
|
37
|
-
version: 3.
|
|
37
|
+
version: 3.3.0
|
|
38
38
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
39
|
requirements:
|
|
40
40
|
- - ">="
|
|
41
41
|
- !ruby/object:Gem::Version
|
|
42
42
|
version: '0'
|
|
43
43
|
requirements: []
|
|
44
|
-
rubygems_version: 3.
|
|
45
|
-
signing_key:
|
|
44
|
+
rubygems_version: 3.7.2
|
|
46
45
|
specification_version: 4
|
|
47
46
|
summary: An elegant way to define types in Ruby
|
|
48
47
|
test_files: []
|