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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -43
  3. data/lib/mangrove/result.rb +78 -0
  4. data/lib/mangrove/version.rb +1 -1
  5. data/lib/tapioca/dsl/compilers/mangrove_enum.rb +7 -1
  6. data/sorbet/config +1 -0
  7. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  8. data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
  9. data/sorbet/rbi/gems/{docile@1.4.0.rbi → docile@1.4.1.rbi} +2 -1
  10. data/sorbet/rbi/gems/{erubi@1.12.0.rbi → erubi@1.13.1.rbi} +26 -17
  11. data/sorbet/rbi/gems/{json@2.7.2.rbi → json@2.9.1.rbi} +516 -134
  12. data/sorbet/rbi/gems/logger@1.6.5.rbi +940 -0
  13. data/sorbet/rbi/gems/{parallel@1.24.0.rbi → parallel@1.26.3.rbi} +31 -21
  14. data/sorbet/rbi/gems/{parser@3.3.2.0.rbi → parser@3.3.7.0.rbi} +23 -1736
  15. data/sorbet/rbi/gems/{prism@0.29.0.rbi → prism@1.3.0.rbi} +13817 -10401
  16. data/sorbet/rbi/gems/{psych@5.1.2.rbi → psych@5.2.3.rbi} +289 -236
  17. data/sorbet/rbi/gems/{racc@1.8.0.rbi → racc@1.8.1.rbi} +0 -4
  18. data/sorbet/rbi/gems/rbi@0.2.3.rbi +4542 -0
  19. data/sorbet/rbi/gems/rbs@3.8.1.rbi +6882 -0
  20. data/sorbet/rbi/gems/{rdoc@6.7.0.rbi → rdoc@6.11.0.rbi} +1115 -1058
  21. data/sorbet/rbi/gems/{regexp_parser@2.9.2.rbi → regexp_parser@2.10.0.rbi} +193 -170
  22. data/sorbet/rbi/gems/{rspec-core@3.13.0.rbi → rspec-core@3.13.2.rbi} +146 -280
  23. data/sorbet/rbi/gems/{rspec-expectations@3.13.0.rbi → rspec-expectations@3.13.3.rbi} +323 -294
  24. data/sorbet/rbi/gems/{rspec-mocks@3.13.1.rbi → rspec-mocks@3.13.2.rbi} +46 -46
  25. data/sorbet/rbi/gems/{rspec-support@3.13.1.rbi → rspec-support@3.13.2.rbi} +22 -22
  26. data/sorbet/rbi/gems/ruboclean@0.7.1.rbi +473 -0
  27. data/sorbet/rbi/gems/{rubocop-ast@1.31.3.rbi → rubocop-ast@1.37.0.rbi} +1293 -745
  28. data/sorbet/rbi/gems/{rubocop-rspec@2.30.0.rbi → rubocop-rspec@3.4.0.rbi} +341 -1073
  29. data/sorbet/rbi/gems/{rubocop@1.64.1.rbi → rubocop@1.70.0.rbi} +5693 -3796
  30. data/sorbet/rbi/gems/{simplecov-html@0.12.3.rbi → simplecov-html@0.13.1.rbi} +77 -68
  31. data/sorbet/rbi/gems/{spoom@1.3.2.rbi → spoom@1.5.1.rbi} +2306 -1701
  32. data/sorbet/rbi/gems/{stringio@3.1.0.rbi → stringio@3.1.2.rbi} +1 -0
  33. data/sorbet/rbi/gems/{tapioca@0.14.3.rbi → tapioca@0.16.8.rbi} +411 -332
  34. data/sorbet/rbi/gems/{thor@1.3.1.rbi → thor@1.3.2.rbi} +57 -31
  35. data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  36. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
  37. data/sorbet/rbi/gems/{webrick@1.8.1.rbi → webrick@1.9.1.rbi} +92 -72
  38. data/sorbet/rbi/gems/{yard-sorbet@0.8.1.rbi → yard-sorbet@0.9.0.rbi} +36 -29
  39. data/sorbet/rbi/gems/{yard@0.9.36.rbi → yard@0.9.37.rbi} +393 -235
  40. metadata +39 -42
  41. data/sorbet/rbi/gems/rbi@0.1.13.rbi +0 -3078
  42. data/sorbet/rbi/gems/rexml@3.2.8.rbi +0 -4794
  43. data/sorbet/rbi/gems/ruboclean@0.6.0.rbi +0 -315
  44. data/sorbet/rbi/gems/rubocop-capybara@2.20.0.rbi +0 -1208
  45. data/sorbet/rbi/gems/rubocop-factory_bot@2.25.1.rbi +0 -928
  46. data/sorbet/rbi/gems/rubocop-rspec_rails@2.28.3.rbi +0 -911
  47. data/sorbet/rbi/gems/strscan@3.1.0.rbi +0 -9
  48. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +0 -65
  49. /data/sorbet/rbi/gems/{io-console@0.7.2.rbi → io-console@0.8.0.rbi} +0 -0
  50. /data/sorbet/rbi/gems/{reline@0.5.8.rbi → reline@0.6.0.rbi} +0 -0
  51. /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: 71226a1d7d1fb7eba306e5838de3bb3f0558c5ecaaf765c4f598fbeac1177e5c
4
- data.tar.gz: abce827cd50183555927cf71dbd87d04d2e42273ac71c2eeee6e7d14307ba95c
3
+ metadata.gz: 1dfe08aeea0151d419caef995589fe18b98674aeb78817f651d58eef0b45e552
4
+ data.tar.gz: 070b2971e1f62072b6e26289fe7e04f0f5527c414db63664cb76ef82c19b9ffd
5
5
  SHA512:
6
- metadata.gz: 973e7abe2127c628b8fd3172f62d82103c571910269b6a0256bb6f405ae29eefbd4779bcef569369a5ab79b5a8fc1709c6ef5190f9b2af26be04eb9a8a377d39
7
- data.tar.gz: 8daf78cf239f11cd7bf83efad79dfe163f42953dc7d9be08e1bf0dc6a5976db6630f5c5b037062bc2acabe69c568737819adeaea158b42b0d91188006399910b
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 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.
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
- ## Features
8
+ ---
9
9
 
10
- - Option Type
11
- - Result Type
12
- - Enums with inner types (ADTs)
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
- ## Usage
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
- [Documentation is available here](https://kazzix14.github.io/mangrove/docs).
23
- For more concrete examples, see [`spec/**/**_spec.rb`](https://github.com/kazzix14/mangrove/tree/main/spec).
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
- Mangrove::Result[OkType, ErrType]
27
- Mangrove::Result::Ok[OkType, ErrType]
28
- Mangrove::Result::Err[OkType, ErrType]
29
- Mangrove::Option[InnerType]
30
- Mangrove::Option::Some[InnerType]
31
- Mangrove::Option::None[InnerType]
32
-
33
- my_ok = Result::Ok.new("my value")
34
- my_err = Result::Err.new("my err")
35
- my_some = Option::Some.new(1234)
36
- my_none = Option::None.new
37
-
38
- ##############################
39
-
40
- response = MyClient
41
- .new
42
- .and_then { |client| client.get_response() }
43
- .and_then { |response| response.body }
44
-
45
- case response
46
- when Mangrove::Result::Ok
47
- puts response.ok_inner
48
- when Mangrove::Result::Err
49
- puts response.err_inner
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 VariantWithInteger, Integer
59
- variant VariantWithString, String
60
- variant VariantWithException, Exception
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.
@@ -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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Mangrove
5
- VERSION = "0.30.1"
5
+ VERSION = "0.34.0"
6
6
  end
@@ -48,7 +48,13 @@ module Tapioca
48
48
  inner_type
49
49
  }
50
50
 
51
- constant_type.create_method("inner", return_type: "T.any(#{inner_types.join(", ")})")
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
@@ -3,3 +3,4 @@
3
3
  --ignore=tmp/
4
4
  --ignore=vendor/
5
5
  --enable-experimental-requires-ancestor
6
+ --disable-watchman