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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 849b5a1d0d8d4ec70dda7366f9e72a991aa71ce72631495f66c78e3bb03b8de3
4
- data.tar.gz: 4e5f71133ec37642bee7ef092103926d9b673e6ac3d2f0c621bee69e25b4953c
3
+ metadata.gz: 76a73e683c2e961c41eef7d14d5f41ac3e7252f9c22cc2b4b807c6ede92b6a89
4
+ data.tar.gz: 0a264a10f5c070c2b08160e0c94d69cf403c86a2961a0b72eab069b5e47c33af
5
5
  SHA512:
6
- metadata.gz: dc9e6b031328b7c398b04cb15ebeb9c347c3b4ff6c49a6e64570dbfdfb2b95b8facfde45bbb07f63c285631280629c2d5b8674e7709505ebac8342fd81a539ed
7
- data.tar.gz: cde6c6557d6b47c1e35b6b6216fb939e85f778439483d5c493a21d64cade1d3f6b4bca5c689d0def331a5fb2f0da7f6ee7c497fd974ece7224af72dbb616970c
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(base)
11
- class << base
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
- base.prepend LowType.redefine_methods(file_path: LowType.file_path(klass: base), klass: base)
28
- end
29
-
30
- def self.redefine_methods(file_path:, klass:)
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 Boolean; end
137
- class KeyValue; end
46
+ class Boolean; end # TrueClass or FalseClass
47
+ class KeyValue; end # KeyValue[String => Hash]
48
+ class MixedTypes; end # MixedTypes[String | Integer]
138
49
  end
@@ -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
@@ -26,10 +26,10 @@ module LowType
26
26
 
27
27
  def validate!(arg:, name:)
28
28
  if arg.nil? && required?
29
- raise ::LowType::RequiredValueError, "Missing value of required type [#{@types.join(',')}] for '#{name}'"
29
+ raise ArgumentError, "Missing required argument of type '#{@types.join(', ')}' for '#{name}'"
30
30
  end
31
31
 
32
- raise ::LowType::InvalidTypeError, "Invalid type '#{arg.class}' for '#{name}'" unless @types.include?(arg.class)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LowType
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
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.0
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: 2025-10-10 00:00:00.000000000 Z
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.2.0
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.5.9
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: []