mangrove 0.21.2 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.vscode/settings.json +1 -0
- data/README.md +6 -117
- data/lib/mangrove/enum.rb +101 -0
- data/lib/mangrove/version.rb +1 -1
- data/lib/mangrove.rb +1 -0
- data/lib/tapioca/dsl/compilers/mangrove_enum.rb +52 -0
- data/sorbet/rbi/gems/rdoc@6.6.0.rbi +12669 -0
- data/sorbet/rbi/gems/{simplecov@0.21.2.rbi → simplecov@0.22.0.rbi} +62 -49
- data/sorbet/rbi/gems/tapioca@0.11.10.rbi +178 -3
- data/sorbet/rbi/gems/webrick@1.8.1.rbi +2606 -0
- data/sorbet/tapioca/require.rb +1 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b103efd7003a037c53a885f2ee19899ba8643770237c11bee728f06054611be7
|
4
|
+
data.tar.gz: 5e7557ab35bb32b8212b830ba5d06c6f32904cf2b40fd04de3f469770afb19d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8f487f7ccd5ce99225cb19108eed5e3f89d1fece9048bd589ad78f4179a07ce370d47a876a66e30a992661cee5f40f970e3d3544b2a53e32677d58c3a5294c1
|
7
|
+
data.tar.gz: 1c3003bca763ff503c496f15d271d253a8510c33bdb74ade89708c91774d31fd0c0f29b06a89de4a3661ef4b1b8fa8e863f838d42b4a89aa7e02cca189abcfed
|
data/.rubocop.yml
CHANGED
data/.vscode/settings.json
CHANGED
data/README.md
CHANGED
@@ -1,119 +1,17 @@
|
|
1
1
|
# Mangrove
|
2
2
|
Mangrove provides type utility to use with Sorbet.
|
3
|
-
use `rubocop-mangrove` to statically check rescuing ControlSignal is done
|
4
3
|
|
5
|
-
-
|
6
|
-
- [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
|
7
|
-
|
8
|
-
You can do something like this with the gem.
|
9
|
-
```ruby
|
10
|
-
class TransposeExample
|
11
|
-
extend T::Sig
|
12
|
-
|
13
|
-
sig { params(numbers: T::Enumerable[Integer]).returns(Mangrove::Result[T::Array[Integer], String]) }
|
14
|
-
def divide_arguments_by_3(numbers)
|
15
|
-
Mangrove::Result.from_results(numbers
|
16
|
-
.map { |number|
|
17
|
-
if number % 3 == 0
|
18
|
-
Mangrove::Result::Ok.new(number / 3)
|
19
|
-
else
|
20
|
-
Mangrove::Result::Err.new("number #{number} is not divisible by 3")
|
21
|
-
end
|
22
|
-
})
|
23
|
-
rescue ::Mangrove::ControlFlow::ControlSignal => e
|
24
|
-
Mangrove::Result::Err.new(e.inner_value)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
# rubocop:enable Lint/ConstantDefinitionInBlock
|
28
|
-
|
29
|
-
expect(TransposeExample.new.divide_arguments_by_3([3, 4, 5])).to eq Mangrove::Result::Err.new(["number 4 is not divisible by 3", "number 5 is not divisible by 3"])
|
30
|
-
expect(TransposeExample.new.divide_arguments_by_3([3, 6, 9])).to eq Mangrove::Result::Ok.new([1, 2, 3])
|
31
|
-
|
32
|
-
```
|
33
|
-
|
34
|
-
or like this.
|
35
|
-
```ruby
|
36
|
-
class MyController
|
37
|
-
extend T::Sig
|
38
|
-
|
39
|
-
sig { params(input: String).returns(String) }
|
40
|
-
def create(input)
|
41
|
-
result = MyService.new.execute(input)
|
42
|
-
|
43
|
-
case result
|
44
|
-
when Mangrove::Result::Ok
|
45
|
-
result.ok_inner
|
46
|
-
when Mangrove::Result::Err
|
47
|
-
error = result.err_inner
|
48
|
-
|
49
|
-
case error
|
50
|
-
when MyService::MyServiceError::E1
|
51
|
-
"e1: #{error.inner.msg}"
|
52
|
-
when MyService::MyServiceError::E2
|
53
|
-
"e2: #{error.inner.msg}"
|
54
|
-
when MyService::MyServiceError::Other
|
55
|
-
"other: #{error.inner.msg}"
|
56
|
-
else T.absurd(error)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
4
|
+
Mangrove is a Ruby Gem designed to be the definitive toolkit for leveraging Sorbet's type system in Ruby applications. It's designed to offer a robust, statically-typed experience, focusing on solid types, a functional programming style, and an interface-driven approach.
|
61
5
|
|
62
|
-
|
63
|
-
extend T::Sig
|
64
|
-
extend T::Generic
|
6
|
+
Use `rubocop-mangrove` to statically check rescuing ControlSignal is done
|
65
7
|
|
66
|
-
|
67
|
-
|
68
|
-
E = type_member { { upper: MyServiceError } }
|
69
|
-
|
70
|
-
sig { params(input: String).returns(Mangrove::Result[String, MyServiceError]) }
|
71
|
-
def execute(input)
|
72
|
-
input
|
73
|
-
.safe_to_i
|
74
|
-
.map_err_wt(MyServiceError::Other) { |e|
|
75
|
-
MyServiceError::Other.new(MyAppError::Other.new(e)).as_my_service_error
|
76
|
-
}.and_then_wt(String) { |num|
|
77
|
-
if num < 1
|
78
|
-
Mangrove::Result.err(String, MyServiceError::E1.new(MyAppError::E1.new("num < 1")).as_my_service_error)
|
79
|
-
elsif num < 3
|
80
|
-
Mangrove::Result
|
81
|
-
.ok(num, String)
|
82
|
-
.and_then_wt(String) { |n|
|
83
|
-
if n < 2
|
84
|
-
Mangrove::Result.ok("`#{n}` < 2", String)
|
85
|
-
else
|
86
|
-
Mangrove::Result.err(String, "not `#{n}` < 2")
|
87
|
-
end
|
88
|
-
}
|
89
|
-
.map_err_wt(MyServiceError::E1) { |e|
|
90
|
-
MyServiceError::E1.new(MyAppError::E1.new("mapping to E1 #{e}")).as_my_service_error
|
91
|
-
}
|
92
|
-
.map_ok { |str|
|
93
|
-
{
|
94
|
-
my_key: str
|
95
|
-
}
|
96
|
-
}
|
97
|
-
.map_ok(&:to_s)
|
98
|
-
else
|
99
|
-
Mangrove::Result.err(String, MyServiceError::E2.new(MyAppError::E2.new).as_my_service_error)
|
100
|
-
end
|
101
|
-
}
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
expect(MyController.new.create("0")).to eq "e1: num < 1"
|
106
|
-
expect(MyController.new.create("1")).to eq "{:my_key=>\"`1` < 2\"}"
|
107
|
-
expect(MyController.new.create("2")).to eq "e1: mapping to E1 not `2` < 2"
|
108
|
-
expect(MyController.new.create("3")).to eq "e2: e2"
|
109
|
-
expect(MyController.new.create("invalid")).to eq "other: invalid value for Integer(): \"invalid\""
|
110
|
-
```
|
111
|
-
|
112
|
-
Other examples are available at [`spec/**/**_spec.rb`](https://github.com/kazzix14/mangrove/tree/main/spec).
|
8
|
+
- [Documentation](https://kazzix14.github.io/mangrove/docs/)
|
9
|
+
- [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
|
113
10
|
|
114
11
|
## Features
|
115
12
|
- Option Type
|
116
13
|
- Result Type
|
14
|
+
- Enums with inner types (ADTs)
|
117
15
|
|
118
16
|
## Installation
|
119
17
|
|
@@ -152,19 +50,10 @@ bundle exec tapioca init
|
|
152
50
|
bundle exec rspec -f d
|
153
51
|
bundle exec rubocop -DESP
|
154
52
|
bundle exec srb typecheck
|
53
|
+
bundle exec spoom tc
|
155
54
|
bundle exec ordinare --check
|
156
55
|
bundle exec ruboclean
|
157
56
|
bundle exec yardoc -o docs/ --plugin yard-sorbet
|
158
57
|
rake build
|
159
58
|
rake release
|
160
59
|
```
|
161
|
-
|
162
|
-
## Development
|
163
|
-
|
164
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
165
|
-
|
166
|
-
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).
|
167
|
-
|
168
|
-
## Contributing
|
169
|
-
|
170
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/kazzix14/mangrove.
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
module Mangrove
|
7
|
+
class EnumVariantsScope
|
8
|
+
extend ::T::Sig
|
9
|
+
extend ::T::Helpers
|
10
|
+
|
11
|
+
sig { params(parent: T::Class[T.anything]).void }
|
12
|
+
def initialize(parent)
|
13
|
+
@parent = parent
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(block: T.proc.void).void }
|
17
|
+
def call(&block)
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(variant: T::Class[T.anything], inner_type: T.untyped).void }
|
22
|
+
def variant(variant, inner_type)
|
23
|
+
variant.instance_variable_set(:@__mangrove__enum_inner_type, inner_type)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private_constant :EnumVariantsScope
|
27
|
+
|
28
|
+
module Enum
|
29
|
+
extend ::T::Sig
|
30
|
+
extend ::T::Helpers
|
31
|
+
|
32
|
+
requires_ancestor { Module }
|
33
|
+
|
34
|
+
sig { params(block: T.proc.bind(EnumVariantsScope).void).void }
|
35
|
+
def variants(&block)
|
36
|
+
receiver = block.send(:binding).receiver
|
37
|
+
|
38
|
+
outer_code = <<~RUBY
|
39
|
+
def self.const_missing(id)
|
40
|
+
code = <<~NESTED_RUBY
|
41
|
+
class \#{id} < \#{caller.send(:binding).receiver.name}
|
42
|
+
def initialize(inner)
|
43
|
+
@inner = T.let(inner, self.class.instance_variable_get(:@__mangrove__enum_inner_type))
|
44
|
+
end
|
45
|
+
|
46
|
+
def inner
|
47
|
+
@inner
|
48
|
+
end
|
49
|
+
|
50
|
+
def as_super
|
51
|
+
T.cast(self, \#{caller.send(:binding).receiver.name})
|
52
|
+
end
|
53
|
+
|
54
|
+
def ==(other)
|
55
|
+
other.is_a?(self.class) && other.inner == @inner
|
56
|
+
end
|
57
|
+
end
|
58
|
+
NESTED_RUBY
|
59
|
+
|
60
|
+
class_eval(code, __FILE__, __LINE__ + 1)
|
61
|
+
class_eval(id.to_s, __FILE__, __LINE__ + 1)
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
original_const_missing = receiver.method(:const_missing)
|
66
|
+
|
67
|
+
receiver.class_eval(outer_code, __FILE__, __LINE__ + 1)
|
68
|
+
EnumVariantsScope.new(receiver).call(&block)
|
69
|
+
|
70
|
+
# Mark as sealed hear because we can not define classes in const_missing after marking as sealed
|
71
|
+
receiver.send(:eval, "sealed!", nil, __FILE__, __LINE__)
|
72
|
+
|
73
|
+
variants = constants.filter_map { |variant_name|
|
74
|
+
maybe_variant = receiver.const_get(variant_name, false)
|
75
|
+
|
76
|
+
if maybe_variant.instance_variable_defined?(:@__mangrove__enum_inner_type)
|
77
|
+
maybe_variant
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
receiver.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
82
|
+
@sorbet_sealed_module_all_subclasses = #{variants} # @sorbet_sealed_module_all_subclasses = #{variants}
|
83
|
+
RUBY
|
84
|
+
|
85
|
+
# Bring back the original const_missing
|
86
|
+
receiver.define_singleton_method(:const_missing, original_const_missing)
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(receiver: T::Class[T.anything]).void }
|
90
|
+
def self.extended(receiver)
|
91
|
+
code = <<~RUBY
|
92
|
+
extend T::Sig
|
93
|
+
extend T::Helpers
|
94
|
+
|
95
|
+
abstract!
|
96
|
+
RUBY
|
97
|
+
|
98
|
+
receiver.class_eval(code)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/mangrove/version.rb
CHANGED
data/lib/mangrove.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "mangrove"
|
5
|
+
require "tapioca/dsl"
|
6
|
+
|
7
|
+
module Tapioca
|
8
|
+
module Compilers
|
9
|
+
class MangroveEnum < Tapioca::Dsl::Compiler
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
ConstantType = type_member { { fixed: T.class_of(::Mangrove::Enum) } }
|
13
|
+
|
14
|
+
sig { override.returns(T::Enumerable[Module]) }
|
15
|
+
def self.gather_constants
|
16
|
+
all_classes.select { |c| c.singleton_class < ::Mangrove::Enum && T::AbstractUtils.abstract_module?(c) }
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { override.void }
|
20
|
+
def decorate
|
21
|
+
root.create_path(constant) { |constant_type|
|
22
|
+
constant_type.nodes.append(
|
23
|
+
RBI::Helper.new("abstract"),
|
24
|
+
RBI::Helper.new("sealed")
|
25
|
+
)
|
26
|
+
|
27
|
+
variants = constant.constants.filter_map { |variant_name|
|
28
|
+
maybe_variant = constant.const_get(variant_name, false)
|
29
|
+
|
30
|
+
if maybe_variant.instance_variable_defined?(:@__mangrove__enum_inner_type)
|
31
|
+
maybe_variant
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
inner_types = variants.map { |variant|
|
36
|
+
inner_type = variant.instance_variable_get(:@__mangrove__enum_inner_type).to_s
|
37
|
+
constant_type.create_class(variant.name.gsub(/.*::/, ""), superclass_name: constant_type.fully_qualified_name) { |variant_type|
|
38
|
+
variant_type.create_method("initialize", parameters: [create_param("inner", type: inner_type)], return_type: "void")
|
39
|
+
variant_type.create_method("inner", return_type: inner_type)
|
40
|
+
variant_type.create_method("as_super", return_type: constant.name.to_s)
|
41
|
+
}
|
42
|
+
|
43
|
+
inner_type
|
44
|
+
}
|
45
|
+
|
46
|
+
constant_type.create_method("inner", return_type: "T.any(#{inner_types.join(", ")})")
|
47
|
+
constant_type.create_method("as_super", return_type: constant.name.to_s)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|