low_type 0.1.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/lib/low_type.rb +138 -0
- data/lib/type_expression.rb +51 -0
- data/lib/version.rb +5 -0
- metadata +48 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 849b5a1d0d8d4ec70dda7366f9e72a991aa71ce72631495f66c78e3bb03b8de3
|
|
4
|
+
data.tar.gz: 4e5f71133ec37642bee7ef092103926d9b673e6ac3d2f0c621bee69e25b4953c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dc9e6b031328b7c398b04cb15ebeb9c347c3b4ff6c49a6e64570dbfdfb2b95b8facfde45bbb07f63c285631280629c2d5b8674e7709505ebac8342fd81a539ed
|
|
7
|
+
data.tar.gz: cde6c6557d6b47c1e35b6b6216fb939e85f778439483d5c493a21d64cade1d3f6b4bca5c689d0def331a5fb2f0da7f6ee7c497fd974ece7224af72dbb616970c
|
data/lib/low_type.rb
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type_expression'
|
|
4
|
+
|
|
5
|
+
module LowType
|
|
6
|
+
class InvalidTypeError < StandardError; end;
|
|
7
|
+
class RequiredValueError < StandardError; end;
|
|
8
|
+
|
|
9
|
+
# 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
|
|
12
|
+
def low_params
|
|
13
|
+
@low_params ||= {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def type(expression)
|
|
17
|
+
# TODO: Runtime type expression for the supplied variable.
|
|
18
|
+
end
|
|
19
|
+
alias_method :low_type, :type
|
|
20
|
+
|
|
21
|
+
def value(expression)
|
|
22
|
+
# TODO: Cancel out a type expression.
|
|
23
|
+
end
|
|
24
|
+
alias_method :low_value, :value
|
|
25
|
+
end
|
|
26
|
+
|
|
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
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
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
|
+
def file_path(klass:)
|
|
65
|
+
caller.find { |callee| callee.end_with?("<class:#{klass}>'") }.split(':').first
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def type?(expression)
|
|
69
|
+
expression.respond_to?(:new) || expression == Integer
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def value?(expression)
|
|
73
|
+
!expression.respond_to?(:new) && expression != Integer
|
|
74
|
+
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
|
+
end
|
|
133
|
+
|
|
134
|
+
class ValueExpression; end
|
|
135
|
+
|
|
136
|
+
class Boolean; end
|
|
137
|
+
class KeyValue; end
|
|
138
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module LowType
|
|
2
|
+
class TypeExpression
|
|
3
|
+
attr_reader :type, :default_value
|
|
4
|
+
|
|
5
|
+
def initialize(type:)
|
|
6
|
+
@types = [type]
|
|
7
|
+
@default_value = :LOW_TYPE_UNDEFINED
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def |(expression)
|
|
11
|
+
if expression.class == ::LowType::TypeExpression
|
|
12
|
+
@types = @types + expression.types
|
|
13
|
+
@default_value = expression.default_value
|
|
14
|
+
elsif ::LowType.value?(expression)
|
|
15
|
+
@default_value = expression
|
|
16
|
+
else
|
|
17
|
+
@types << expression
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def required?
|
|
24
|
+
@default_value == :LOW_TYPE_UNDEFINED
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def validate!(arg:, name:)
|
|
28
|
+
if arg.nil? && required?
|
|
29
|
+
raise ::LowType::RequiredValueError, "Missing value of required type [#{@types.join(',')}] for '#{name}'"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
raise ::LowType::InvalidTypeError, "Invalid type '#{arg.class}' for '#{name}'" unless @types.include?(arg.class)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Object
|
|
38
|
+
class << self
|
|
39
|
+
# "|" is not defined on Object class and this is the most compute-efficient way to achieve our goal (world peace).
|
|
40
|
+
# "|" bitwise operator on Integer is not defined when the receiver is an Integer class, so we are not in conflict.
|
|
41
|
+
def |(expression)
|
|
42
|
+
if expression.class == ::LowType::TypeExpression
|
|
43
|
+
expression | self
|
|
44
|
+
expression
|
|
45
|
+
else
|
|
46
|
+
type_expression = ::LowType::TypeExpression.new(type: self)
|
|
47
|
+
type_expression | expression
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/version.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: low_type
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- maedi
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: An elegant and simple way to define types in Ruby, only when you need
|
|
14
|
+
them.
|
|
15
|
+
email:
|
|
16
|
+
- maediprichard@gmail.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- lib/low_type.rb
|
|
22
|
+
- lib/type_expression.rb
|
|
23
|
+
- lib/version.rb
|
|
24
|
+
homepage: https://codeberg.org/low_ruby/low_type
|
|
25
|
+
licenses: []
|
|
26
|
+
metadata:
|
|
27
|
+
homepage_uri: https://codeberg.org/low_ruby/low_type
|
|
28
|
+
source_code_uri: https://codeberg.org/low_ruby/low_type/src/branch/main
|
|
29
|
+
post_install_message:
|
|
30
|
+
rdoc_options: []
|
|
31
|
+
require_paths:
|
|
32
|
+
- lib
|
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: 3.2.0
|
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
requirements: []
|
|
44
|
+
rubygems_version: 3.5.9
|
|
45
|
+
signing_key:
|
|
46
|
+
specification_version: 4
|
|
47
|
+
summary: An elegant way to define types in Ruby
|
|
48
|
+
test_files: []
|