mangrove 0.21.2 → 0.22.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 +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
|