errgonomic 0.1.0 → 0.2.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 +4 -4
- data/.rubocop.yml +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -1
- data/Rakefile +8 -4
- data/doctest_helper.rb +3 -0
- data/flake.nix +16 -10
- data/gemset.nix +186 -21
- data/lib/errgonomic/core_ext/blank.rb +4 -4
- data/lib/errgonomic/option.rb +358 -0
- data/lib/errgonomic/presence.rb +92 -0
- data/lib/errgonomic/result.rb +316 -0
- data/lib/errgonomic/version.rb +1 -1
- data/lib/errgonomic.rb +41 -42
- metadata +36 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e21646c1a4fefc2d70df2089a912fce14df157b543f16b712c63d6dfdf5630c
|
4
|
+
data.tar.gz: 3732b419a1fbee854590a5fbdb700917c2a079880be10ff9b95520dc4102ef69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1384fea58251177994a31855f6e99ebc15d8cbf173860063cb0d8b53b04f57ec6679e848d98bdd0c37a712f13e34540e4bc7f9908e6a8874bfe891d8f37e6db8
|
7
|
+
data.tar.gz: 229368094740e527915793df1d05b58c82b0da41e04c1fd47924abca7cf98dff7ea5de2dee4e28d3ef3ccf03d0d2950a648d000885813903b5aea9b5d9671ccc
|
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require: rubocop-yard
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--plugin yard-doctest
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2025-03-28
|
4
|
+
|
5
|
+
- Introduce (most of) Result and Option
|
6
|
+
- Presence helpers which raise should have a bang on their name
|
7
|
+
- [Dev, Test] - Replace rspec with yard-doctest
|
8
|
+
|
3
9
|
## [0.1.0] - 2025-02-27
|
4
10
|
|
5
|
-
- Initial release
|
11
|
+
- Initial release with some basic extensions for presence
|
data/Rakefile
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require "rspec/core/rake_task"
|
3
|
+
require 'bundler/gem_tasks'
|
5
4
|
|
5
|
+
require 'rspec/core/rake_task'
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
7
7
|
|
8
|
-
require
|
8
|
+
require 'yard/doctest/rake'
|
9
|
+
YARD::Doctest::RakeTask.new do |task|
|
10
|
+
task.doctest_opts = %w[-v]
|
11
|
+
task.pattern = 'lib/**/*.rb'
|
12
|
+
end
|
9
13
|
|
10
|
-
task default: %i[spec
|
14
|
+
task default: %i[spec yard:doctest]
|
data/doctest_helper.rb
ADDED
data/flake.nix
CHANGED
@@ -15,13 +15,6 @@
|
|
15
15
|
"aarch64-darwin"
|
16
16
|
];
|
17
17
|
overlays = [
|
18
|
-
(final: prev: {
|
19
|
-
gems = final.bundlerEnv {
|
20
|
-
name = "errgonomic";
|
21
|
-
gemdir = ./.;
|
22
|
-
# src = final.lib.cleanSource ../.;
|
23
|
-
};
|
24
|
-
})
|
25
18
|
];
|
26
19
|
forAllSystems =
|
27
20
|
f:
|
@@ -36,12 +29,25 @@
|
|
36
29
|
{
|
37
30
|
devShells = forAllSystems (
|
38
31
|
{ pkgs, ... }:
|
32
|
+
let
|
33
|
+
inherit (pkgs) ruby bundix;
|
34
|
+
in
|
39
35
|
{
|
40
36
|
default = pkgs.mkShell {
|
41
37
|
buildInputs = [
|
42
|
-
|
43
|
-
|
44
|
-
pkgs.
|
38
|
+
ruby
|
39
|
+
bundix
|
40
|
+
(pkgs.bundlerEnv {
|
41
|
+
name = "errgonomic";
|
42
|
+
gemdir = ./.;
|
43
|
+
extraConfigPaths = [
|
44
|
+
./errgonomic.gemspec
|
45
|
+
./lib/errgonomic/version.rb
|
46
|
+
];
|
47
|
+
postInstall = ''
|
48
|
+
find . >&2
|
49
|
+
'';
|
50
|
+
})
|
45
51
|
];
|
46
52
|
};
|
47
53
|
}
|
data/gemset.nix
CHANGED
@@ -9,6 +9,26 @@
|
|
9
9
|
};
|
10
10
|
version = "2.4.2";
|
11
11
|
};
|
12
|
+
backport = {
|
13
|
+
groups = ["default"];
|
14
|
+
platforms = [];
|
15
|
+
source = {
|
16
|
+
remotes = ["https://rubygems.org"];
|
17
|
+
sha256 = "0xbzzjrgah0f8ifgd449kak2vyf30micpz6x2g82aipfv7ypsb4i";
|
18
|
+
type = "gem";
|
19
|
+
};
|
20
|
+
version = "1.2.0";
|
21
|
+
};
|
22
|
+
benchmark = {
|
23
|
+
groups = ["default"];
|
24
|
+
platforms = [];
|
25
|
+
source = {
|
26
|
+
remotes = ["https://rubygems.org"];
|
27
|
+
sha256 = "0jl71qcgamm96dzyqk695j24qszhcc7liw74qc83fpjljp2gh4hg";
|
28
|
+
type = "gem";
|
29
|
+
};
|
30
|
+
version = "0.4.0";
|
31
|
+
};
|
12
32
|
concurrent-ruby = {
|
13
33
|
groups = ["default"];
|
14
34
|
platforms = [];
|
@@ -37,7 +57,17 @@
|
|
37
57
|
path = ./.;
|
38
58
|
type = "path";
|
39
59
|
};
|
40
|
-
version = "0.
|
60
|
+
version = "0.2.0";
|
61
|
+
};
|
62
|
+
jaro_winkler = {
|
63
|
+
groups = ["default"];
|
64
|
+
platforms = [];
|
65
|
+
source = {
|
66
|
+
remotes = ["https://rubygems.org"];
|
67
|
+
sha256 = "09645h5an19zc1i7wlmixszj8xxqb2zc8qlf8dmx39bxpas1l24b";
|
68
|
+
type = "gem";
|
69
|
+
};
|
70
|
+
version = "1.6.0";
|
41
71
|
};
|
42
72
|
json = {
|
43
73
|
groups = ["default"];
|
@@ -49,6 +79,28 @@
|
|
49
79
|
};
|
50
80
|
version = "2.10.1";
|
51
81
|
};
|
82
|
+
kramdown = {
|
83
|
+
dependencies = ["rexml"];
|
84
|
+
groups = ["default"];
|
85
|
+
platforms = [];
|
86
|
+
source = {
|
87
|
+
remotes = ["https://rubygems.org"];
|
88
|
+
sha256 = "131nwypz8b4pq1hxs6gsz3k00i9b75y3cgpkq57vxknkv6mvdfw7";
|
89
|
+
type = "gem";
|
90
|
+
};
|
91
|
+
version = "2.5.1";
|
92
|
+
};
|
93
|
+
kramdown-parser-gfm = {
|
94
|
+
dependencies = ["kramdown"];
|
95
|
+
groups = ["default"];
|
96
|
+
platforms = [];
|
97
|
+
source = {
|
98
|
+
remotes = ["https://rubygems.org"];
|
99
|
+
sha256 = "0a8pb3v951f4x7h968rqfsa19c8arz21zw1vaj42jza22rap8fgv";
|
100
|
+
type = "gem";
|
101
|
+
};
|
102
|
+
version = "1.1.0";
|
103
|
+
};
|
52
104
|
language_server-protocol = {
|
53
105
|
groups = ["default"];
|
54
106
|
platforms = [];
|
@@ -59,15 +111,66 @@
|
|
59
111
|
};
|
60
112
|
version = "3.17.0.4";
|
61
113
|
};
|
62
|
-
|
114
|
+
logger = {
|
63
115
|
groups = ["default"];
|
64
116
|
platforms = [];
|
65
117
|
source = {
|
66
118
|
remotes = ["https://rubygems.org"];
|
67
|
-
sha256 = "
|
119
|
+
sha256 = "05s008w9vy7is3njblmavrbdzyrwwc1fsziffdr58w9pwqj8sqfx";
|
68
120
|
type = "gem";
|
69
121
|
};
|
70
|
-
version = "1.
|
122
|
+
version = "1.6.6";
|
123
|
+
};
|
124
|
+
mini_portile2 = {
|
125
|
+
groups = ["default"];
|
126
|
+
platforms = [];
|
127
|
+
source = {
|
128
|
+
remotes = ["https://rubygems.org"];
|
129
|
+
sha256 = "0x8asxl83msn815lwmb2d7q5p29p7drhjv5va0byhk60v9n16iwf";
|
130
|
+
type = "gem";
|
131
|
+
};
|
132
|
+
version = "2.8.8";
|
133
|
+
};
|
134
|
+
minitest = {
|
135
|
+
groups = ["default" "development"];
|
136
|
+
platforms = [];
|
137
|
+
source = {
|
138
|
+
remotes = ["https://rubygems.org"];
|
139
|
+
sha256 = "0izrg03wn2yj3gd76ck7ifbm9h2kgy8kpg4fk06ckpy4bbicmwlw";
|
140
|
+
type = "gem";
|
141
|
+
};
|
142
|
+
version = "5.25.4";
|
143
|
+
};
|
144
|
+
nokogiri = {
|
145
|
+
dependencies = ["mini_portile2" "racc"];
|
146
|
+
groups = ["default"];
|
147
|
+
platforms = [];
|
148
|
+
source = {
|
149
|
+
remotes = ["https://rubygems.org"];
|
150
|
+
sha256 = "0npx535cs8qc33n0lpbbwl0p9fi3a5bczn6ayqhxvknh9yqw77vb";
|
151
|
+
type = "gem";
|
152
|
+
};
|
153
|
+
version = "1.18.3";
|
154
|
+
};
|
155
|
+
observer = {
|
156
|
+
groups = ["default"];
|
157
|
+
platforms = [];
|
158
|
+
source = {
|
159
|
+
remotes = ["https://rubygems.org"];
|
160
|
+
sha256 = "1b2h1642jy1xrgyakyzz6bkq43gwp8yvxrs8sww12rms65qi18yq";
|
161
|
+
type = "gem";
|
162
|
+
};
|
163
|
+
version = "0.1.2";
|
164
|
+
};
|
165
|
+
ostruct = {
|
166
|
+
groups = ["default"];
|
167
|
+
platforms = [];
|
168
|
+
source = {
|
169
|
+
remotes = ["https://rubygems.org"];
|
170
|
+
sha256 = "05xqijcf80sza5pnlp1c8whdaay8x5dc13214ngh790zrizgp8q9";
|
171
|
+
type = "gem";
|
172
|
+
};
|
173
|
+
version = "0.6.1";
|
71
174
|
};
|
72
175
|
parallel = {
|
73
176
|
groups = ["default"];
|
@@ -120,6 +223,17 @@
|
|
120
223
|
};
|
121
224
|
version = "13.2.1";
|
122
225
|
};
|
226
|
+
rbs = {
|
227
|
+
dependencies = ["logger"];
|
228
|
+
groups = ["default"];
|
229
|
+
platforms = [];
|
230
|
+
source = {
|
231
|
+
remotes = ["https://rubygems.org"];
|
232
|
+
sha256 = "07cwjkx7b3ssy8ccqq1s34sc5snwvgxan2ikmp9y2rz2a9wy6v1b";
|
233
|
+
type = "gem";
|
234
|
+
};
|
235
|
+
version = "3.8.1";
|
236
|
+
};
|
123
237
|
regexp_parser = {
|
124
238
|
groups = ["default"];
|
125
239
|
platforms = [];
|
@@ -130,6 +244,27 @@
|
|
130
244
|
};
|
131
245
|
version = "2.10.0";
|
132
246
|
};
|
247
|
+
reverse_markdown = {
|
248
|
+
dependencies = ["nokogiri"];
|
249
|
+
groups = ["default"];
|
250
|
+
platforms = [];
|
251
|
+
source = {
|
252
|
+
remotes = ["https://rubygems.org"];
|
253
|
+
sha256 = "195c7yra7amggqj7rir0yr09r4v29c2hgkbkb21mj0jsfs3868mb";
|
254
|
+
type = "gem";
|
255
|
+
};
|
256
|
+
version = "3.0.0";
|
257
|
+
};
|
258
|
+
rexml = {
|
259
|
+
groups = ["default"];
|
260
|
+
platforms = [];
|
261
|
+
source = {
|
262
|
+
remotes = ["https://rubygems.org"];
|
263
|
+
sha256 = "1jmbf6lf7pcyacpb939xjjpn1f84c3nw83dy3p1lwjx0l2ljfif7";
|
264
|
+
type = "gem";
|
265
|
+
};
|
266
|
+
version = "3.4.1";
|
267
|
+
};
|
133
268
|
rspec = {
|
134
269
|
dependencies = ["rspec-core" "rspec-expectations" "rspec-mocks"];
|
135
270
|
groups = ["default"];
|
@@ -206,16 +341,16 @@
|
|
206
341
|
};
|
207
342
|
version = "1.38.1";
|
208
343
|
};
|
209
|
-
rubocop-
|
210
|
-
dependencies = ["rubocop" "
|
211
|
-
groups = ["
|
344
|
+
rubocop-yard = {
|
345
|
+
dependencies = ["rubocop" "yard"];
|
346
|
+
groups = ["development"];
|
212
347
|
platforms = [];
|
213
348
|
source = {
|
214
349
|
remotes = ["https://rubygems.org"];
|
215
|
-
sha256 = "
|
350
|
+
sha256 = "03s8lwah6apkr1g25whhd9y2zrqq9dy56g5kwn0bxp0slakrpisz";
|
216
351
|
type = "gem";
|
217
352
|
};
|
218
|
-
version = "
|
353
|
+
version = "0.10.0";
|
219
354
|
};
|
220
355
|
ruby-progressbar = {
|
221
356
|
groups = ["default"];
|
@@ -227,38 +362,36 @@
|
|
227
362
|
};
|
228
363
|
version = "1.13.0";
|
229
364
|
};
|
230
|
-
|
231
|
-
dependencies = ["
|
365
|
+
solargraph = {
|
366
|
+
dependencies = ["backport" "benchmark" "diff-lcs" "jaro_winkler" "kramdown" "kramdown-parser-gfm" "logger" "observer" "ostruct" "parser" "rbs" "reverse_markdown" "rubocop" "thor" "tilt" "yard" "yard-solargraph"];
|
232
367
|
groups = ["default"];
|
233
368
|
platforms = [];
|
234
369
|
source = {
|
235
370
|
remotes = ["https://rubygems.org"];
|
236
|
-
sha256 = "
|
371
|
+
sha256 = "0fqa486hfn6kdbqp3ppy3jvl9xyj8jz41a2dzgkhc6ny2pj31w92";
|
237
372
|
type = "gem";
|
238
373
|
};
|
239
|
-
version = "
|
374
|
+
version = "0.52.0";
|
240
375
|
};
|
241
|
-
|
242
|
-
dependencies = ["lint_roller" "rubocop"];
|
376
|
+
thor = {
|
243
377
|
groups = ["default"];
|
244
378
|
platforms = [];
|
245
379
|
source = {
|
246
380
|
remotes = ["https://rubygems.org"];
|
247
|
-
sha256 = "
|
381
|
+
sha256 = "1nmymd86a0vb39pzj2cwv57avdrl6pl3lf5bsz58q594kqxjkw7f";
|
248
382
|
type = "gem";
|
249
383
|
};
|
250
|
-
version = "1.
|
384
|
+
version = "1.3.2";
|
251
385
|
};
|
252
|
-
|
253
|
-
dependencies = ["lint_roller" "rubocop-performance"];
|
386
|
+
tilt = {
|
254
387
|
groups = ["default"];
|
255
388
|
platforms = [];
|
256
389
|
source = {
|
257
390
|
remotes = ["https://rubygems.org"];
|
258
|
-
sha256 = "
|
391
|
+
sha256 = "0szpapi229v3scrvw1pgy0vpjm7z3qlf58m1198kxn70cs278g96";
|
259
392
|
type = "gem";
|
260
393
|
};
|
261
|
-
version = "
|
394
|
+
version = "2.6.0";
|
262
395
|
};
|
263
396
|
unicode-display_width = {
|
264
397
|
dependencies = ["unicode-emoji"];
|
@@ -281,4 +414,36 @@
|
|
281
414
|
};
|
282
415
|
version = "4.0.4";
|
283
416
|
};
|
417
|
+
yard = {
|
418
|
+
groups = ["development"];
|
419
|
+
platforms = [];
|
420
|
+
source = {
|
421
|
+
remotes = ["https://rubygems.org"];
|
422
|
+
sha256 = "14k9lb9a60r9z2zcqg08by9iljrrgjxdkbd91gw17rkqkqwi1sd6";
|
423
|
+
type = "gem";
|
424
|
+
};
|
425
|
+
version = "0.9.37";
|
426
|
+
};
|
427
|
+
yard-doctest = {
|
428
|
+
dependencies = ["minitest" "yard"];
|
429
|
+
groups = ["development"];
|
430
|
+
platforms = [];
|
431
|
+
source = {
|
432
|
+
remotes = ["https://rubygems.org"];
|
433
|
+
sha256 = "0zw4fa5ri58w76yawh0sc9xj69z26qm59lvjxr1gqn4zv5smmcvw";
|
434
|
+
type = "gem";
|
435
|
+
};
|
436
|
+
version = "0.1.17";
|
437
|
+
};
|
438
|
+
yard-solargraph = {
|
439
|
+
dependencies = ["yard"];
|
440
|
+
groups = ["default"];
|
441
|
+
platforms = [];
|
442
|
+
source = {
|
443
|
+
remotes = ["https://rubygems.org"];
|
444
|
+
sha256 = "03lklm47k6k294ww97x6zpvlqlyjm5q8jidrixhil622r4cld6m1";
|
445
|
+
type = "gem";
|
446
|
+
};
|
447
|
+
version = "0.1.0";
|
448
|
+
};
|
284
449
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'concurrent/map'
|
4
4
|
|
5
5
|
class Object
|
6
6
|
# An object is blank if it's false, empty, or a whitespace string.
|
@@ -99,7 +99,7 @@ class Array
|
|
99
99
|
# [1,2,3].blank? # => false
|
100
100
|
#
|
101
101
|
# @return [true, false]
|
102
|
-
|
102
|
+
alias blank? empty?
|
103
103
|
|
104
104
|
def present? # :nodoc:
|
105
105
|
!empty?
|
@@ -113,7 +113,7 @@ class Hash
|
|
113
113
|
# { key: 'value' }.blank? # => false
|
114
114
|
#
|
115
115
|
# @return [true, false]
|
116
|
-
|
116
|
+
alias blank? empty?
|
117
117
|
|
118
118
|
def present? # :nodoc:
|
119
119
|
!empty?
|
@@ -125,7 +125,7 @@ class Symbol
|
|
125
125
|
#
|
126
126
|
# :''.blank? # => true
|
127
127
|
# :symbol.blank? # => false
|
128
|
-
|
128
|
+
alias blank? empty?
|
129
129
|
|
130
130
|
def present? # :nodoc:
|
131
131
|
!empty?
|
@@ -0,0 +1,358 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Errgonomic
|
4
|
+
module Option
|
5
|
+
# The base class for all options. Some and None are subclasses.
|
6
|
+
#
|
7
|
+
class Any
|
8
|
+
# An option of the same type with an equal inner value is equal.
|
9
|
+
#
|
10
|
+
# Because we're going to monkey patch this into other libraries Rails, we
|
11
|
+
# allow some "pass through" functionality into the inner value of a Some,
|
12
|
+
# such as comparability here.
|
13
|
+
#
|
14
|
+
# TODO: does None == null?
|
15
|
+
#
|
16
|
+
# strict:
|
17
|
+
# Some(1) == 1 # => raise Errgonomic::NotComparableError, "Cannot compare Errgonomic::Option::Some with Integer"
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Some(1) == Some(1) # => true
|
21
|
+
# Some(1) == Some(2) # => false
|
22
|
+
# Some(1) == None() # => false
|
23
|
+
# None() == None() # => true
|
24
|
+
# Some(1) == 1 # => false
|
25
|
+
# None() == nil # => false
|
26
|
+
def ==(other)
|
27
|
+
return false if self.class != other.class
|
28
|
+
return true if none?
|
29
|
+
|
30
|
+
value == other.value
|
31
|
+
end
|
32
|
+
|
33
|
+
# @example
|
34
|
+
# measurement = Errgonomic::Option::Some.new(1)
|
35
|
+
# case measurement
|
36
|
+
# in Errgonomic::Option::Some, value
|
37
|
+
# "Measurement is #{measurement.value}"
|
38
|
+
# in Errgonomic::Option::None
|
39
|
+
# "Measurement is not available"
|
40
|
+
# else
|
41
|
+
# "not matched"
|
42
|
+
# end # => "Measurement is 1"
|
43
|
+
def deconstruct
|
44
|
+
return [self, value] if some?
|
45
|
+
|
46
|
+
[Errgonomic::Option::None]
|
47
|
+
end
|
48
|
+
|
49
|
+
# return true if the contained value is Some and the block returns truthy
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# Some(1).some_and { |x| x > 0 } # => true
|
53
|
+
# Some(0).some_and { |x| x > 0 } # => false
|
54
|
+
# None().some_and { |x| x > 0 } # => false
|
55
|
+
def some_and(&block)
|
56
|
+
return false if none?
|
57
|
+
|
58
|
+
!!block.call(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
alias some_and? some_and
|
62
|
+
|
63
|
+
# return true if the contained value is None or the block returns truthy
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# None().none_or { false } # => true
|
67
|
+
# Some(1).none_or { |x| x > 0 } # => true
|
68
|
+
# Some(1).none_or { |x| x < 0 } # => false
|
69
|
+
def none_or(&block)
|
70
|
+
return true if none?
|
71
|
+
|
72
|
+
!!block.call(value)
|
73
|
+
end
|
74
|
+
|
75
|
+
alias none_or? none_or
|
76
|
+
|
77
|
+
# return an Array with the contained value, if any
|
78
|
+
# @example
|
79
|
+
# Some(1).to_a # => [1]
|
80
|
+
# None().to_a # => []
|
81
|
+
def to_a
|
82
|
+
return [] if none?
|
83
|
+
|
84
|
+
[value]
|
85
|
+
end
|
86
|
+
|
87
|
+
# returns the inner value if present, else raises an error
|
88
|
+
# @example
|
89
|
+
# Some(1).unwrap! # => 1
|
90
|
+
# None().unwrap! # => raise Errgonomic::UnwrapError, "cannot unwrap None"
|
91
|
+
def unwrap!
|
92
|
+
raise Errgonomic::UnwrapError, 'cannot unwrap None' if none?
|
93
|
+
|
94
|
+
value
|
95
|
+
end
|
96
|
+
|
97
|
+
# returns the inner value if pressent, else raises an error with the given
|
98
|
+
# message
|
99
|
+
# @example
|
100
|
+
# Some(1).expect!("msg") # => 1
|
101
|
+
# None().expect!("msg") # => raise Errgonomic::ExpectError, "msg"
|
102
|
+
def expect!(msg)
|
103
|
+
raise Errgonomic::ExpectError, msg if none?
|
104
|
+
|
105
|
+
value
|
106
|
+
end
|
107
|
+
|
108
|
+
# returns the inner value if present, else returns the default value
|
109
|
+
# @example
|
110
|
+
# Some(1).unwrap_or(2) # => 1
|
111
|
+
# None().unwrap_or(2) # => 2
|
112
|
+
def unwrap_or(default)
|
113
|
+
return default if none?
|
114
|
+
|
115
|
+
value
|
116
|
+
end
|
117
|
+
|
118
|
+
# returns the inner value if present, else returns the result of the
|
119
|
+
# provided block
|
120
|
+
# @example
|
121
|
+
# Some(1).unwrap_or_else { 2 } # => 1
|
122
|
+
# None().unwrap_or_else { 2 } # => 2
|
123
|
+
def unwrap_or_else(&block)
|
124
|
+
return block.call if none?
|
125
|
+
|
126
|
+
value
|
127
|
+
end
|
128
|
+
|
129
|
+
# Calls a function with the inner value, if Some, but returns the original
|
130
|
+
# option. In Rust, this is "inspect" but that clashes with Ruby
|
131
|
+
# conventions. We call this "tap_some" to avoid further clashing with
|
132
|
+
# "tap."
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# tapped = false
|
136
|
+
# Some(1).tap_some { |x| tapped = x } # => Some(1)
|
137
|
+
# tapped # => 1
|
138
|
+
# tapped = false
|
139
|
+
# None().tap_some { tapped = true } # => None()
|
140
|
+
# tapped # => false
|
141
|
+
def tap_some(&block)
|
142
|
+
block.call(value) if some?
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
# Maps the Option to another Option by applying a function to the
|
147
|
+
# contained value (if Some) or returns None. Raises a pedantic exception
|
148
|
+
# if the return value of the block is not an Option.
|
149
|
+
#
|
150
|
+
# @example
|
151
|
+
# Some(1).map { |x| x + 1 } # => Some(2)
|
152
|
+
# None().map { |x| x + 1 } # => None()
|
153
|
+
def map(&block)
|
154
|
+
return self if none?
|
155
|
+
|
156
|
+
Some(block.call(value))
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the provided default (if none), or applies a function to the
|
160
|
+
# contained value (if some). If you want lazy evaluation for the provided
|
161
|
+
# value, use +map_or_else+.
|
162
|
+
#
|
163
|
+
# @example
|
164
|
+
# None().map_or(1) { 100 } # => Some(1)
|
165
|
+
# Some(1).map_or(100) { |x| x + 1 } # => Some(2)
|
166
|
+
# Some("foo").map_or(0) { |str| str.length } # => Some(3)
|
167
|
+
def map_or(default, &block)
|
168
|
+
return Some(default) if none?
|
169
|
+
|
170
|
+
Some(block.call(value))
|
171
|
+
end
|
172
|
+
|
173
|
+
# Computes a default from the given Proc if None, or applies the block to
|
174
|
+
# the contained value (if Some).
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# None().map_or_else(-> { :foo }) { :bar } # => Some(:foo)
|
178
|
+
# Some("str").map_or_else(-> { 100 }) { |str| str.length } # => Some(3)
|
179
|
+
# None().map_or_else( -> { nil }) { |str| str.length } # => None()
|
180
|
+
def map_or_else(proc, &block)
|
181
|
+
if none?
|
182
|
+
val = proc.call
|
183
|
+
return val ? Some(val) : None()
|
184
|
+
end
|
185
|
+
|
186
|
+
Some(block.call(value))
|
187
|
+
end
|
188
|
+
|
189
|
+
# convert the option into a result where Some is Ok and None is Err
|
190
|
+
# @example
|
191
|
+
# None().ok # => Err()
|
192
|
+
# Some(1).ok # => Ok(1)
|
193
|
+
def ok
|
194
|
+
return Errgonomic::Result::Ok.new(value) if some?
|
195
|
+
|
196
|
+
Errgonomic::Result::Err.new
|
197
|
+
end
|
198
|
+
|
199
|
+
# Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err)
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# None().ok_or("wow") # => Err("wow")
|
203
|
+
# Some(1).ok_or("such err") # => Ok(1)
|
204
|
+
def ok_or(err)
|
205
|
+
return Errgonomic::Result::Ok.new(value) if some?
|
206
|
+
|
207
|
+
Errgonomic::Result::Err.new(err)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Transforms the option into a result, mapping Some(v) to Ok(v) and None to Err(err).
|
211
|
+
# TODO: block or proc?
|
212
|
+
#
|
213
|
+
# @example
|
214
|
+
# None().ok_or_else { "wow" } # => Err("wow")
|
215
|
+
# Some("foo").ok_or_else { "such err" } # => Ok("foo")
|
216
|
+
def ok_or_else(&block)
|
217
|
+
return Errgonomic::Result::Ok.new(value) if some?
|
218
|
+
|
219
|
+
Errgonomic::Result::Err.new(block.call)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Returns the option if it contains a value, otherwise returns the provided Option. Returns an Option.
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# None().or(Some(1)) # => Some(1)
|
226
|
+
# Some(2).or(Some(3)) # => Some(2)
|
227
|
+
# None().or(2) # => raise Errgonomic::ArgumentError.new, "other must be an Option, was Integer"
|
228
|
+
def or(other)
|
229
|
+
raise ArgumentError, "other must be an Option, was #{other.class.name}" unless other.is_a?(Any)
|
230
|
+
|
231
|
+
return self if some?
|
232
|
+
|
233
|
+
other
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns the option if it contains a value, otherwise calls the block and returns the result. Returns an Option.
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# None().or_else { Some(1) } # => Some(1)
|
240
|
+
# Some(2).or_else { Some(3) } # => Some(2)
|
241
|
+
# None().or_else { 2 } # => raise Errgonomic::ArgumentError.new, "block must return an Option, was Integer"
|
242
|
+
def or_else(&block)
|
243
|
+
return self if some?
|
244
|
+
|
245
|
+
val = block.call
|
246
|
+
if !val.is_a?(Errgonomic::Option::Any) && Errgonomic.give_me_ambiguous_downstream_errors?
|
247
|
+
raise Errgonomic::ArgumentError.new, "block must return an Option, was #{val.class.name}"
|
248
|
+
end
|
249
|
+
|
250
|
+
val
|
251
|
+
end
|
252
|
+
|
253
|
+
# If self is Some, return the provided other Option.
|
254
|
+
#
|
255
|
+
# @example
|
256
|
+
# None().and(Some(1)) # => None()
|
257
|
+
# Some(2).and(Some(3)) # => Some(3)
|
258
|
+
def and(other)
|
259
|
+
return self if none?
|
260
|
+
|
261
|
+
other
|
262
|
+
end
|
263
|
+
|
264
|
+
# If self is Some, call the given block and return its value. Block most return an Option.
|
265
|
+
#
|
266
|
+
# @example
|
267
|
+
# None().and_then { Some(1) } # => None()
|
268
|
+
# Some(2).and_then { Some(3) } # => Some(3)
|
269
|
+
def and_then(&block)
|
270
|
+
return self if none?
|
271
|
+
|
272
|
+
val = block.call
|
273
|
+
if Errgonomic.give_me_ambiguous_downstream_errors? && !val.is_a?(Errgonomic::Option::Any)
|
274
|
+
raise Errgonomic::ArgumentError.new, "block must return an Option, was #{val.class.name}"
|
275
|
+
end
|
276
|
+
|
277
|
+
val
|
278
|
+
end
|
279
|
+
|
280
|
+
# Zips self with another Option.
|
281
|
+
#
|
282
|
+
# If self is Some(s) and other is Some(o), this method returns
|
283
|
+
# Some([s, o]). Otherwise, None is returned.
|
284
|
+
#
|
285
|
+
# @example
|
286
|
+
# None().zip(Some(1)) # => None()
|
287
|
+
# Some(1).zip(None()) # => None()
|
288
|
+
# Some(2).zip(Some(3)) # => Some([2, 3])
|
289
|
+
def zip(other)
|
290
|
+
return None() unless some? && other.some?
|
291
|
+
|
292
|
+
Some([value, other.value])
|
293
|
+
end
|
294
|
+
|
295
|
+
# Zip two options using the block passed. If self is Some and Other is
|
296
|
+
# some, yield both of their values to the block and return its value as
|
297
|
+
# Some. Else return None.
|
298
|
+
#
|
299
|
+
# @example
|
300
|
+
# None().zip_with(Some(1)) { |a, b| a + b } # => None()
|
301
|
+
# Some(1).zip_with(None()) { |a, b| a + b } # => None()
|
302
|
+
# Some(2).zip_with(Some(3)) { |a, b| a + b } # => Some(5)
|
303
|
+
def zip_with(other, &block)
|
304
|
+
return None() unless some? && other.some?
|
305
|
+
other = block.call(value, other.value)
|
306
|
+
Some(other)
|
307
|
+
end
|
308
|
+
|
309
|
+
# filter
|
310
|
+
# xor
|
311
|
+
# insert
|
312
|
+
# get_or_insert
|
313
|
+
# get_or_insert_with
|
314
|
+
# take
|
315
|
+
# take_if
|
316
|
+
# replace
|
317
|
+
# zip
|
318
|
+
# zip_with
|
319
|
+
end
|
320
|
+
|
321
|
+
# Represent a value
|
322
|
+
class Some < Any
|
323
|
+
attr_accessor :value
|
324
|
+
|
325
|
+
def initialize(value)
|
326
|
+
@value = value
|
327
|
+
end
|
328
|
+
|
329
|
+
def some?
|
330
|
+
true
|
331
|
+
end
|
332
|
+
|
333
|
+
def none?
|
334
|
+
false
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class None < Any
|
339
|
+
def some?
|
340
|
+
false
|
341
|
+
end
|
342
|
+
|
343
|
+
def none?
|
344
|
+
true
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Global convenience for constructing a Some value.
|
351
|
+
def Some(value)
|
352
|
+
Errgonomic::Option::Some.new(value)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Global convenience for constructing a None value.
|
356
|
+
def None
|
357
|
+
Errgonomic::Option::None.new
|
358
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The semantics here borrow heavily from ActiveSupport. Let's prefer that if
|
4
|
+
# loaded, otherwise just copypasta the bits we like. Or convince me to make that
|
5
|
+
# gem a dependency.
|
6
|
+
require_relative './core_ext/blank' unless Object.methods.include?(:blank?)
|
7
|
+
|
8
|
+
class Object
|
9
|
+
# Returns the receiver if it is present, otherwise raises a NotPresentError.
|
10
|
+
# This method is useful to enforce strong expectations, where it is preferable
|
11
|
+
# to fail early rather than risk causing an ambiguous error somewhere else.
|
12
|
+
#
|
13
|
+
# @param message [String] The error message to raise if the receiver is not present.
|
14
|
+
# @return [Object] The receiver if it is present, otherwise raises a NotPresentError.
|
15
|
+
def present_or_raise!(message)
|
16
|
+
raise Errgonomic::NotPresentError, message if blank?
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :present_or_raise, :present_or_raise!
|
22
|
+
|
23
|
+
# Returns the receiver if it is present, otherwise returns the given value. If
|
24
|
+
# constructing the default value is expensive, consider using
|
25
|
+
# +present_or_else+.
|
26
|
+
#
|
27
|
+
# @param value [Object] The value to return if the receiver is not present.
|
28
|
+
# @return [Object] The receiver if it is present, otherwise the given value.
|
29
|
+
def present_or(value)
|
30
|
+
# TBD whether this is *too* strict
|
31
|
+
if value.class != self.class && self.class != NilClass
|
32
|
+
raise Errgonomic::TypeMismatchError,
|
33
|
+
"Type mismatch: default value is a #{value.class} but original was a #{self.class}"
|
34
|
+
end
|
35
|
+
|
36
|
+
return self if present?
|
37
|
+
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the receiver if it is present, otherwise returns the result of the
|
42
|
+
# block. Invoking a block may be preferable to returning a default value with
|
43
|
+
# +present_or+, if constructing the default value is expensive.
|
44
|
+
#
|
45
|
+
# @param block [Proc] The block to call if the receiver is not present.
|
46
|
+
# @return [Object] The receiver if it is present, otherwise the result of the block.
|
47
|
+
def present_or_else(&block)
|
48
|
+
return block.call if blank?
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the receiver if it is blank, otherwise raises a NotPresentError.
|
54
|
+
# This method is helpful to enforce expectations where blank objects are required.
|
55
|
+
#
|
56
|
+
# @param message [String] The error message to raise if the receiver is not blank.
|
57
|
+
# @return [Object] The receiver if it is blank, otherwise raises a NotPresentError.
|
58
|
+
def blank_or_raise!(message)
|
59
|
+
raise Errgonomic::NotPresentError, message unless blank?
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_method :blank_or_raise, :blank_or_raise!
|
65
|
+
|
66
|
+
# Returns the receiver if it is blank, otherwise returns the given value.
|
67
|
+
#
|
68
|
+
# @param value [Object] The value to return if the receiver is not blank.
|
69
|
+
# @return [Object] The receiver if it is blank, otherwise the given value.
|
70
|
+
def blank_or(value)
|
71
|
+
# TBD whether this is *too* strict
|
72
|
+
if value.class != self.class && self.class != NilClass
|
73
|
+
raise Errgonomic::TypeMismatchError,
|
74
|
+
"Type mismatch: default value is a #{value.class} but original was a #{self.class}"
|
75
|
+
end
|
76
|
+
|
77
|
+
return self if blank?
|
78
|
+
|
79
|
+
value
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the receiver if it is blank, otherwise returns the result of the
|
83
|
+
# block.
|
84
|
+
#
|
85
|
+
# @param block [Proc] The block to call if the receiver is not blank.
|
86
|
+
# @return [Object] The receiver if it is blank, otherwise the result of the block.
|
87
|
+
def blank_or_else(&block)
|
88
|
+
return block.call unless blank?
|
89
|
+
|
90
|
+
self
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Errgonomic
|
4
|
+
module Result
|
5
|
+
# The base class for Result's Ok and Err class variants. We implement as
|
6
|
+
# much logic as possible here, and let Ok and Err handle their
|
7
|
+
# initialization and self identification.
|
8
|
+
class Any
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Equality comparison for Result objects is based on value not reference.
|
16
|
+
#
|
17
|
+
# @param other [Object]
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Ok(1) == Ok(1) # => true
|
21
|
+
# Ok(1) == Err(1) # => false
|
22
|
+
# Ok(1).object_id == Ok(1).object_id # => false
|
23
|
+
# Ok(1) == 1 # => false
|
24
|
+
# Err() == nil # => false
|
25
|
+
def ==(other)
|
26
|
+
return false if self.class != other.class
|
27
|
+
|
28
|
+
value == other.value
|
29
|
+
end
|
30
|
+
|
31
|
+
# Indicate that this is some kind of result object. Contrast to
|
32
|
+
# Object#result? which is false for all other types.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# Ok("a").result? # => true
|
36
|
+
# Err("a").result? # => true
|
37
|
+
# "a".result? # => false
|
38
|
+
def result?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return true if the inner value is an Ok and the result of the block is
|
43
|
+
# truthy.
|
44
|
+
#
|
45
|
+
# @param [Proc] block The block to evaluate if the inner value is an Ok.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Ok(1).ok_and?(&:odd?) # => true
|
49
|
+
# Ok(1).ok_and?(&:even?) # => false
|
50
|
+
# Err(:a).ok_and? { |_| true } # => false
|
51
|
+
# Err(:b).ok_and? { |_| false } # => false
|
52
|
+
def ok_and?(&block)
|
53
|
+
if ok?
|
54
|
+
!!block.call(value)
|
55
|
+
else
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Return true if the inner value is an Err and the result of the block is
|
61
|
+
# truthy.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# Ok(1).err_and?(&:odd?) # => false
|
65
|
+
# Ok(1).err_and?(&:even?) # => false
|
66
|
+
# Err(:a).err_and? { |_| true } # => true
|
67
|
+
# Err(:b).err_and? { |_| false } # => false
|
68
|
+
def err_and?(&block)
|
69
|
+
if err?
|
70
|
+
!!block.call(value)
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the inner value of an Ok, else raise an exception when Err.
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# Ok(1).unwrap! # => 1
|
80
|
+
# Err(:c).unwrap! # => raise Errgonomic::UnwrapError, "value is an Err"
|
81
|
+
def unwrap!
|
82
|
+
raise Errgonomic::UnwrapError, 'value is an Err' unless ok?
|
83
|
+
|
84
|
+
@value
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return the inner value of an Ok, else raise an exception with the given
|
88
|
+
# message when Err.
|
89
|
+
#
|
90
|
+
# @param msg [String]
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# Ok(1).expect!("should have worked") # => 1
|
94
|
+
# Err(:d).expect!("should have worked") # => raise Errgonomic::ExpectError, "should have worked"
|
95
|
+
def expect!(msg)
|
96
|
+
raise Errgonomic::ExpectError, msg unless ok?
|
97
|
+
|
98
|
+
@value
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return the inner value of an Err, else raise an exception when Ok.
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# Ok(1).unwrap_err! # => raise Errgonomic::UnwrapError, 1
|
105
|
+
# Err(:e).unwrap_err! # => :e
|
106
|
+
def unwrap_err!
|
107
|
+
raise Errgonomic::UnwrapError, value unless err?
|
108
|
+
|
109
|
+
@value
|
110
|
+
end
|
111
|
+
|
112
|
+
# Given another result, return it if the inner result is Ok, else return
|
113
|
+
# the inner Err. Raise an exception if the other value is not a Result.
|
114
|
+
#
|
115
|
+
# @param other [Errgonomic::Result::Any]
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# Ok(1).and(Ok(2)) # => Ok(2)
|
119
|
+
# Ok(1).and(Err(:f)) # => Err(:f)
|
120
|
+
# Err(:g).and(Ok(1)) # => Err(:g)
|
121
|
+
# Err(:h).and(Err(:i)) # => Err(:h)
|
122
|
+
# Ok(1).and(2) # => raise Errgonomic::ArgumentError, "other must be a Result"
|
123
|
+
def and(other)
|
124
|
+
raise Errgonomic::ArgumentError, 'other must be a Result' unless other.is_a?(Errgonomic::Result::Any)
|
125
|
+
return self if err?
|
126
|
+
|
127
|
+
other
|
128
|
+
end
|
129
|
+
|
130
|
+
# Given a block, evaluate it and return its result if the inner result is
|
131
|
+
# Ok, else return the inner Err. This is lazy evaluated, and we
|
132
|
+
# pedantically check the type of the block's return value at runtime. This
|
133
|
+
# is annoying, sorry, but better than an "undefined method" error.
|
134
|
+
# Hopefully it gives your test suite a chance to detect incorrect usage.
|
135
|
+
#
|
136
|
+
# @param block [Proc]
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# Ok(1).and_then { |x| Ok(x + 1) } # => Ok(2)
|
140
|
+
# Ok(1).and_then { |_| Err(:error) } # => Err(:error)
|
141
|
+
# Err(:error).and_then { |x| Ok(x + 1) } # => Err(:error)
|
142
|
+
# Err(:error).and_then { |x| Err(:error2) } # => Err(:error)
|
143
|
+
def and_then(&block)
|
144
|
+
return self if err?
|
145
|
+
|
146
|
+
res = block.call(value)
|
147
|
+
if !res.is_a?(Errgonomic::Result::Any) && Errgonomic.give_me_ambiguous_downstream_errors?
|
148
|
+
raise Errgonomic::ArgumentError, 'and_then block must return a Result'
|
149
|
+
end
|
150
|
+
|
151
|
+
res
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return other if self is Err, else return the original Option. Raises a
|
155
|
+
# pedantic runtime exception if other is not a Result.
|
156
|
+
#
|
157
|
+
# @param other [Errgonomic::Result::Any]
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
# Err(:j).or(Ok(1)) # => Ok(1)
|
161
|
+
# Err(:k).or(Err(:l)) # => Err(:l)
|
162
|
+
# Err(:m).or("oops") # => raise Errgonomic::ArgumentError, "other must be a Result; you might want unwrap_or"
|
163
|
+
def or(other)
|
164
|
+
unless other.is_a?(Errgonomic::Result::Any)
|
165
|
+
raise Errgonomic::ArgumentError,
|
166
|
+
'other must be a Result; you might want unwrap_or'
|
167
|
+
end
|
168
|
+
return other if err?
|
169
|
+
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Return self if it is Ok, else lazy evaluate the block and return its
|
174
|
+
# result. Raises a pedantic runtime check that the block returns a Result.
|
175
|
+
# Sorry about that, hopefully it helps your tests. Better than ambiguous
|
176
|
+
# downstream "undefined method" errors, probably.
|
177
|
+
#
|
178
|
+
# @param block [Proc]
|
179
|
+
#
|
180
|
+
# @example
|
181
|
+
# Ok(1).or_else { Ok(2) } # => Ok(1)
|
182
|
+
# Err(:o).or_else { Ok(1) } # => Ok(1)
|
183
|
+
# Err(:q).or_else { Err(:r) } # => Err(:r)
|
184
|
+
# Err(:s).or_else { "oops" } # => raise Errgonomic::ArgumentError, "or_else block must return a Result"
|
185
|
+
def or_else(&block)
|
186
|
+
return self if ok?
|
187
|
+
|
188
|
+
res = block.call(self)
|
189
|
+
if !res.is_a?(Errgonomic::Result::Any) && Errgonomic.give_me_ambiguous_downstream_errors?
|
190
|
+
raise Errgonomic::ArgumentError, 'or_else block must return a Result'
|
191
|
+
end
|
192
|
+
|
193
|
+
res
|
194
|
+
end
|
195
|
+
|
196
|
+
# Return the inner value if self is Ok, else return the provided default.
|
197
|
+
#
|
198
|
+
# @param other [Object]
|
199
|
+
#
|
200
|
+
# @example
|
201
|
+
# Ok(1).unwrap_or(2) # => 1
|
202
|
+
# Err(:t).unwrap_or(:u) # => :u
|
203
|
+
def unwrap_or(other)
|
204
|
+
return value if ok?
|
205
|
+
|
206
|
+
other
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return the inner value if self is Ok, else lazy evaluate the block and
|
210
|
+
# return its result.
|
211
|
+
#
|
212
|
+
# @param block [Proc]
|
213
|
+
#
|
214
|
+
# @example
|
215
|
+
# Ok(1).unwrap_or_else { 2 } # => 1
|
216
|
+
# Err(:v).unwrap_or_else { :w } # => :w
|
217
|
+
def unwrap_or_else(&block)
|
218
|
+
return value if ok?
|
219
|
+
|
220
|
+
block.call(self)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# The Ok variant.
|
225
|
+
class Ok < Any
|
226
|
+
attr_accessor :value
|
227
|
+
|
228
|
+
# Ok is always ok
|
229
|
+
#
|
230
|
+
# @example
|
231
|
+
# Ok(1).ok? # => true
|
232
|
+
def ok?
|
233
|
+
true
|
234
|
+
end
|
235
|
+
|
236
|
+
# Ok is never err
|
237
|
+
#
|
238
|
+
# @example
|
239
|
+
# Ok(1).err? # => false
|
240
|
+
def err?
|
241
|
+
false
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class Err < Any
|
246
|
+
class Arbitrary; end
|
247
|
+
|
248
|
+
attr_accessor :value
|
249
|
+
|
250
|
+
# Err may be constructed without a value, if you want.
|
251
|
+
#
|
252
|
+
# @example
|
253
|
+
# Err(:y).value # => :y
|
254
|
+
# Err().value # => Arbitrary
|
255
|
+
def initialize(value = Arbitrary)
|
256
|
+
super(value)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Err is always err
|
260
|
+
#
|
261
|
+
# @example
|
262
|
+
# Err(:z).err? # => true
|
263
|
+
def err?
|
264
|
+
true
|
265
|
+
end
|
266
|
+
|
267
|
+
# Err is never ok
|
268
|
+
#
|
269
|
+
# @example
|
270
|
+
# Err(:A).ok? # => false
|
271
|
+
def ok?
|
272
|
+
false
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Introduce certain helper methods into the Object class.
|
279
|
+
#
|
280
|
+
# @example
|
281
|
+
# "foo".result? # => false
|
282
|
+
# "foo".assert_result! # => raise Errgonomic::ResultRequiredError
|
283
|
+
class Object
|
284
|
+
# Convenience method to indicate whether we are working with a result.
|
285
|
+
# TBD whether we implement some stubs for the rest of the Result API; I want
|
286
|
+
# to think about how effectively these map to truthiness or presence.
|
287
|
+
#
|
288
|
+
# @example
|
289
|
+
# "foo".result? # => false
|
290
|
+
# Ok("foo").result? # => true
|
291
|
+
def result?
|
292
|
+
false
|
293
|
+
end
|
294
|
+
|
295
|
+
# Lacking static typing, we are going to want to make it easy to enforce at
|
296
|
+
# runtime that a given object is a Result.
|
297
|
+
#
|
298
|
+
# @example
|
299
|
+
# "foo".assert_result! # => raise Errgonomic::ResultRequiredError
|
300
|
+
# Ok("foo").assert_result! # => true
|
301
|
+
def assert_result!
|
302
|
+
return true if result?
|
303
|
+
|
304
|
+
raise Errgonomic::ResultRequiredError
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# Global convenience method for constructing an Ok result.
|
309
|
+
def Ok(value)
|
310
|
+
Errgonomic::Result::Ok.new(value)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Global convenience method for constructing an Err result.
|
314
|
+
def Err(value = Errgonomic::Result::Err::Arbitrary)
|
315
|
+
Errgonomic::Result::Err.new(value)
|
316
|
+
end
|
data/lib/errgonomic/version.rb
CHANGED
data/lib/errgonomic.rb
CHANGED
@@ -1,59 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'errgonomic/version' unless defined?(Errgonomic::VERSION)
|
4
4
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
# A more opinionated blend with Rails presence.
|
6
|
+
require_relative 'errgonomic/presence'
|
7
|
+
|
8
|
+
# Bring in our Option and Result.
|
9
|
+
require_relative 'errgonomic/option'
|
10
|
+
require_relative 'errgonomic/result'
|
11
11
|
|
12
|
+
# Errgonomic adds opinionated abstractions to handle errors in a way that blends
|
13
|
+
# Rust and Ruby ergonomics. This library leans on Rails conventions for some
|
14
|
+
# presence-related methods; when in doubt, make those feel like Rails. It also
|
15
|
+
# has an implementation of Option and Result; when in doubt, make those feel
|
16
|
+
# more like Rust.
|
12
17
|
module Errgonomic
|
13
18
|
class Error < StandardError; end
|
14
19
|
|
15
20
|
class NotPresentError < Error; end
|
16
21
|
|
17
22
|
class TypeMismatchError < Error; end
|
18
|
-
end
|
19
23
|
|
20
|
-
class
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
class UnwrapError < Error; end
|
25
|
+
|
26
|
+
class ExpectError < Error; end
|
27
|
+
|
28
|
+
class ArgumentError < Error; end
|
29
|
+
|
30
|
+
class ResultRequiredError < Error; end
|
31
|
+
|
32
|
+
class NotComparableError < StandardError; end
|
33
|
+
|
34
|
+
# A little bit of control over how pedantic we are in our runtime type checks.
|
35
|
+
def self.give_me_ambiguous_downstream_errors?
|
36
|
+
@give_me_ambiguous_downstream_errors || true
|
37
|
+
end
|
38
|
+
|
39
|
+
# You can opt out of the pedantic runtime checks for lazy block evaluation,
|
40
|
+
# but not quietly.
|
41
|
+
def self.with_ambiguous_downstream_errors
|
42
|
+
original_value = @give_me_ambiguous_downstream_errors
|
43
|
+
@give_me_ambiguous_downstream_errors = true
|
44
|
+
yield
|
45
|
+
ensure
|
46
|
+
@give_me_ambiguous_downstream_errors = original_value
|
30
47
|
end
|
31
48
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
# @param value [Object] The value to return if the receiver is not present.
|
37
|
-
# @return [Object] The receiver if it is present, otherwise the given value.
|
38
|
-
def present_or(value)
|
39
|
-
# TBD whether this is *too* strict
|
40
|
-
if value.class != self.class && self.class != NilClass
|
41
|
-
raise Errgonomic::TypeMismatchError, "Type mismatch: default value is a #{value.class} but original was a #{self.class}"
|
42
|
-
end
|
43
|
-
|
44
|
-
return self if present?
|
45
|
-
|
46
|
-
value
|
49
|
+
# Lenient inner value comparison means the inner value of a Some or Ok can be
|
50
|
+
# compared to some other non-Result or non-Option value.
|
51
|
+
def self.lenient_inner_value_comparison?
|
52
|
+
@lenient_inner_value_comparison ||= true
|
47
53
|
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
# +present_or+, if constructing the default value is expensive.
|
52
|
-
#
|
53
|
-
# @param block [Proc] The block to call if the receiver is not present.
|
54
|
-
# @return [Object] The receiver if it is present, otherwise the result of the block.
|
55
|
-
def present_or_else(&block)
|
56
|
-
return block.call if blank?
|
57
|
-
self
|
55
|
+
def self.give_me_lenient_inner_value_comparison=(value)
|
56
|
+
@lenient_inner_value_comparison = value
|
58
57
|
end
|
59
58
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: errgonomic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Zadrozny
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard-doctest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.1'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.1'
|
27
55
|
description: Let's blend the Rails 'present' and 'blank' conventions with a few patterns
|
28
56
|
from Rust Option types.
|
29
57
|
email:
|
@@ -34,17 +62,23 @@ extra_rdoc_files: []
|
|
34
62
|
files:
|
35
63
|
- ".envrc"
|
36
64
|
- ".rspec"
|
65
|
+
- ".rubocop.yml"
|
37
66
|
- ".standard.yml"
|
67
|
+
- ".yardopts"
|
38
68
|
- CHANGELOG.md
|
39
69
|
- CODE_OF_CONDUCT.md
|
40
70
|
- LICENSE.txt
|
41
71
|
- README.md
|
42
72
|
- Rakefile
|
73
|
+
- doctest_helper.rb
|
43
74
|
- flake.lock
|
44
75
|
- flake.nix
|
45
76
|
- gemset.nix
|
46
77
|
- lib/errgonomic.rb
|
47
78
|
- lib/errgonomic/core_ext/blank.rb
|
79
|
+
- lib/errgonomic/option.rb
|
80
|
+
- lib/errgonomic/presence.rb
|
81
|
+
- lib/errgonomic/result.rb
|
48
82
|
- lib/errgonomic/version.rb
|
49
83
|
- sig/errgonomic.rbs
|
50
84
|
homepage: https://omc.io/
|