mangrove 0.31.0 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/README.md +131 -41
  4. data/lib/mangrove/result/ext.rb +18 -0
  5. data/lib/mangrove/result.rb +51 -0
  6. data/lib/mangrove/version.rb +1 -1
  7. data/lib/tapioca/dsl/compilers/mangrove_result_ext.rb +40 -0
  8. data/sorbet/config +1 -0
  9. data/sorbet/rbi/gems/benchmark@0.4.0.rbi +618 -0
  10. data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
  11. data/sorbet/rbi/gems/{docile@1.4.0.rbi → docile@1.4.1.rbi} +2 -1
  12. data/sorbet/rbi/gems/{erubi@1.12.0.rbi → erubi@1.13.1.rbi} +26 -17
  13. data/sorbet/rbi/gems/{json@2.7.2.rbi → json@2.9.1.rbi} +516 -134
  14. data/sorbet/rbi/gems/logger@1.6.5.rbi +940 -0
  15. data/sorbet/rbi/gems/{parallel@1.24.0.rbi → parallel@1.26.3.rbi} +31 -21
  16. data/sorbet/rbi/gems/{parser@3.3.2.0.rbi → parser@3.3.7.0.rbi} +23 -1736
  17. data/sorbet/rbi/gems/{prism@0.29.0.rbi → prism@1.3.0.rbi} +13817 -10401
  18. data/sorbet/rbi/gems/{psych@5.1.2.rbi → psych@5.2.3.rbi} +289 -236
  19. data/sorbet/rbi/gems/{racc@1.8.0.rbi → racc@1.8.1.rbi} +0 -4
  20. data/sorbet/rbi/gems/rbi@0.2.3.rbi +4542 -0
  21. data/sorbet/rbi/gems/rbs@3.8.1.rbi +6882 -0
  22. data/sorbet/rbi/gems/{rdoc@6.7.0.rbi → rdoc@6.11.0.rbi} +1115 -1058
  23. data/sorbet/rbi/gems/{regexp_parser@2.9.2.rbi → regexp_parser@2.10.0.rbi} +193 -170
  24. data/sorbet/rbi/gems/{rspec-core@3.13.0.rbi → rspec-core@3.13.2.rbi} +146 -280
  25. data/sorbet/rbi/gems/{rspec-expectations@3.13.0.rbi → rspec-expectations@3.13.3.rbi} +323 -294
  26. data/sorbet/rbi/gems/{rspec-mocks@3.13.1.rbi → rspec-mocks@3.13.2.rbi} +46 -46
  27. data/sorbet/rbi/gems/{rspec-support@3.13.1.rbi → rspec-support@3.13.2.rbi} +22 -22
  28. data/sorbet/rbi/gems/ruboclean@0.7.1.rbi +473 -0
  29. data/sorbet/rbi/gems/{rubocop-ast@1.31.3.rbi → rubocop-ast@1.37.0.rbi} +1293 -745
  30. data/sorbet/rbi/gems/{rubocop-rspec@2.30.0.rbi → rubocop-rspec@3.4.0.rbi} +341 -1073
  31. data/sorbet/rbi/gems/{rubocop@1.64.1.rbi → rubocop@1.70.0.rbi} +5693 -3796
  32. data/sorbet/rbi/gems/{simplecov-html@0.12.3.rbi → simplecov-html@0.13.1.rbi} +77 -68
  33. data/sorbet/rbi/gems/{spoom@1.3.2.rbi → spoom@1.5.1.rbi} +2306 -1701
  34. data/sorbet/rbi/gems/{stringio@3.1.0.rbi → stringio@3.1.2.rbi} +1 -0
  35. data/sorbet/rbi/gems/{tapioca@0.14.3.rbi → tapioca@0.16.8.rbi} +411 -332
  36. data/sorbet/rbi/gems/{thor@1.3.1.rbi → thor@1.3.2.rbi} +57 -31
  37. data/sorbet/rbi/gems/unicode-display_width@3.1.4.rbi +132 -0
  38. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
  39. data/sorbet/rbi/gems/{webrick@1.8.1.rbi → webrick@1.9.1.rbi} +92 -72
  40. data/sorbet/rbi/gems/{yard-sorbet@0.8.1.rbi → yard-sorbet@0.9.0.rbi} +36 -29
  41. data/sorbet/rbi/gems/{yard@0.9.36.rbi → yard@0.9.37.rbi} +393 -235
  42. metadata +41 -42
  43. data/sorbet/rbi/gems/rbi@0.1.13.rbi +0 -3078
  44. data/sorbet/rbi/gems/rexml@3.2.8.rbi +0 -4794
  45. data/sorbet/rbi/gems/ruboclean@0.6.0.rbi +0 -315
  46. data/sorbet/rbi/gems/rubocop-capybara@2.20.0.rbi +0 -1208
  47. data/sorbet/rbi/gems/rubocop-factory_bot@2.25.1.rbi +0 -928
  48. data/sorbet/rbi/gems/rubocop-rspec_rails@2.28.3.rbi +0 -911
  49. data/sorbet/rbi/gems/strscan@3.1.0.rbi +0 -9
  50. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +0 -65
  51. /data/sorbet/rbi/gems/{io-console@0.7.2.rbi → io-console@0.8.0.rbi} +0 -0
  52. /data/sorbet/rbi/gems/{reline@0.5.8.rbi → reline@0.6.0.rbi} +0 -0
  53. /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: 5f834b6984567df445825c79b6027e0259d955c082aa68bbfbd7dc6ac1cf7e40
4
- data.tar.gz: 989be7ac4cd2fab0ccf154785ab19560dd0133b51b76880d2e1ce281e638ea83
3
+ metadata.gz: b672fc8a3a469a1d00a066bbc9781acbfd0229e5acf40f4d05452a3db9bd6ff2
4
+ data.tar.gz: 1fd9cdd284c43bbceb6b0bc44d21f358cc85d9fef1a5a3d6e9244529aa072279
5
5
  SHA512:
6
- metadata.gz: 8b4fe3703902f7f2c566f3789c3d2cc2e8ee4d02d2591bcedfa3425319af3da86d215c63ab6dfd5ba79826ff07f7f4ab4c33f8d33560eb2378a14a9a132e7030
7
- data.tar.gz: fc2b7c8ad55cd4052b25837c64e20573b2ed0446871cb2f1c3a9d8dcd554224c44848256f0c2f9752161dfb54d7e6fcd5740d2a854aaff1117fa7e30fda80eeb
6
+ metadata.gz: c85f28b501f523e3d541a19a64cc560f252ce72479e1df036b75c09dfec4d43843169a614922ce1aece40867434cd5b471aaa2e4eb37337f8694f38971dc491f
7
+ data.tar.gz: a806cd4beb648c60e7e5ceed320ca63d0e9033b7d940fc14fbd0a3254b44b2a657704cf5ee82097aad991b11d7bf0285f99fa0bbe1946ecb5492e6f49f76fe1b
data/.rubocop.yml CHANGED
@@ -35,6 +35,7 @@ Naming/VariableNumber:
35
35
  EnforcedStyle: snake_case
36
36
 
37
37
  Style/BlockDelimiters:
38
+ Enabled: false
38
39
  EnforcedStyle: always_braces
39
40
  AllowedMethods:
40
41
  - describe
data/README.md CHANGED
@@ -1,79 +1,150 @@
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
5
  - [Documentation](https://kazzix14.github.io/mangrove/docs/)
6
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
+ ---
21
33
 
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).
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
- Mangrove::Result[OkType, ErrType]
27
- Mangrove::Result::Ok[OkType]
28
- Mangrove::Result::Err[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
+ ### 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 VariantWithInteger, Integer
59
- variant VariantWithString, String
60
- variant VariantWithException, Exception
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
@@ -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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Mangrove
5
- VERSION = "0.31.0"
5
+ VERSION = "0.35.0"
6
6
  end
@@ -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
@@ -3,3 +3,4 @@
3
3
  --ignore=tmp/
4
4
  --ignore=vendor/
5
5
  --enable-experimental-requires-ancestor
6
+ --disable-watchman