mangrove 0.21.2 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 614ac5851a78950c569f55d16aeba8fd0e1d30c11de41912bdd5cd71ee946a0c
4
- data.tar.gz: 4a67c0db80f4b2f2e79e544edf8baa8bdb86781f12575269c2e52d7c691cbc7d
3
+ metadata.gz: b103efd7003a037c53a885f2ee19899ba8643770237c11bee728f06054611be7
4
+ data.tar.gz: 5e7557ab35bb32b8212b830ba5d06c6f32904cf2b40fd04de3f469770afb19d1
5
5
  SHA512:
6
- metadata.gz: fe09a873d6d1ea23dd9708763d9bb9235176843f581309631918b1bd9a5a52b52e0c9d7948afb649aa968d1bae5538a81786695fdea1f18401bbb0f5cb9c446b
7
- data.tar.gz: 7c8a24200e2a1add424b6cb2e4117bf22161cd7765c31f9588350e88b500a50485bfe6c60255d99cef8391b35e695e116a2a4725df9d1e6fa4119d49e1e4da63
6
+ metadata.gz: d8f487f7ccd5ce99225cb19108eed5e3f89d1fece9048bd589ad78f4179a07ce370d47a876a66e30a992661cee5f40f970e3d3544b2a53e32677d58c3a5294c1
7
+ data.tar.gz: 1c3003bca763ff503c496f15d271d253a8510c33bdb74ade89708c91774d31fd0c0f29b06a89de4a3661ef4b1b8fa8e863f838d42b4a89aa7e02cca189abcfed
data/.rubocop.yml CHANGED
@@ -53,6 +53,9 @@ Style/IfUnlessModifier:
53
53
  Style/MultilineBlockChain:
54
54
  Enabled: false
55
55
 
56
+ Style/RedundantConstantBase:
57
+ Enabled: false
58
+
56
59
  Style/StringLiterals:
57
60
  Enabled: true
58
61
  EnforcedStyle: double_quotes
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "cSpell.words": [
3
+ "klass",
3
4
  "ordinare",
4
5
  "ruboclean"
5
6
  ]
data/README.md CHANGED
@@ -1,119 +1,17 @@
1
1
  # Mangrove
2
2
  Mangrove provides type utility to use with Sorbet.
3
- use `rubocop-mangrove` to statically check rescuing ControlSignal is done
4
3
 
5
- - [Documentation](https://kazzix14.github.io/mangrove/docs/)
6
- - [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
7
-
8
- You can do something like this with the gem.
9
- ```ruby
10
- class TransposeExample
11
- extend T::Sig
12
-
13
- sig { params(numbers: T::Enumerable[Integer]).returns(Mangrove::Result[T::Array[Integer], String]) }
14
- def divide_arguments_by_3(numbers)
15
- Mangrove::Result.from_results(numbers
16
- .map { |number|
17
- if number % 3 == 0
18
- Mangrove::Result::Ok.new(number / 3)
19
- else
20
- Mangrove::Result::Err.new("number #{number} is not divisible by 3")
21
- end
22
- })
23
- rescue ::Mangrove::ControlFlow::ControlSignal => e
24
- Mangrove::Result::Err.new(e.inner_value)
25
- end
26
- end
27
- # rubocop:enable Lint/ConstantDefinitionInBlock
28
-
29
- expect(TransposeExample.new.divide_arguments_by_3([3, 4, 5])).to eq Mangrove::Result::Err.new(["number 4 is not divisible by 3", "number 5 is not divisible by 3"])
30
- expect(TransposeExample.new.divide_arguments_by_3([3, 6, 9])).to eq Mangrove::Result::Ok.new([1, 2, 3])
31
-
32
- ```
33
-
34
- or like this.
35
- ```ruby
36
- class MyController
37
- extend T::Sig
38
-
39
- sig { params(input: String).returns(String) }
40
- def create(input)
41
- result = MyService.new.execute(input)
42
-
43
- case result
44
- when Mangrove::Result::Ok
45
- result.ok_inner
46
- when Mangrove::Result::Err
47
- error = result.err_inner
48
-
49
- case error
50
- when MyService::MyServiceError::E1
51
- "e1: #{error.inner.msg}"
52
- when MyService::MyServiceError::E2
53
- "e2: #{error.inner.msg}"
54
- when MyService::MyServiceError::Other
55
- "other: #{error.inner.msg}"
56
- else T.absurd(error)
57
- end
58
- end
59
- end
60
- end
4
+ 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.
61
5
 
62
- class MyService
63
- extend T::Sig
64
- extend T::Generic
6
+ Use `rubocop-mangrove` to statically check rescuing ControlSignal is done
65
7
 
66
- include Kernel
67
-
68
- E = type_member { { upper: MyServiceError } }
69
-
70
- sig { params(input: String).returns(Mangrove::Result[String, MyServiceError]) }
71
- def execute(input)
72
- input
73
- .safe_to_i
74
- .map_err_wt(MyServiceError::Other) { |e|
75
- MyServiceError::Other.new(MyAppError::Other.new(e)).as_my_service_error
76
- }.and_then_wt(String) { |num|
77
- if num < 1
78
- Mangrove::Result.err(String, MyServiceError::E1.new(MyAppError::E1.new("num < 1")).as_my_service_error)
79
- elsif num < 3
80
- Mangrove::Result
81
- .ok(num, String)
82
- .and_then_wt(String) { |n|
83
- if n < 2
84
- Mangrove::Result.ok("`#{n}` < 2", String)
85
- else
86
- Mangrove::Result.err(String, "not `#{n}` < 2")
87
- end
88
- }
89
- .map_err_wt(MyServiceError::E1) { |e|
90
- MyServiceError::E1.new(MyAppError::E1.new("mapping to E1 #{e}")).as_my_service_error
91
- }
92
- .map_ok { |str|
93
- {
94
- my_key: str
95
- }
96
- }
97
- .map_ok(&:to_s)
98
- else
99
- Mangrove::Result.err(String, MyServiceError::E2.new(MyAppError::E2.new).as_my_service_error)
100
- end
101
- }
102
- end
103
- end
104
-
105
- expect(MyController.new.create("0")).to eq "e1: num < 1"
106
- expect(MyController.new.create("1")).to eq "{:my_key=>\"`1` < 2\"}"
107
- expect(MyController.new.create("2")).to eq "e1: mapping to E1 not `2` < 2"
108
- expect(MyController.new.create("3")).to eq "e2: e2"
109
- expect(MyController.new.create("invalid")).to eq "other: invalid value for Integer(): \"invalid\""
110
- ```
111
-
112
- Other examples are available at [`spec/**/**_spec.rb`](https://github.com/kazzix14/mangrove/tree/main/spec).
8
+ - [Documentation](https://kazzix14.github.io/mangrove/docs/)
9
+ - [Coverage](https://kazzix14.github.io/mangrove/coverage/index.html#_AllFiles)
113
10
 
114
11
  ## Features
115
12
  - Option Type
116
13
  - Result Type
14
+ - Enums with inner types (ADTs)
117
15
 
118
16
  ## Installation
119
17
 
@@ -152,19 +50,10 @@ bundle exec tapioca init
152
50
  bundle exec rspec -f d
153
51
  bundle exec rubocop -DESP
154
52
  bundle exec srb typecheck
53
+ bundle exec spoom tc
155
54
  bundle exec ordinare --check
156
55
  bundle exec ruboclean
157
56
  bundle exec yardoc -o docs/ --plugin yard-sorbet
158
57
  rake build
159
58
  rake release
160
59
  ```
161
-
162
- ## Development
163
-
164
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
165
-
166
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
167
-
168
- ## Contributing
169
-
170
- Bug reports and pull requests are welcome on GitHub at https://github.com/kazzix14/mangrove.
@@ -0,0 +1,101 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Mangrove
7
+ class EnumVariantsScope
8
+ extend ::T::Sig
9
+ extend ::T::Helpers
10
+
11
+ sig { params(parent: T::Class[T.anything]).void }
12
+ def initialize(parent)
13
+ @parent = parent
14
+ end
15
+
16
+ sig { params(block: T.proc.void).void }
17
+ def call(&block)
18
+ instance_eval(&block)
19
+ end
20
+
21
+ sig { params(variant: T::Class[T.anything], inner_type: T.untyped).void }
22
+ def variant(variant, inner_type)
23
+ variant.instance_variable_set(:@__mangrove__enum_inner_type, inner_type)
24
+ end
25
+ end
26
+ private_constant :EnumVariantsScope
27
+
28
+ module Enum
29
+ extend ::T::Sig
30
+ extend ::T::Helpers
31
+
32
+ requires_ancestor { Module }
33
+
34
+ sig { params(block: T.proc.bind(EnumVariantsScope).void).void }
35
+ def variants(&block)
36
+ receiver = block.send(:binding).receiver
37
+
38
+ outer_code = <<~RUBY
39
+ def self.const_missing(id)
40
+ code = <<~NESTED_RUBY
41
+ class \#{id} < \#{caller.send(:binding).receiver.name}
42
+ def initialize(inner)
43
+ @inner = T.let(inner, self.class.instance_variable_get(:@__mangrove__enum_inner_type))
44
+ end
45
+
46
+ def inner
47
+ @inner
48
+ end
49
+
50
+ def as_super
51
+ T.cast(self, \#{caller.send(:binding).receiver.name})
52
+ end
53
+
54
+ def ==(other)
55
+ other.is_a?(self.class) && other.inner == @inner
56
+ end
57
+ end
58
+ NESTED_RUBY
59
+
60
+ class_eval(code, __FILE__, __LINE__ + 1)
61
+ class_eval(id.to_s, __FILE__, __LINE__ + 1)
62
+ end
63
+ RUBY
64
+
65
+ original_const_missing = receiver.method(:const_missing)
66
+
67
+ receiver.class_eval(outer_code, __FILE__, __LINE__ + 1)
68
+ EnumVariantsScope.new(receiver).call(&block)
69
+
70
+ # Mark as sealed hear because we can not define classes in const_missing after marking as sealed
71
+ receiver.send(:eval, "sealed!", nil, __FILE__, __LINE__)
72
+
73
+ variants = constants.filter_map { |variant_name|
74
+ maybe_variant = receiver.const_get(variant_name, false)
75
+
76
+ if maybe_variant.instance_variable_defined?(:@__mangrove__enum_inner_type)
77
+ maybe_variant
78
+ end
79
+ }
80
+
81
+ receiver.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
82
+ @sorbet_sealed_module_all_subclasses = #{variants} # @sorbet_sealed_module_all_subclasses = #{variants}
83
+ RUBY
84
+
85
+ # Bring back the original const_missing
86
+ receiver.define_singleton_method(:const_missing, original_const_missing)
87
+ end
88
+
89
+ sig { params(receiver: T::Class[T.anything]).void }
90
+ def self.extended(receiver)
91
+ code = <<~RUBY
92
+ extend T::Sig
93
+ extend T::Helpers
94
+
95
+ abstract!
96
+ RUBY
97
+
98
+ receiver.class_eval(code)
99
+ end
100
+ end
101
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Mangrove
5
- VERSION = "0.21.2"
5
+ VERSION = "0.22.0"
6
6
  end
data/lib/mangrove.rb CHANGED
@@ -6,6 +6,7 @@ require "sorbet-runtime"
6
6
  require_relative "mangrove/version"
7
7
  require_relative "mangrove/option"
8
8
  require_relative "mangrove/result"
9
+ require_relative "mangrove/enum"
9
10
  require_relative "mangrove/control_flow/control_signal"
10
11
 
11
12
  # Mangrove
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "mangrove"
5
+ require "tapioca/dsl"
6
+
7
+ module Tapioca
8
+ module Compilers
9
+ class MangroveEnum < Tapioca::Dsl::Compiler
10
+ extend T::Sig
11
+
12
+ ConstantType = type_member { { fixed: T.class_of(::Mangrove::Enum) } }
13
+
14
+ sig { override.returns(T::Enumerable[Module]) }
15
+ def self.gather_constants
16
+ all_classes.select { |c| c.singleton_class < ::Mangrove::Enum && T::AbstractUtils.abstract_module?(c) }
17
+ end
18
+
19
+ sig { override.void }
20
+ def decorate
21
+ root.create_path(constant) { |constant_type|
22
+ constant_type.nodes.append(
23
+ RBI::Helper.new("abstract"),
24
+ RBI::Helper.new("sealed")
25
+ )
26
+
27
+ variants = constant.constants.filter_map { |variant_name|
28
+ maybe_variant = constant.const_get(variant_name, false)
29
+
30
+ if maybe_variant.instance_variable_defined?(:@__mangrove__enum_inner_type)
31
+ maybe_variant
32
+ end
33
+ }
34
+
35
+ inner_types = variants.map { |variant|
36
+ inner_type = variant.instance_variable_get(:@__mangrove__enum_inner_type).to_s
37
+ constant_type.create_class(variant.name.gsub(/.*::/, ""), superclass_name: constant_type.fully_qualified_name) { |variant_type|
38
+ variant_type.create_method("initialize", parameters: [create_param("inner", type: inner_type)], return_type: "void")
39
+ variant_type.create_method("inner", return_type: inner_type)
40
+ variant_type.create_method("as_super", return_type: constant.name.to_s)
41
+ }
42
+
43
+ inner_type
44
+ }
45
+
46
+ constant_type.create_method("inner", return_type: "T.any(#{inner_types.join(", ")})")
47
+ constant_type.create_method("as_super", return_type: constant.name.to_s)
48
+ }
49
+ end
50
+ end
51
+ end
52
+ end