mangrove 0.30.1 → 0.34.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/README.md +107 -43
- data/lib/mangrove/result.rb +78 -0
- data/lib/mangrove/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/mangrove_enum.rb +7 -1
- 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 +39 -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: 1dfe08aeea0151d419caef995589fe18b98674aeb78817f651d58eef0b45e552
|
|
4
|
+
data.tar.gz: 070b2971e1f62072b6e26289fe7e04f0f5527c414db63664cb76ef82c19b9ffd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75bc64b315237e3e0c44ce8245a13e4562daf2ac2444cd3aff98151869f3c3f796259b9f62ab3d3b5fc2a58749033e3839e44d86785c20f0ec1a6b3a51e23474
|
|
7
|
+
data.tar.gz: 60204fffda1d393f5d1b6b0c893c29757d66c14c149685b0e138d2b1e86926da61784315f789c45dcec549dd564cc87f0c7166f9c5f259ce9de05081d41ddac2
|
data/README.md
CHANGED
|
@@ -1,79 +1,124 @@
|
|
|
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
|
-
- [Documentation](https://kazzix14.github.io/mangrove/docs/)
|
|
6
|
-
- [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
|
|
5
|
+
- **[Documentation](https://kazzix14.github.io/mangrove/docs/)**
|
|
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
|
+
---
|
|
33
|
+
|
|
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:
|
|
21
42
|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
### Enums (ADTs)
|
|
91
|
+
|
|
92
|
+
Define an enum with typed variants:
|
|
53
93
|
|
|
94
|
+
```ruby
|
|
54
95
|
class MyEnum
|
|
55
96
|
extend Mangrove::Enum
|
|
56
97
|
|
|
57
98
|
variants do
|
|
58
|
-
variant
|
|
59
|
-
variant
|
|
60
|
-
variant
|
|
61
|
-
variant VariantWithTuple, [Integer, String]
|
|
62
|
-
variant VariantWithShape, { name: String, age: Integer }
|
|
99
|
+
variant IntVariant, Integer
|
|
100
|
+
variant StrVariant, String
|
|
101
|
+
variant ShapeVariant, { name: String, age: Integer }
|
|
63
102
|
end
|
|
64
103
|
end
|
|
104
|
+
|
|
105
|
+
int_v = MyEnum::IntVariant.new(123)
|
|
106
|
+
puts int_v.inner # => 123
|
|
65
107
|
```
|
|
66
108
|
|
|
109
|
+
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).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
67
113
|
## Commands for Development
|
|
68
114
|
|
|
69
|
-
```
|
|
115
|
+
```bash
|
|
70
116
|
git config core.hooksPath hooks
|
|
71
117
|
bundle install
|
|
72
118
|
bundle exec tapioca init
|
|
73
119
|
bundle exec tapioca gems -w `nproc`
|
|
74
120
|
bundle exec tapioca dsl -w `nproc`
|
|
75
121
|
bundle exec tapioca check-shims
|
|
76
|
-
bundle exec tapioca init
|
|
77
122
|
bundle exec rspec -f d
|
|
78
123
|
bundle exec rubocop -DESP
|
|
79
124
|
bundle exec srb typecheck
|
|
@@ -85,3 +130,22 @@ bundle exec yard server --reload --plugin yard-sorbet
|
|
|
85
130
|
rake build
|
|
86
131
|
rake release
|
|
87
132
|
```
|
|
133
|
+
|
|
134
|
+
Run these commands to maintain code quality, generate documentation, and verify type safety under Sorbet.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Contributing
|
|
139
|
+
|
|
140
|
+
We welcome contributions! To get started:
|
|
141
|
+
|
|
142
|
+
1. Fork & clone the repo
|
|
143
|
+
2. Install dependencies: `bundle install`
|
|
144
|
+
3. Make your changes and add tests
|
|
145
|
+
4. Submit a PR
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
Mangrove is available under the [MIT License](https://opensource.org/licenses/MIT). See the LICENSE file for details.
|
data/lib/mangrove/result.rb
CHANGED
|
@@ -42,6 +42,9 @@ module Mangrove
|
|
|
42
42
|
sig { abstract.returns(OkType) }
|
|
43
43
|
def unwrap_or_raise_inner!; end
|
|
44
44
|
|
|
45
|
+
sig { abstract.params(ctx: Result::CollectingContext[OkType, ErrType]).returns(OkType) }
|
|
46
|
+
def unwrap_in(ctx); end
|
|
47
|
+
|
|
45
48
|
sig { abstract.params(message: String).returns(OkType) }
|
|
46
49
|
def expect!(message); end
|
|
47
50
|
|
|
@@ -66,6 +69,12 @@ module Mangrove
|
|
|
66
69
|
sig { abstract.type_parameters(:NewErrType).params(_t_new_err: T::Class[T.type_parameter(:NewErrType)], block: T.proc.params(this: ErrType).returns(T.type_parameter(:NewErrType))).returns(Result[OkType, T.type_parameter(:NewErrType)]) }
|
|
67
70
|
def map_err_wt(_t_new_err, &block); end
|
|
68
71
|
|
|
72
|
+
sig { abstract.params(block: T.proc.params(this: OkType).void).returns(Result[OkType, ErrType]) }
|
|
73
|
+
def tap_ok(&block); end
|
|
74
|
+
|
|
75
|
+
sig { abstract.params(block: T.proc.params(this: ErrType).void).returns(Result[OkType, ErrType]) }
|
|
76
|
+
def tap_err(&block); end
|
|
77
|
+
|
|
69
78
|
sig { abstract.type_parameters(:NewOkType, :NewErrType).params(other: Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]).returns(T.any(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)], Result[T.type_parameter(:NewOkType), ErrType])) }
|
|
70
79
|
def and(other); end
|
|
71
80
|
|
|
@@ -151,6 +160,43 @@ module Mangrove
|
|
|
151
160
|
def err_wt(_t_ok, inner)
|
|
152
161
|
Result::Err[T.type_parameter(:ErrType)].new(inner)
|
|
153
162
|
end
|
|
163
|
+
|
|
164
|
+
sig {
|
|
165
|
+
type_parameters(:O, :E)
|
|
166
|
+
.params(
|
|
167
|
+
_t_ok: T::Class[T.type_parameter(:O)],
|
|
168
|
+
_t_err: T::Class[T.type_parameter(:E)],
|
|
169
|
+
block: T.proc.params(
|
|
170
|
+
do_block: CollectingContext[T.type_parameter(:O), T.type_parameter(:E)]
|
|
171
|
+
).returns(Mangrove::Result[T.type_parameter(:O), T.type_parameter(:E)])
|
|
172
|
+
)
|
|
173
|
+
.returns(Mangrove::Result[T.type_parameter(:O), T.type_parameter(:E)])
|
|
174
|
+
}
|
|
175
|
+
def collecting(_t_ok, _t_err, &block)
|
|
176
|
+
catch(:__mangrove_result_collecting_context_return) {
|
|
177
|
+
block.call(CollectingContext[T.type_parameter(:O), T.type_parameter(:E)].new)
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class CollectingContext
|
|
183
|
+
extend T::Sig
|
|
184
|
+
extend T::Generic
|
|
185
|
+
|
|
186
|
+
O = type_member
|
|
187
|
+
E = type_member
|
|
188
|
+
|
|
189
|
+
sig { params(result: Mangrove::Result[O, E]).returns(O) }
|
|
190
|
+
def try!(result)
|
|
191
|
+
case result
|
|
192
|
+
when Mangrove::Result::Ok
|
|
193
|
+
result.ok_inner
|
|
194
|
+
when Mangrove::Result::Err
|
|
195
|
+
throw :__mangrove_result_collecting_context_return, result
|
|
196
|
+
else
|
|
197
|
+
T.absurd(result)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
154
200
|
end
|
|
155
201
|
|
|
156
202
|
class Ok
|
|
@@ -206,6 +252,11 @@ module Mangrove
|
|
|
206
252
|
@inner
|
|
207
253
|
end
|
|
208
254
|
|
|
255
|
+
sig { override.params(_ctx: Result::CollectingContext[OkType, ErrType]).returns(OkType) }
|
|
256
|
+
def unwrap_in(_ctx)
|
|
257
|
+
@inner
|
|
258
|
+
end
|
|
259
|
+
|
|
209
260
|
sig { override.params(_message: String).returns(OkType) }
|
|
210
261
|
def expect!(_message)
|
|
211
262
|
@inner
|
|
@@ -256,6 +307,17 @@ module Mangrove
|
|
|
256
307
|
self
|
|
257
308
|
end
|
|
258
309
|
|
|
310
|
+
sig { override.params(block: T.proc.params(this: OkType).void).returns(Result[OkType, ErrType]) }
|
|
311
|
+
def tap_ok(&block)
|
|
312
|
+
block.call(@inner)
|
|
313
|
+
self
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
sig { override.params(_block: T.proc.params(this: ErrType).void).returns(Result[OkType, ErrType]) }
|
|
317
|
+
def tap_err(&_block)
|
|
318
|
+
self
|
|
319
|
+
end
|
|
320
|
+
|
|
259
321
|
sig { override.type_parameters(:NewOkType, :NewErrType).params(other: Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]).returns(Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]) }
|
|
260
322
|
def and(other)
|
|
261
323
|
other
|
|
@@ -381,6 +443,11 @@ module Mangrove
|
|
|
381
443
|
raise T.unsafe(@inner)
|
|
382
444
|
end
|
|
383
445
|
|
|
446
|
+
sig { override.params(_ctx: Result::CollectingContext[OkType, ErrType]).returns(T.noreturn) }
|
|
447
|
+
def unwrap_in(_ctx)
|
|
448
|
+
throw :__mangrove_result_collecting_context_return, self
|
|
449
|
+
end
|
|
450
|
+
|
|
384
451
|
sig { override.params(message: String).returns(OkType) }
|
|
385
452
|
def expect!(message)
|
|
386
453
|
raise Result::ControlSignal, message
|
|
@@ -429,6 +496,17 @@ module Mangrove
|
|
|
429
496
|
Result::Err[T.type_parameter(:NewErrType)].new(block.call(@inner))
|
|
430
497
|
end
|
|
431
498
|
|
|
499
|
+
sig { override.params(_block: T.proc.params(this: OkType).void).returns(Result[OkType, ErrType]) }
|
|
500
|
+
def tap_ok(&_block)
|
|
501
|
+
self
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
sig { override.params(block: T.proc.params(this: ErrType).void).returns(Result[OkType, ErrType]) }
|
|
505
|
+
def tap_err(&block)
|
|
506
|
+
block.call(@inner)
|
|
507
|
+
self
|
|
508
|
+
end
|
|
509
|
+
|
|
432
510
|
sig { override.type_parameters(:NewOkType, :NewErrType).params(_other: Result[T.type_parameter(:NewOkType), T.type_parameter(:NewErrType)]).returns(Result[T.type_parameter(:NewOkType), ErrType]) }
|
|
433
511
|
def and(_other)
|
|
434
512
|
self
|
data/lib/mangrove/version.rb
CHANGED
|
@@ -48,7 +48,13 @@ module Tapioca
|
|
|
48
48
|
inner_type
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
return_type = if inner_types.size == 1
|
|
52
|
+
T.must(inner_types.first)
|
|
53
|
+
else
|
|
54
|
+
"T.any(#{inner_types.join(", ")})"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
constant_type.create_method("inner", return_type:)
|
|
52
58
|
constant_type.create_method("as_super", return_type: constant.name.to_s)
|
|
53
59
|
constant_type.sort_nodes!
|
|
54
60
|
}
|
data/sorbet/config
CHANGED