low_type 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76a73e683c2e961c41eef7d14d5f41ac3e7252f9c22cc2b4b807c6ede92b6a89
4
- data.tar.gz: 0a264a10f5c070c2b08160e0c94d69cf403c86a2961a0b72eab069b5e47c33af
3
+ metadata.gz: b01f96320aa0543e44d8b4b40430903af4966174fb9ae1533ddf7240c1919ec1
4
+ data.tar.gz: 87094ba39f9755fce4befa350b7ce33d7bd807a9774f1366340a0c9003788933
5
5
  SHA512:
6
- metadata.gz: cd2e3b8caf03f7575d765eb573b66035706f2cc82fecea20fb806902e94aae5a7040d3eac4afdb3888b49359a035903e5b714a4662dd1cc4b25b28d79d40797b
7
- data.tar.gz: 56c33ae15714520170d9cc85df710ce20b5a50dd1162705341938129ab95f0c6556baec3cd19c3371523c3f3994ed549d024a17937c83688ec91879e8e36e6b0
6
+ metadata.gz: 2b4ff1cca9f7763084b3283a13b34955863a81074a6161703c3993aa6d0916180804f8b6058ee35779be159eec7000d9c2f676c656254a67039cb3548073f10d
7
+ data.tar.gz: 39aad0e4a488963333b31c8ec21fc6586e78e5d0ebf76a6546997183ada9ce6baacbd0107ecd0ed71ce5e5c9fa14d8597936b5db874edc48fdc6c082651ea72e
data/lib/low_type.rb CHANGED
@@ -6,6 +6,19 @@ require_relative 'type_expression'
6
6
  module LowType
7
7
  # We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
8
8
  def self.included(klass)
9
+
10
+ # Array[] class method returns a type expression only for the duration of this "included" hook.
11
+ array_class_method = Array.method('[]').unbind
12
+ Array.define_singleton_method('[]') do |expression|
13
+ TypeExpression.new(type: [(expression)])
14
+ end
15
+
16
+ # Hash[] class method returns a type expression only for the duration of this "included" hook.
17
+ hash_class_method = Hash.method('[]').unbind
18
+ Hash.define_singleton_method('[]') do |expression|
19
+ TypeExpression.new(type: expression)
20
+ end
21
+
9
22
  class << klass
10
23
  def low_params
11
24
  @low_params ||= {}
@@ -24,8 +37,12 @@ module LowType
24
37
 
25
38
  parser = Parser.new(file_path: LowType.file_path(klass:))
26
39
  private_start_line = parser.private_start_line
40
+
27
41
  klass.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.instance_methods, private_start_line:, klass:)
28
42
  klass.singleton_class.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.class_methods, private_start_line:, klass:)
43
+ ensure
44
+ Array.define_singleton_method('[]', array_class_method)
45
+ Hash.define_singleton_method('[]', hash_class_method)
29
46
  end
30
47
 
31
48
  class << self
@@ -44,6 +61,4 @@ module LowType
44
61
 
45
62
  class ValueExpression; end
46
63
  class Boolean; end # TrueClass or FalseClass
47
- class KeyValue; end # KeyValue[String => Hash]
48
- class MixedTypes; end # MixedTypes[String | Integer]
49
64
  end
data/lib/redefiner.rb ADDED
@@ -0,0 +1,87 @@
1
+ require_relative 'param_proxy'
2
+ require_relative 'parser'
3
+ require_relative 'type_expression'
4
+
5
+ module LowType
6
+ class Redefiner
7
+ class << self
8
+ def redefine_methods(method_nodes:, private_start_line:, klass:)
9
+ Module.new do
10
+ method_nodes.each do |method_node|
11
+ klass.low_params[method_node.name] = Redefiner.param_proxy_with_type_expressions(method_node)
12
+ next if klass.low_params[method_node.name].empty?
13
+
14
+ define_method(method_node.name) do |*args, **kwargs|
15
+ klass.low_params[method_node.name].each do |param_proxy|
16
+ arg = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
17
+ arg = param_proxy.type_expression.default_value if arg.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
18
+ param_proxy.type_expression.validate!(arg:, name: param_proxy.name)
19
+ param_proxy.position ? args[param_proxy.position] = arg : kwargs[param_proxy.name] = arg
20
+ end
21
+
22
+ super(*args, **kwargs)
23
+ end
24
+
25
+ if private_start_line && method_node.start_line > private_start_line
26
+ private method_node.name
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def param_proxy_with_type_expressions(method_node)
33
+ params = method_node.parameters.slice
34
+ proxy_method = eval("-> (#{params}) {}")
35
+ required_args, required_kwargs = Redefiner.required_args(proxy_method)
36
+
37
+ typed_method = eval(
38
+ <<~RUBY
39
+ -> (#{params}) {
40
+ param_proxies = []
41
+
42
+ proxy_method.parameters.each_with_index do |param, position|
43
+ type, name = param
44
+ position = nil unless [:opt, :req, :rest].include?(type)
45
+
46
+ expression = binding.local_variable_get(name)
47
+
48
+ if expression.class == TypeExpression
49
+ param_proxies << ParamProxy.new(type_expression: expression, name:, type:, position:)
50
+ elsif ::LowType.type?(expression)
51
+ param_proxies << ParamProxy.new(type_expression: TypeExpression.new(type: expression), name:, type:, position:)
52
+ end
53
+ end
54
+
55
+ param_proxies
56
+ }
57
+ RUBY
58
+ )
59
+
60
+ # Call method with only its required args to evaluate type expressions (which are stored as default values).
61
+ typed_method.call(*required_args, **required_kwargs)
62
+
63
+ # TODO: Write spec for this.
64
+ rescue ArgumentError => e
65
+ raise ArgumentError, "Incorrect param syntax"
66
+ end
67
+
68
+ def required_args(proxy_method)
69
+ required_args = []
70
+ required_kwargs = {}
71
+
72
+ proxy_method.parameters.each do |param|
73
+ param_type, param_name = param
74
+
75
+ case param_type
76
+ when :req
77
+ required_args << nil
78
+ when :keyreq
79
+ required_kwargs[param_name] = nil
80
+ end
81
+ end
82
+
83
+ [required_args, required_kwargs]
84
+ end
85
+ end
86
+ end
87
+ end
@@ -29,7 +29,16 @@ module LowType
29
29
  raise ArgumentError, "Missing required argument of type '#{@types.join(', ')}' for '#{name}'"
30
30
  end
31
31
 
32
- raise TypeError, "Invalid type '#{arg.class}' for '#{name}'" unless @types.include?(arg.class)
32
+ @types.each do |type|
33
+ return true if LowType.type?(type) && type == arg.class
34
+ # TODO: Shallow validation of enumerables could be made deeper with user config.
35
+ return true if type.class == Array && arg.class == Array && type.first == arg.first.class
36
+ if type.class == Hash && arg.class == Hash && type.keys[0] == arg.keys[0].class && type.values[0] == arg.values[0].class
37
+ return true
38
+ end
39
+ end
40
+
41
+ raise TypeError, "Invalid type '#{arg.class}' for '#{name}'"
33
42
  end
34
43
  end
35
44
  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.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: low_type
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -20,6 +20,7 @@ files:
20
20
  - lib/low_type.rb
21
21
  - lib/param_proxy.rb
22
22
  - lib/parser.rb
23
+ - lib/redefiner.rb
23
24
  - lib/type_expression.rb
24
25
  - lib/version.rb
25
26
  homepage: https://codeberg.org/low_ruby/low_type