mangrove 0.30.1 → 0.34.0
Sign up to get free protection for your applications and to get access to all the features.
- 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