mangrove 0.31.0 → 0.35.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 +1 -0
- data/README.md +131 -41
- data/lib/mangrove/result/ext.rb +18 -0
- data/lib/mangrove/result.rb +51 -0
- data/lib/mangrove/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/mangrove_result_ext.rb +40 -0
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
- data/sorbet/rbi/gems/{docile@1.4.0.rbi → docile@1.4.1.rbi} +2 -1
- data/sorbet/rbi/gems/{erubi@1.12.0.rbi → erubi@1.13.1.rbi} +26 -17
- data/sorbet/rbi/gems/{json@2.7.2.rbi → json@2.9.1.rbi} +516 -134
- data/sorbet/rbi/gems/logger@1.6.5.rbi +940 -0
- data/sorbet/rbi/gems/{parallel@1.24.0.rbi → parallel@1.26.3.rbi} +31 -21
- data/sorbet/rbi/gems/{parser@3.3.2.0.rbi → parser@3.3.7.0.rbi} +23 -1736
- data/sorbet/rbi/gems/{prism@0.29.0.rbi → prism@1.3.0.rbi} +13817 -10401
- data/sorbet/rbi/gems/{psych@5.1.2.rbi → psych@5.2.3.rbi} +289 -236
- data/sorbet/rbi/gems/{racc@1.8.0.rbi → racc@1.8.1.rbi} +0 -4
- data/sorbet/rbi/gems/rbi@0.2.3.rbi +4542 -0
- data/sorbet/rbi/gems/rbs@3.8.1.rbi +6882 -0
- data/sorbet/rbi/gems/{rdoc@6.7.0.rbi → rdoc@6.11.0.rbi} +1115 -1058
- data/sorbet/rbi/gems/{regexp_parser@2.9.2.rbi → regexp_parser@2.10.0.rbi} +193 -170
- data/sorbet/rbi/gems/{rspec-core@3.13.0.rbi → rspec-core@3.13.2.rbi} +146 -280
- data/sorbet/rbi/gems/{rspec-expectations@3.13.0.rbi → rspec-expectations@3.13.3.rbi} +323 -294
- data/sorbet/rbi/gems/{rspec-mocks@3.13.1.rbi → rspec-mocks@3.13.2.rbi} +46 -46
- data/sorbet/rbi/gems/{rspec-support@3.13.1.rbi → rspec-support@3.13.2.rbi} +22 -22
- data/sorbet/rbi/gems/ruboclean@0.7.1.rbi +473 -0
- data/sorbet/rbi/gems/{rubocop-ast@1.31.3.rbi → rubocop-ast@1.37.0.rbi} +1293 -745
- data/sorbet/rbi/gems/{rubocop-rspec@2.30.0.rbi → rubocop-rspec@3.4.0.rbi} +341 -1073
- data/sorbet/rbi/gems/{rubocop@1.64.1.rbi → rubocop@1.70.0.rbi} +5693 -3796
- data/sorbet/rbi/gems/{simplecov-html@0.12.3.rbi → simplecov-html@0.13.1.rbi} +77 -68
- data/sorbet/rbi/gems/{spoom@1.3.2.rbi → spoom@1.5.1.rbi} +2306 -1701
- data/sorbet/rbi/gems/{stringio@3.1.0.rbi → stringio@3.1.2.rbi} +1 -0
- data/sorbet/rbi/gems/{tapioca@0.14.3.rbi → tapioca@0.16.8.rbi} +411 -332
- data/sorbet/rbi/gems/{thor@1.3.1.rbi → thor@1.3.2.rbi} +57 -31
- data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
- data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
- data/sorbet/rbi/gems/{webrick@1.8.1.rbi → webrick@1.9.1.rbi} +92 -72
- data/sorbet/rbi/gems/{yard-sorbet@0.8.1.rbi → yard-sorbet@0.9.0.rbi} +36 -29
- data/sorbet/rbi/gems/{yard@0.9.36.rbi → yard@0.9.37.rbi} +393 -235
- metadata +41 -42
- data/sorbet/rbi/gems/rbi@0.1.13.rbi +0 -3078
- data/sorbet/rbi/gems/rexml@3.2.8.rbi +0 -4794
- data/sorbet/rbi/gems/ruboclean@0.6.0.rbi +0 -315
- data/sorbet/rbi/gems/rubocop-capybara@2.20.0.rbi +0 -1208
- data/sorbet/rbi/gems/rubocop-factory_bot@2.25.1.rbi +0 -928
- data/sorbet/rbi/gems/rubocop-rspec_rails@2.28.3.rbi +0 -911
- data/sorbet/rbi/gems/strscan@3.1.0.rbi +0 -9
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +0 -65
- /data/sorbet/rbi/gems/{io-console@0.7.2.rbi → io-console@0.8.0.rbi} +0 -0
- /data/sorbet/rbi/gems/{reline@0.5.8.rbi → reline@0.6.0.rbi} +0 -0
- /data/sorbet/rbi/gems/{ruby-lsp@0.17.2.rbi → ruby-lsp@0.23.6.rbi} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b672fc8a3a469a1d00a066bbc9781acbfd0229e5acf40f4d05452a3db9bd6ff2
|
4
|
+
data.tar.gz: 1fd9cdd284c43bbceb6b0bc44d21f358cc85d9fef1a5a3d6e9244529aa072279
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c85f28b501f523e3d541a19a64cc560f252ce72479e1df036b75c09dfec4d43843169a614922ce1aece40867434cd5b471aaa2e4eb37337f8694f38971dc491f
|
7
|
+
data.tar.gz: a806cd4beb648c60e7e5ceed320ca63d0e9033b7d940fc14fbd0a3254b44b2a657704cf5ee82097aad991b11d7bf0285f99fa0bbe1946ecb5492e6f49f76fe1b
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,79 +1,150 @@
|
|
1
1
|
# Mangrove
|
2
2
|
|
3
|
-
Mangrove is a Ruby
|
3
|
+
Mangrove is a Ruby toolkit that brings a functional, statically-typed flavor to your Sorbet-enabled projects. Inspired by concepts from languages like Rust and Haskell, Mangrove provides a robust set of tools—primarily `Result` and ADT-like Enums—to help you write safer, more expressive Ruby code.
|
4
4
|
|
5
5
|
- [Documentation](https://kazzix14.github.io/mangrove/docs/)
|
6
6
|
- [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
|
7
7
|
|
8
|
-
|
8
|
+
---
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
-
|
10
|
+
## Highlights
|
11
|
+
|
12
|
+
- **Sorbet Integration**
|
13
|
+
Built from the ground up to work smoothly with Sorbet's type system.
|
14
|
+
|
15
|
+
- **Result Type**
|
16
|
+
Model success/failure outcomes with explicit types—no more "return false or nil" for errors!
|
17
|
+
|
18
|
+
- **Enums (ADTs)**
|
19
|
+
Define your own sealed enums with typed variants. Each variant can hold distinct inner data.
|
20
|
+
|
21
|
+
- **Functional Patterns**
|
22
|
+
Chain transformations or short-circuit error handling with a clean monadic style.
|
23
|
+
|
24
|
+
---
|
13
25
|
|
14
26
|
## Installation
|
15
27
|
|
16
|
-
```
|
28
|
+
```bash
|
17
29
|
bundle add mangrove
|
18
30
|
```
|
19
31
|
|
20
|
-
|
32
|
+
---
|
21
33
|
|
22
|
-
|
23
|
-
|
34
|
+
## Usage Overview
|
35
|
+
|
36
|
+
Mangrove revolves around **`Result`** and a sealed "Enum" mechanism for ADTs.
|
37
|
+
|
38
|
+
### Result
|
39
|
+
|
40
|
+
A `Result` is either `Ok(T)` or `Err(E)`. You can compose them with monadic methods like `and_then` and `or_else`.
|
41
|
+
For "early returns" in a functional style, you have two main approaches:
|
42
|
+
|
43
|
+
1. **A context-based DSL (e.g., `ctx.try!`)**
|
44
|
+
2. **An instance method on `Result` itself, `unwrap_in(ctx)`**, which behaves similarly.
|
45
|
+
|
46
|
+
Here's an example of chaining requests and short-circuiting on error:
|
24
47
|
|
25
48
|
```ruby
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Mangrove::
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
class MyClient
|
50
|
+
extend T::Sig
|
51
|
+
|
52
|
+
sig { returns(Mangrove::Result[String, StandardError]) }
|
53
|
+
def connect
|
54
|
+
# ...
|
55
|
+
Mangrove::Result::Ok.new("Connected")
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(data: String).returns(Mangrove::Result[String, StandardError]) }
|
59
|
+
def request(data)
|
60
|
+
# ...
|
61
|
+
Mangrove::Result::Ok.new("Response: #{data}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Let's say we have a special DSL context for collecting short-circuits:
|
66
|
+
# (Hypothetical usage)
|
67
|
+
|
68
|
+
Result.collecting(String, StandardError) do |ctx|
|
69
|
+
final_result = MyClient.new
|
70
|
+
.connect
|
71
|
+
.and_then do |connection|
|
72
|
+
MyClient.new.request("Payload from #{connection}")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Option 1: Call from the context
|
76
|
+
response_data = ctx.try!(final_result)
|
77
|
+
# => If 'final_result' is Err, short-circuits now;
|
78
|
+
# otherwise returns the Ok(T) value.
|
79
|
+
|
80
|
+
puts response_data # If no errors, prints "Response: Connected"
|
81
|
+
|
82
|
+
# Option 2: Call via 'unwrap_in(ctx)'
|
83
|
+
# This does the same short-circuit if 'Err', using the context:
|
84
|
+
response_data_alt = final_result.unwrap_in(ctx)
|
50
85
|
end
|
51
86
|
|
52
|
-
|
87
|
+
# More chaining, etc...
|
88
|
+
```
|
89
|
+
|
90
|
+
### Extension Methods
|
91
|
+
|
92
|
+
Mangrove provides convenient extension methods through `Mangrove::Result::Ext`. These methods allow you to easily wrap any value in a `Result`:
|
53
93
|
|
94
|
+
```ruby
|
95
|
+
# Include the extension in your classes
|
96
|
+
class Object
|
97
|
+
include Mangrove::Result::Ext
|
98
|
+
end
|
99
|
+
|
100
|
+
# Now you can use in_ok and in_err on any object
|
101
|
+
"success".in_ok # => Result::Ok("success")
|
102
|
+
"error".in_err # => Result::Err("error")
|
103
|
+
|
104
|
+
# Useful in method chains
|
105
|
+
"hello"
|
106
|
+
.upcase
|
107
|
+
.in_ok
|
108
|
+
.map_ok { |s| "#{s}!" } # => Result::Ok("HELLO!")
|
109
|
+
|
110
|
+
# Error case
|
111
|
+
"error message"
|
112
|
+
.in_err
|
113
|
+
.map_err { |e| "#{e}!" } # => Result::Err("error message!")
|
114
|
+
```
|
115
|
+
|
116
|
+
### Enums (ADTs)
|
117
|
+
|
118
|
+
Define an enum with typed variants:
|
119
|
+
|
120
|
+
```ruby
|
54
121
|
class MyEnum
|
55
122
|
extend Mangrove::Enum
|
56
123
|
|
57
124
|
variants do
|
58
|
-
variant
|
59
|
-
variant
|
60
|
-
variant
|
61
|
-
variant VariantWithTuple, [Integer, String]
|
62
|
-
variant VariantWithShape, { name: String, age: Integer }
|
125
|
+
variant IntVariant, Integer
|
126
|
+
variant StrVariant, String
|
127
|
+
variant ShapeVariant, { name: String, age: Integer }
|
63
128
|
end
|
64
129
|
end
|
130
|
+
|
131
|
+
int_v = MyEnum::IntVariant.new(123)
|
132
|
+
puts int_v.inner # => 123
|
65
133
|
```
|
66
134
|
|
135
|
+
For more details on monadic methods, short-circuit contexts, and advanced usage, please visit the [official documentation](https://kazzix14.github.io/mangrove/docs/) or see real-world usages in [`spec/`](https://github.com/kazzix14/mangrove/tree/main/spec).
|
136
|
+
|
137
|
+
---
|
138
|
+
|
67
139
|
## Commands for Development
|
68
140
|
|
69
|
-
```
|
141
|
+
```bash
|
70
142
|
git config core.hooksPath hooks
|
71
143
|
bundle install
|
72
144
|
bundle exec tapioca init
|
73
145
|
bundle exec tapioca gems -w `nproc`
|
74
146
|
bundle exec tapioca dsl -w `nproc`
|
75
147
|
bundle exec tapioca check-shims
|
76
|
-
bundle exec tapioca init
|
77
148
|
bundle exec rspec -f d
|
78
149
|
bundle exec rubocop -DESP
|
79
150
|
bundle exec srb typecheck
|
@@ -85,3 +156,22 @@ bundle exec yard server --reload --plugin yard-sorbet
|
|
85
156
|
rake build
|
86
157
|
rake release
|
87
158
|
```
|
159
|
+
|
160
|
+
Run these commands to maintain code quality, generate documentation, and verify type safety under Sorbet.
|
161
|
+
|
162
|
+
---
|
163
|
+
|
164
|
+
## Contributing
|
165
|
+
|
166
|
+
We welcome contributions! To get started:
|
167
|
+
|
168
|
+
1. Fork & clone the repo
|
169
|
+
2. Install dependencies: `bundle install`
|
170
|
+
3. Make your changes and add tests
|
171
|
+
4. Submit a PR
|
172
|
+
|
173
|
+
---
|
174
|
+
|
175
|
+
## License
|
176
|
+
|
177
|
+
Mangrove is available under the [MIT License](https://opensource.org/licenses/MIT). See the LICENSE file for details.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Mangrove
|
5
|
+
module Result
|
6
|
+
module Ext
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
def in_ok
|
10
|
+
Mangrove::Result::Ok.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
def in_err
|
14
|
+
Mangrove::Result::Err.new(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/mangrove/result.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require_relative "result/control_signal"
|
5
|
+
require_relative "result/ext"
|
5
6
|
|
6
7
|
module Mangrove
|
7
8
|
# Result is a type that represents either success (`Ok`) or failure (`Err`).
|
@@ -42,6 +43,9 @@ module Mangrove
|
|
42
43
|
sig { abstract.returns(OkType) }
|
43
44
|
def unwrap_or_raise_inner!; end
|
44
45
|
|
46
|
+
sig { abstract.params(ctx: Result::CollectingContext[OkType, ErrType]).returns(OkType) }
|
47
|
+
def unwrap_in(ctx); end
|
48
|
+
|
45
49
|
sig { abstract.params(message: String).returns(OkType) }
|
46
50
|
def expect!(message); end
|
47
51
|
|
@@ -157,6 +161,43 @@ module Mangrove
|
|
157
161
|
def err_wt(_t_ok, inner)
|
158
162
|
Result::Err[T.type_parameter(:ErrType)].new(inner)
|
159
163
|
end
|
164
|
+
|
165
|
+
sig {
|
166
|
+
type_parameters(:O, :E)
|
167
|
+
.params(
|
168
|
+
_t_ok: T::Class[T.type_parameter(:O)],
|
169
|
+
_t_err: T::Class[T.type_parameter(:E)],
|
170
|
+
block: T.proc.params(
|
171
|
+
do_block: CollectingContext[T.type_parameter(:O), T.type_parameter(:E)]
|
172
|
+
).returns(Mangrove::Result[T.type_parameter(:O), T.type_parameter(:E)])
|
173
|
+
)
|
174
|
+
.returns(Mangrove::Result[T.type_parameter(:O), T.type_parameter(:E)])
|
175
|
+
}
|
176
|
+
def collecting(_t_ok, _t_err, &block)
|
177
|
+
catch(:__mangrove_result_collecting_context_return) {
|
178
|
+
block.call(CollectingContext[T.type_parameter(:O), T.type_parameter(:E)].new)
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class CollectingContext
|
184
|
+
extend T::Sig
|
185
|
+
extend T::Generic
|
186
|
+
|
187
|
+
O = type_member
|
188
|
+
E = type_member
|
189
|
+
|
190
|
+
sig { params(result: Mangrove::Result[O, E]).returns(O) }
|
191
|
+
def try!(result)
|
192
|
+
case result
|
193
|
+
when Mangrove::Result::Ok
|
194
|
+
result.ok_inner
|
195
|
+
when Mangrove::Result::Err
|
196
|
+
throw :__mangrove_result_collecting_context_return, result
|
197
|
+
else
|
198
|
+
T.absurd(result)
|
199
|
+
end
|
200
|
+
end
|
160
201
|
end
|
161
202
|
|
162
203
|
class Ok
|
@@ -212,6 +253,11 @@ module Mangrove
|
|
212
253
|
@inner
|
213
254
|
end
|
214
255
|
|
256
|
+
sig { override.params(_ctx: Result::CollectingContext[OkType, ErrType]).returns(OkType) }
|
257
|
+
def unwrap_in(_ctx)
|
258
|
+
@inner
|
259
|
+
end
|
260
|
+
|
215
261
|
sig { override.params(_message: String).returns(OkType) }
|
216
262
|
def expect!(_message)
|
217
263
|
@inner
|
@@ -398,6 +444,11 @@ module Mangrove
|
|
398
444
|
raise T.unsafe(@inner)
|
399
445
|
end
|
400
446
|
|
447
|
+
sig { override.params(_ctx: Result::CollectingContext[OkType, ErrType]).returns(T.noreturn) }
|
448
|
+
def unwrap_in(_ctx)
|
449
|
+
throw :__mangrove_result_collecting_context_return, self
|
450
|
+
end
|
451
|
+
|
401
452
|
sig { override.params(message: String).returns(OkType) }
|
402
453
|
def expect!(message)
|
403
454
|
raise Result::ControlSignal, message
|
data/lib/mangrove/version.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "mangrove/result/ext"
|
5
|
+
require "tapioca/dsl"
|
6
|
+
require "sorbet-runtime"
|
7
|
+
|
8
|
+
module Tapioca
|
9
|
+
module Compilers
|
10
|
+
class MangroveResultExt < Tapioca::Dsl::Compiler
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
ConstantType = type_member { { fixed: T.class_of(Mangrove::Result::Ext) } }
|
14
|
+
|
15
|
+
sig { override.returns(T::Enumerable[Module]) }
|
16
|
+
def self.gather_constants
|
17
|
+
all_classes.select { |c| c < ::Mangrove::Result::Ext }
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { override.void }
|
21
|
+
def decorate
|
22
|
+
return unless valid_constant_name?(constant.to_s)
|
23
|
+
|
24
|
+
root.create_path(constant) do |klass|
|
25
|
+
klass.create_method("in_ok", return_type: "Mangrove::Result::Ok[#{constant}]")
|
26
|
+
klass.create_method("in_err", return_type: "Mangrove::Result::Err[#{constant}]")
|
27
|
+
end
|
28
|
+
rescue NameError
|
29
|
+
# 握りつぶす
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
sig { params(string: String).returns(T::Boolean) }
|
35
|
+
def valid_constant_name?(string)
|
36
|
+
Object.const_defined?(string) && !!(string =~ /\A[A-Z][a-zA-Z0-9_]*\z/)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/sorbet/config
CHANGED