philiprehberger-try 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 40ddecd021c85d699464539a71ffa8744a3638e6988a1e1dec760347e838a9b2
4
+ data.tar.gz: f411a171dceece62cbe7c860e042a91dbb1f18d06e5d4bccbb24b67e127e81f2
5
+ SHA512:
6
+ metadata.gz: e56704dcff5128983d1f762c40c93313ec789a311047b746c0b76c038f6c975d793c19c80d70b60ed7d361e1b7fd7d93a51115e5d34ced8097ed36854ea8d699
7
+ data.tar.gz: 119a1a224d8cc61d6077ffaef468a62a717a9c5e39081ca021e6831c544f605f06944233091279d4e8088a9fc530ef16e099b58001a9b9f72a0ca6af7c677ba5
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this gem will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-21
11
+
12
+ ### Added
13
+ - Initial release
14
+ - `Try.call` for wrapping risky expressions
15
+ - `Success` and `Failure` result types
16
+ - `.or_else` for default values on failure
17
+ - `.or_try` for chained recovery attempts
18
+ - `.on(ExceptionClass)` for specific error handling
19
+ - `.on_error` for failure side effects
20
+ - `.map` for transforming success values
21
+ - Optional timeout support
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # philiprehberger-try
2
+
3
+ [![Tests](https://github.com/philiprehberger/rb-try/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-try/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-try.svg)](https://rubygems.org/gems/philiprehberger-try)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Concise error handling with fallbacks, chained recovery, and timeout wrapping
8
+
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "philiprehberger-try"
19
+ ```
20
+
21
+ Then run:
22
+
23
+ ```sh
24
+ bundle install
25
+ ```
26
+
27
+ Or install directly:
28
+
29
+ ```sh
30
+ gem install philiprehberger-try
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```ruby
36
+ require "philiprehberger/try"
37
+ ```
38
+
39
+ ### Basic wrapping
40
+
41
+ Wrap any risky expression with `Try.call`. It returns a `Success` or `Failure`:
42
+
43
+ ```ruby
44
+ result = Philiprehberger::Try.call { Integer("42") }
45
+ result.success? # => true
46
+ result.value # => 42
47
+
48
+ result = Philiprehberger::Try.call { Integer("nope") }
49
+ result.failure? # => true
50
+ result.error # => #<ArgumentError: invalid value for Integer(): "nope">
51
+ ```
52
+
53
+ ### Default values with `or_else`
54
+
55
+ Provide a fallback value when the operation fails:
56
+
57
+ ```ruby
58
+ value = Philiprehberger::Try.call { Integer("nope") }
59
+ .or_else(0)
60
+ .value
61
+
62
+ value # => 0
63
+ ```
64
+
65
+ ### Chained recovery with `or_try`
66
+
67
+ Chain multiple recovery strategies. The first success wins:
68
+
69
+ ```ruby
70
+ result = Philiprehberger::Try
71
+ .call { fetch_from_cache(key) }
72
+ .or_try { fetch_from_database(key) }
73
+ .or_try { fetch_from_api(key) }
74
+
75
+ result.value! # returns the first successful result or raises
76
+ ```
77
+
78
+ ### Handling specific exceptions with `on`
79
+
80
+ Recover from specific exception types:
81
+
82
+ ```ruby
83
+ result = Philiprehberger::Try.call { parse_config(path) }
84
+ .on(Errno::ENOENT) { |_e| default_config }
85
+ .on(JSON::ParserError) { |e| raise ConfigError, "Invalid JSON: #{e.message}" }
86
+ ```
87
+
88
+ ### Side effects with `on_error`
89
+
90
+ Log or report errors without changing the result:
91
+
92
+ ```ruby
93
+ result = Philiprehberger::Try.call { risky_operation }
94
+ .on_error { |e| logger.warn("Operation failed: #{e.message}") }
95
+ .or_else(fallback)
96
+ ```
97
+
98
+ ### Transforming values with `map`
99
+
100
+ Transform a successful value. Failures propagate unchanged:
101
+
102
+ ```ruby
103
+ result = Philiprehberger::Try.call { File.read("data.json") }
104
+ .map { |contents| JSON.parse(contents) }
105
+ .map { |data| data.fetch("key") }
106
+
107
+ result.value # => parsed value or nil if any step failed
108
+ ```
109
+
110
+ ### Timeout support
111
+
112
+ Add a timeout constraint to any operation:
113
+
114
+ ```ruby
115
+ result = Philiprehberger::Try.call(timeout: 5) { slow_http_request }
116
+
117
+ result.failure? # => true if it took longer than 5 seconds
118
+ result.error # => #<Timeout::Error: execution expired>
119
+ ```
120
+
121
+ ## API
122
+
123
+ | Method | On Success | On Failure |
124
+ |---|---|---|
125
+ | `Try.call(timeout: nil) { block }` | Returns `Success` wrapping block result | Returns `Failure` wrapping exception |
126
+ | `#value` | Returns the wrapped value | Returns `nil` |
127
+ | `#value!` | Returns the wrapped value | Raises the stored exception |
128
+ | `#success?` | `true` | `false` |
129
+ | `#failure?` | `false` | `true` |
130
+ | `#error` | N/A | Returns the stored exception |
131
+ | `#or_else(default)` | Returns self | Returns `Success.new(default)` |
132
+ | `#or_try { block }` | Returns self | Calls `Try.call` with the block |
133
+ | `#on(ExceptionClass) { block }` | Returns self | If error matches, returns `Try.call { block }` |
134
+ | `#on_error { block }` | Returns self | Calls block for side effect, returns self |
135
+ | `#map { block }` | Wraps block result in new `Try.call` | Returns self |
136
+
137
+ ## Development
138
+
139
+ ```sh
140
+ bundle install
141
+ bundle exec rspec
142
+ bundle exec rubocop
143
+ ```
144
+
145
+ ## License
146
+
147
+ MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module Try
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'timeout'
4
+ require_relative 'try/version'
5
+
6
+ module Philiprehberger
7
+ module Try
8
+ def self.call(timeout: nil)
9
+ value = if timeout
10
+ Timeout.timeout(timeout) { yield }
11
+ else
12
+ yield
13
+ end
14
+ Success.new(value)
15
+ rescue StandardError => e
16
+ Failure.new(e)
17
+ end
18
+
19
+ class Success
20
+ attr_reader :value
21
+
22
+ def initialize(value)
23
+ @value = value
24
+ freeze
25
+ end
26
+
27
+ def value!
28
+ @value
29
+ end
30
+
31
+ def success?
32
+ true
33
+ end
34
+
35
+ def failure?
36
+ false
37
+ end
38
+
39
+ def or_else(_default)
40
+ self
41
+ end
42
+
43
+ def or_try
44
+ self
45
+ end
46
+
47
+ def on(_exception_class)
48
+ self
49
+ end
50
+
51
+ def on_error
52
+ self
53
+ end
54
+
55
+ def map
56
+ Try.call { yield @value }
57
+ end
58
+ end
59
+
60
+ class Failure
61
+ attr_reader :error
62
+
63
+ def initialize(error)
64
+ @error = error
65
+ freeze
66
+ end
67
+
68
+ def value
69
+ nil
70
+ end
71
+
72
+ def value!
73
+ raise @error
74
+ end
75
+
76
+ def success?
77
+ false
78
+ end
79
+
80
+ def failure?
81
+ true
82
+ end
83
+
84
+ def or_else(default)
85
+ Success.new(default)
86
+ end
87
+
88
+ def or_try(&block)
89
+ Try.call(&block)
90
+ end
91
+
92
+ def on(exception_class, &block)
93
+ return Try.call { block.call(@error) } if @error.is_a?(exception_class)
94
+
95
+ self
96
+ end
97
+
98
+ def on_error
99
+ yield @error
100
+ self
101
+ end
102
+
103
+ def map
104
+ self
105
+ end
106
+ end
107
+ end
108
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: philiprehberger-try
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Rehberger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A tiny utility for concise error handling. Wrap risky expressions with
14
+ Try.call, chain fallbacks with or_else and or_try, handle specific exceptions, and
15
+ add timeout constraints — all without verbose begin/rescue blocks.
16
+ email:
17
+ - me@philiprehberger.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - README.md
25
+ - lib/philiprehberger/try.rb
26
+ - lib/philiprehberger/try/version.rb
27
+ homepage: https://github.com/philiprehberger/rb-try
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ homepage_uri: https://github.com/philiprehberger/rb-try
32
+ source_code_uri: https://github.com/philiprehberger/rb-try
33
+ changelog_uri: https://github.com/philiprehberger/rb-try/blob/main/CHANGELOG.md
34
+ bug_tracker_uri: https://github.com/philiprehberger/rb-try/issues
35
+ rubygems_mfa_required: 'true'
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 3.1.0
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.5.22
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Concise error handling with fallbacks, chained recovery, and timeout wrapping
55
+ test_files: []