mangrove 0.21.2 → 0.22.0

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