tins 1.43.0 → 1.44.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/.contexts/code_comment.rb +5 -8
- data/.contexts/lib.rb +0 -2
- data/CHANGES.md +12 -0
- data/README.md +158 -6
- data/Rakefile +19 -16
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/turing.rb +3 -1
- data/lib/tins/alias.rb +1 -0
- data/lib/tins/annotate.rb +37 -27
- data/lib/tins/ask_and_send.rb +41 -0
- data/lib/tins/attempt.rb +39 -0
- data/lib/tins/bijection.rb +34 -0
- data/lib/tins/case_predicate.rb +21 -0
- data/lib/tins/complete.rb +16 -0
- data/lib/tins/concern.rb +64 -0
- data/lib/tins/date_dummy.rb +36 -4
- data/lib/tins/date_time_dummy.rb +34 -2
- data/lib/tins/deep_dup.rb +9 -2
- data/lib/tins/deprecate.rb +12 -0
- data/lib/tins/dslkit.rb +559 -83
- data/lib/tins/duration.rb +120 -5
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +104 -21
- data/lib/tins/find.rb +114 -11
- data/lib/tins/generator.rb +10 -2
- data/lib/tins/go.rb +81 -4
- data/lib/tins/hash_bfs.rb +4 -2
- data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
- data/lib/tins/hash_union.rb +47 -2
- data/lib/tins/if_predicate.rb +31 -0
- data/lib/tins/implement.rb +50 -0
- data/lib/tins/limited.rb +54 -5
- data/lib/tins/lines_file.rb +81 -2
- data/lib/tins/lru_cache.rb +54 -17
- data/lib/tins/memoize.rb +86 -58
- data/lib/tins/method_description.rb +87 -4
- data/lib/tins/minimize.rb +39 -11
- data/lib/tins/module_group.rb +27 -2
- data/lib/tins/named_set.rb +20 -0
- data/lib/tins/null.rb +86 -15
- data/lib/tins/once.rb +61 -4
- data/lib/tins/p.rb +44 -8
- data/lib/tins/partial_application.rb +66 -7
- data/lib/tins/proc_compose.rb +58 -1
- data/lib/tins/proc_prelude.rb +97 -10
- data/lib/tins/range_plus.rb +30 -2
- data/lib/tins/require_maybe.rb +36 -0
- data/lib/tins/responding.rb +39 -0
- data/lib/tins/secure_write.rb +24 -4
- data/lib/tins/sexy_singleton.rb +45 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +179 -10
- data/lib/tins/subhash.rb +35 -10
- data/lib/tins/temp_io.rb +7 -0
- data/lib/tins/temp_io_enum.rb +19 -0
- data/lib/tins/terminal.rb +31 -9
- data/lib/tins/thread_local.rb +67 -5
- data/lib/tins/time_dummy.rb +46 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +56 -1
- data/lib/tins/unit.rb +288 -149
- data/lib/tins/version.rb +1 -1
- data/lib/tins/write.rb +14 -3
- data/lib/tins/xt/blank.rb +81 -2
- data/lib/tins/xt/concern.rb +51 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/irb.rb +46 -2
- data/lib/tins/xt/method_description.rb +0 -12
- data/lib/tins/xt/minimize.rb +7 -0
- data/lib/tins/xt/named.rb +71 -16
- data/lib/tins/xt/proc_compose.rb +4 -0
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt.rb +1 -3
- data/lib/tins.rb +16 -3
- data/tests/duration_test.rb +4 -0
- data/tests/from_module_test.rb +30 -2
- data/tests/implement_test.rb +6 -8
- data/tests/lines_file_test.rb +2 -0
- data/tests/lru_cache_test.rb +12 -0
- data/tests/method_description_test.rb +14 -20
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/scope_test.rb +1 -1
- data/tests/string_version_test.rb +2 -0
- data/tests/to_test.rb +6 -6
- data/tins.gemspec +9 -9
- metadata +23 -41
- data/lib/tins/count_by.rb +0 -21
- data/lib/tins/deep_const_get.rb +0 -64
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -23
- data/lib/tins/xt/count_by.rb +0 -7
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -25
- data/tests/count_by_test.rb +0 -17
- data/tests/deep_const_get_test.rb +0 -37
- data/tests/uniq_by_test.rb +0 -31
- /data/{COPYING → LICENSE} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85b86d026f46e89213c6a4716ccd6296b4a8990b2fcd20ac8aa12a227c2f7a9a
|
4
|
+
data.tar.gz: b85c6e3f69fa911e10bb801fca3197949877bc6908bd565167c5dbb67c6d8248
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb41548efe7a7c5628c225c89fe81417081a7aff6f5760a7ac21d5490731468488a25d0d795b4a932ebe516caf3d0d4722f2f645b674454b8b014791b44ecf88
|
7
|
+
data.tar.gz: 50eb96f4b5a54a4e38d0282ac926752c792acf8a7d09e61bb12eaad725e7f17bcd7195043f69a9c5d59a23b77d99a0461012474dbb6564c7e4b87ad40e0d4c0c
|
data/.contexts/code_comment.rb
CHANGED
@@ -5,22 +5,19 @@ context do
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
-
namespace "tests" do
|
9
|
-
Dir['tests/**/*.rb'].each do |filename|
|
10
|
-
file filename, tags: 'test'
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
8
|
|
15
9
|
file 'README.md', tags: 'documentation'
|
16
10
|
|
17
11
|
file '.contexts/yard.md', tags: [ 'yard', 'cheatsheet' ]
|
18
12
|
|
19
|
-
meta
|
13
|
+
meta guidelines: <<~EOT
|
20
14
|
# Guidelines for creating YARD documentation
|
21
15
|
|
22
16
|
- Look into the file, with tags yard and cheatsheet for how comment ruby
|
23
17
|
constructs.
|
24
|
-
- In comments above initialize methods
|
18
|
+
- In comments above initialize methods **ALWAYS** omit @return.
|
19
|
+
- **NEVER** output @return [ void ] in comments of any method, because
|
20
|
+
in Ruby every method returns something. If you don't know or if the
|
21
|
+
method is just called because its side effect just omit the @return.
|
25
22
|
EOT
|
26
23
|
end
|
data/.contexts/lib.rb
CHANGED
data/CHANGES.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 2025-09-12 v1.44.0
|
4
|
+
|
5
|
+
### Major Changes
|
6
|
+
|
7
|
+
- **Ruby Version Requirement**: Updated minimum Ruby version requirement to 3.1
|
8
|
+
- **Dependency Modernization**: Replaced deprecated `Tins::Memoize` module
|
9
|
+
implementation with `mize` gem for memoization functionality
|
10
|
+
- **Documentation Overhaul**: Comprehensive YARD documentation added across all
|
11
|
+
modules with examples and parameter descriptions
|
12
|
+
- **README Enhancement**: Improved README.md with better documentation,
|
13
|
+
examples, and usage instructions
|
14
|
+
|
3
15
|
## 2025-09-05 v1.43.0
|
4
16
|
|
5
17
|
- Added new `dsl_lazy_accessor` method that creates lazy-loaded accessors with
|
data/README.md
CHANGED
@@ -2,18 +2,170 @@
|
|
2
2
|
|
3
3
|
## Description
|
4
4
|
|
5
|
-
|
5
|
+
A collection of useful Ruby utilities that extend the standard library with
|
6
|
+
practical conveniences. Tins provides lightweight, dependency-free tools for
|
7
|
+
common programming tasks.
|
6
8
|
|
7
|
-
##
|
9
|
+
## Documentation
|
8
10
|
|
9
|
-
[
|
11
|
+
Complete API documentation is available at: [RubyDoc.info](https://rubydoc.info/gems/tins)
|
10
12
|
|
11
|
-
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'tins'
|
19
|
+
```
|
20
|
+
|
21
|
+
Or:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'tins', require 'tins/xt'
|
25
|
+
```
|
26
|
+
|
27
|
+
to automatically extend some core classes with useful methods.
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
$ bundle install
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
|
35
|
+
$ gem install tins
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# Load all utilities
|
41
|
+
|
42
|
+
require 'tins'
|
43
|
+
|
44
|
+
# Load all utilities and extends core classes with useful methods
|
45
|
+
require 'tins/xt'
|
46
|
+
```
|
47
|
+
## Some Usage Examples
|
48
|
+
|
49
|
+
### Duration Handling
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'tins/duration'
|
53
|
+
|
54
|
+
duration = Tins::Duration.new(9000)
|
55
|
+
puts duration.to_s # "02:30:00"
|
56
|
+
puts duration.to_i # 9000 (seconds)
|
57
|
+
|
58
|
+
# Parse durations from strings
|
59
|
+
Tins::Duration.parse('2h 30m', template: '%hh %mm') # 9000 (seconds)
|
60
|
+
```
|
61
|
+
|
62
|
+
### Unit Conversion
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
require 'tins/unit'
|
66
|
+
|
67
|
+
bytes = Tins::Unit.parse('1.5 GB', unit: ?B).to_i # => 1610612736
|
68
|
+
puts Tins::Unit.format(bytes, unit: 'B') # "1.500000 GB"
|
69
|
+
```
|
70
|
+
|
71
|
+
### Secure File Writing
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'tins/xt/secure_write'
|
75
|
+
|
76
|
+
# Write files safely (atomic operation)
|
77
|
+
File.secure_write('config.json', '{"key": "value"}')
|
78
|
+
```
|
79
|
+
|
80
|
+
### Time Freezing for Testing
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
require 'tins/xt/time_freezer'
|
84
|
+
|
85
|
+
# Freeze time during testing
|
86
|
+
Tins::TimeFreezer.freeze(Time.new('2011-12-13 14:15:16')) do
|
87
|
+
puts Time.now # Always returns the frozen time
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
### Building blocks for DSLs
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class Foo
|
95
|
+
include Tins::DynamicScope
|
96
|
+
|
97
|
+
def let(bindings = {})
|
98
|
+
dynamic_scope do
|
99
|
+
bindings.each { |name, value| send("#{name}=", value) }
|
100
|
+
yield
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def twice(x)
|
105
|
+
2 * x
|
106
|
+
end
|
107
|
+
|
108
|
+
def test
|
109
|
+
let x: 1, y: twice(1) do
|
110
|
+
let z: twice(x) do
|
111
|
+
"#{x} * #{y} == #{z} # => #{x * y == twice(x)}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
Foo.new.test # "1 * 2 == 2 # => true"
|
118
|
+
```
|
119
|
+
|
120
|
+
### Core Class Extensions (xt)
|
121
|
+
|
122
|
+
When you require `tins/xt`, some useful methods are added to core classes:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
default_options = {
|
126
|
+
format: :json,
|
127
|
+
timeout: 30,
|
128
|
+
retries: 3
|
129
|
+
}
|
130
|
+
|
131
|
+
user_options = { timeout: 60 }
|
132
|
+
options = user_options | default_options
|
133
|
+
# => { format: :json, timeout: 60, retries: 3 }
|
134
|
+
|
135
|
+
'1.10.3'.version < '1.9.2'.version # => false
|
136
|
+
|
137
|
+
add_one = -> x { x + 1 }
|
138
|
+
multiply_by_two = -> x { x * 2 }
|
139
|
+
composed = multiply_by_two * add_one
|
140
|
+
composed.(5) # => 12
|
141
|
+
|
142
|
+
# For Testing
|
143
|
+
>> o = Object.new
|
144
|
+
>> o.puts # => private method, NoMethodError
|
145
|
+
>> o = o.expose
|
146
|
+
>> o.puts "hello"
|
147
|
+
hello
|
148
|
+
```
|
149
|
+
|
150
|
+
### Hash Symbolization
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
require 'tins/hash_symbolize_keys_recursive'
|
154
|
+
|
155
|
+
hash = {
|
156
|
+
'name' => 'John',
|
157
|
+
'age' => 30,
|
158
|
+
'address' => {
|
159
|
+
'street' => '123 Main St'
|
160
|
+
}
|
161
|
+
}
|
162
|
+
hash.symbolize_keys_recursive! # Converts all keys to symbols recursively
|
163
|
+
```
|
12
164
|
|
13
165
|
## Author
|
14
166
|
|
15
|
-
Florian Frank
|
167
|
+
[Florian Frank](mailto:flori@ping.de)
|
16
168
|
|
17
169
|
## License
|
18
170
|
|
19
|
-
|
171
|
+
[MIT License](./LICENSE)
|
data/Rakefile
CHANGED
@@ -3,30 +3,33 @@
|
|
3
3
|
require 'gem_hadar'
|
4
4
|
|
5
5
|
GemHadar do
|
6
|
-
name
|
7
|
-
author
|
8
|
-
email
|
9
|
-
homepage
|
10
|
-
summary
|
11
|
-
description
|
12
|
-
test_dir
|
6
|
+
name 'tins'
|
7
|
+
author 'Florian Frank'
|
8
|
+
email 'flori@ping.de'
|
9
|
+
homepage "https://github.com/flori/#{name}"
|
10
|
+
summary 'Useful stuff.'
|
11
|
+
description 'All the stuff that isn\'t good/big enough for a real library.'
|
12
|
+
test_dir 'tests'
|
13
13
|
test_files.concat Dir["#{test_dir}/*_test.rb"]
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'.
|
14
|
+
doc_code_files files.grep(%r(\Alib/))
|
15
|
+
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.rvmrc', 'coverage',
|
16
|
+
'.rbx', '.AppleDouble', '.DS_Store', 'tags', 'cscope.out', '.bundle',
|
17
|
+
'.byebug_history', '.yardoc', 'doc', 'TODO.md'
|
18
|
+
package_ignore '.all_images.yml', '.tool-versions', '.gitignore',
|
19
|
+
'VERSION', '.utilsrc', '.github', '.contexts'
|
18
20
|
|
19
|
-
readme
|
20
|
-
licenses <<
|
21
|
+
readme 'README.md'
|
22
|
+
licenses << 'MIT'
|
23
|
+
clean << 'coverage'
|
21
24
|
|
22
|
-
required_ruby_version '>=
|
25
|
+
required_ruby_version '>= 3.1'
|
23
26
|
|
24
27
|
dependency 'sync'
|
25
28
|
dependency 'bigdecimal'
|
29
|
+
dependency 'mize', '~> 0.6'
|
26
30
|
development_dependency 'all_images'
|
27
|
-
development_dependency 'context_spook', '~> 0.2'
|
28
31
|
development_dependency 'debug'
|
29
32
|
development_dependency 'simplecov'
|
30
33
|
development_dependency 'term-ansicolor'
|
31
|
-
development_dependency 'test-unit', '~> 3.
|
34
|
+
development_dependency 'test-unit', '~> 3.7'
|
32
35
|
end
|
data/examples/let.rb
CHANGED
@@ -2,48 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'tins'
|
4
4
|
|
5
|
-
LetScope = Tins::BlankSlate.with :instance_eval, :to_s, :inspect, :extend
|
6
|
-
class LetScope
|
7
|
-
include Tins::MethodMissingDelegator::DelegatorModule
|
8
|
-
include Tins::BlockSelf
|
9
|
-
|
10
|
-
def initialize(my_self, bindings = {}, outer_scope = nil)
|
11
|
-
super(my_self)
|
12
|
-
@outer_scope = outer_scope
|
13
|
-
@bindings = bindings
|
14
|
-
extend Tins::Eigenclass
|
15
|
-
eigenclass_eval { extend Tins::Constant }
|
16
|
-
each_binding do |name, value|
|
17
|
-
eigenclass_eval { constant name, value }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def each_binding(&block)
|
22
|
-
if @outer_scope
|
23
|
-
@outer_scope.each_binding(&block)
|
24
|
-
end
|
25
|
-
@bindings.each(&block)
|
26
|
-
end
|
27
|
-
|
28
|
-
def let(bindings = {}, &block)
|
29
|
-
ls = LetScope.new(block_self(&block), bindings, self)
|
30
|
-
ls.instance_eval(&block)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Including this module into your current namespace defines the let method.
|
34
|
-
module Include
|
35
|
-
include Tins::BlockSelf
|
36
|
-
|
37
|
-
def let(bindings = {}, &block)
|
38
|
-
ls = LetScope.new(block_self(&block), bindings)
|
39
|
-
ls.instance_eval(&block)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
5
|
if $0 == __FILE__
|
45
6
|
class Foo
|
46
|
-
include
|
7
|
+
include Tins::DynamicScope
|
8
|
+
|
9
|
+
def let(bindings = {})
|
10
|
+
dynamic_scope do
|
11
|
+
bindings.each { |name, value| send("#{name}=", value) }
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
47
15
|
|
48
16
|
def twice(x)
|
49
17
|
2 * x
|
data/examples/mail.rb
CHANGED
data/examples/turing.rb
CHANGED
@@ -307,6 +307,8 @@ if $0 == __FILE__ and ARGV.any?
|
|
307
307
|
else
|
308
308
|
raise "unknown turing machine suffix: #{ext}, use .stm or .mtm"
|
309
309
|
end
|
310
|
-
tm =
|
310
|
+
tm = File.open(filename) do |file|
|
311
|
+
machine_type.new(file)
|
312
|
+
end
|
311
313
|
$DEBUG ? tm.step(*tapes) : tm.run(*tapes)
|
312
314
|
end
|
data/lib/tins/alias.rb
CHANGED
data/lib/tins/annotate.rb
CHANGED
@@ -1,38 +1,48 @@
|
|
1
|
-
module Tins
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Tins
|
2
|
+
# A module for adding annotations to classes and methods
|
3
|
+
#
|
4
|
+
# This module provides functionality to add metadata annotations to classes and
|
5
|
+
# methods, allowing for enhanced documentation and introspection capabilities
|
6
|
+
module Annotate
|
7
|
+
# The annotate method sets up annotation functionality for a given name
|
8
|
+
# by defining methods to set and retrieve annotations on class methods
|
9
|
+
#
|
10
|
+
# @param name [ Symbol ] the name of the annotation to define
|
11
|
+
def annotate(name)
|
12
|
+
singleton_class.class_eval do
|
13
|
+
define_method(name) do |annotation = :annotated|
|
14
|
+
instance_variable_set "@__annotation_#{name}__", annotation
|
15
|
+
end
|
7
16
|
|
8
|
-
|
9
|
-
|
10
|
-
|
17
|
+
define_method("#{name}_of") do |method_name|
|
18
|
+
__send__("#{name}_annotations")[method_name]
|
19
|
+
end
|
11
20
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
21
|
+
define_method("#{name}_annotations") do
|
22
|
+
if instance_variable_defined?("@__annotation_#{name}_annotations__")
|
23
|
+
instance_variable_get "@__annotation_#{name}_annotations__"
|
24
|
+
else
|
25
|
+
instance_variable_set "@__annotation_#{name}_annotations__", {}
|
26
|
+
end
|
17
27
|
end
|
18
|
-
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
29
|
+
old_method_added = instance_method(:method_added)
|
30
|
+
define_method(:method_added) do |method_name|
|
31
|
+
old_method_added.bind(self).call method_name
|
32
|
+
if annotation = instance_variable_get("@__annotation_#{name}__")
|
33
|
+
__send__("#{name}_annotations")[method_name] = annotation
|
34
|
+
end
|
35
|
+
instance_variable_set "@__annotation_#{name}__", nil
|
25
36
|
end
|
26
|
-
instance_variable_set "@__annotation_#{name}__", nil
|
27
37
|
end
|
28
|
-
end
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
define_method("#{name}_annotations") do
|
40
|
+
self.class.__send__("#{name}_annotations")
|
41
|
+
end
|
33
42
|
|
34
|
-
|
35
|
-
|
43
|
+
define_method("#{name}_of") do |method_name|
|
44
|
+
self.class.__send__("#{name}_of", method_name)
|
45
|
+
end
|
36
46
|
end
|
37
47
|
end
|
38
48
|
end
|
data/lib/tins/ask_and_send.rb
CHANGED
@@ -1,17 +1,47 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides methods to call private and protected methods on
|
3
|
+
# objects.
|
2
4
|
module AskAndSend
|
5
|
+
# The ask_and_send method attempts to invoke a given method on the object
|
6
|
+
# if that method is available.
|
7
|
+
#
|
8
|
+
# @param method_name [ Symbol ] the name of the method to invoke
|
9
|
+
# @param args [ Array ] arguments to pass to the method
|
10
|
+
# @yield [ block ] optional block to pass to the method
|
11
|
+
# @return [ Object, nil ] the result of the method call or nil if the
|
12
|
+
# method doesn't exist
|
3
13
|
def ask_and_send(method_name, *args, &block)
|
4
14
|
if respond_to?(method_name)
|
5
15
|
__send__(method_name, *args, &block)
|
6
16
|
end
|
7
17
|
end
|
8
18
|
|
19
|
+
# The ask_and_send! method attempts to invoke a private or protected method
|
20
|
+
# on the object.
|
21
|
+
#
|
22
|
+
# @param method_name [ Symbol ] the name of the method to call
|
23
|
+
# @param args [ Array ] arguments to pass to the method
|
24
|
+
# @param block [ Proc ] optional block to pass to the method
|
25
|
+
#
|
26
|
+
# @return [ Object, nil ] the result of the method call or nil if the
|
27
|
+
# method doesn't exist
|
9
28
|
def ask_and_send!(method_name, *args, &block)
|
10
29
|
if respond_to?(method_name, true)
|
11
30
|
__send__(method_name, *args, &block)
|
12
31
|
end
|
13
32
|
end
|
14
33
|
|
34
|
+
# The ask_and_send_or_self method attempts to invoke the specified method
|
35
|
+
# on the object If the method exists, it calls the method with the provided
|
36
|
+
# arguments and block If the method does not exist, it returns the object
|
37
|
+
# itself
|
38
|
+
#
|
39
|
+
# @param method_name [ Symbol ] the name of the method to call
|
40
|
+
# @param args [ Array ] the arguments to pass to the method
|
41
|
+
# @param block [ Proc ] the block to pass to the method
|
42
|
+
#
|
43
|
+
# @return [ Object ] the result of the method call or self if the method
|
44
|
+
# doesn't exist
|
15
45
|
def ask_and_send_or_self(method_name, *args, &block)
|
16
46
|
if respond_to?(method_name)
|
17
47
|
__send__(method_name, *args, &block)
|
@@ -20,6 +50,17 @@ module Tins
|
|
20
50
|
end
|
21
51
|
end
|
22
52
|
|
53
|
+
# The ask_and_send_or_self! method attempts to send a message to the object
|
54
|
+
# with the given method name and arguments. If the object responds to the
|
55
|
+
# method, it executes the method and returns the result. If the object does
|
56
|
+
# not respond to the method, it returns the object itself.
|
57
|
+
#
|
58
|
+
# @param method_name [ Symbol ] the name of the method to send
|
59
|
+
# @param args [ Array ] the arguments to pass to the method
|
60
|
+
# @param block [ Proc ] the block to pass to the method
|
61
|
+
#
|
62
|
+
# @return [ Object ] the result of the method call or the object itself if
|
63
|
+
# the method is not found
|
23
64
|
def ask_and_send_or_self!(method_name, *args, &block)
|
24
65
|
if respond_to?(method_name, true)
|
25
66
|
__send__(method_name, *args, &block)
|
data/lib/tins/attempt.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides functionality for attempting operations with error
|
3
|
+
# handling and retry logic.
|
2
4
|
module Attempt
|
3
5
|
# Attempts code in block *attempts* times, sleeping according to *sleep*
|
4
6
|
# between attempts and catching the exception(s) in *exception_class*.
|
@@ -63,6 +65,11 @@ module Tins
|
|
63
65
|
|
64
66
|
private
|
65
67
|
|
68
|
+
# The sleep_duration method handles sleeping for a specified duration or
|
69
|
+
# based on a proc call.
|
70
|
+
#
|
71
|
+
# @param duration [ Numeric, Proc ] the duration to sleep or a proc that
|
72
|
+
# returns the duration
|
66
73
|
def sleep_duration(duration, count)
|
67
74
|
case duration
|
68
75
|
when Numeric
|
@@ -72,6 +79,26 @@ module Tins
|
|
72
79
|
end
|
73
80
|
end
|
74
81
|
|
82
|
+
# Computes the base for exponential backoff that results in a specific
|
83
|
+
# total sleep duration.
|
84
|
+
#
|
85
|
+
# This method solves for the base `x` in the geometric series:
|
86
|
+
# x^0 + x^1 + x^2 + ... + x^(attempts-1) = sleep
|
87
|
+
#
|
88
|
+
# The solution ensures that when using exponential delays with base `x`,
|
89
|
+
# the total time spent across all attempts equals approximately the
|
90
|
+
# specified sleep duration. This method of computation is used if a
|
91
|
+
# negative number of secondes was requested in the attempt method.
|
92
|
+
#
|
93
|
+
# @param sleep [Numeric] The total number of seconds to distribute across
|
94
|
+
# all attempts
|
95
|
+
# @param attempts [Integer] The number of attempts (must be > 2)
|
96
|
+
#
|
97
|
+
# @return [Float] The base for exponential backoff delays
|
98
|
+
# @raise [ArgumentError] If attempts <= 2, or if the sleep parameters are
|
99
|
+
# invalid
|
100
|
+
# @raise [ArgumentError] If the algorithm fails to converge after maximum
|
101
|
+
# iterations
|
75
102
|
def compute_duration_base(sleep, attempts)
|
76
103
|
x1, x2 = 1, sleep
|
77
104
|
attempts <= sleep or raise ArgumentError,
|
@@ -98,6 +125,18 @@ module Tins
|
|
98
125
|
result
|
99
126
|
end
|
100
127
|
|
128
|
+
# The interpret_sleep method determines the sleep behavior for retry attempts.
|
129
|
+
#
|
130
|
+
# @param sleep [Numeric, Proc, nil] the sleep duration or proc to use
|
131
|
+
# between retries, nil if no sleep was requested
|
132
|
+
# @param attempts [Integer] the number of retry attempts
|
133
|
+
#
|
134
|
+
# @return [Proc, nil] a proc that calculates the sleep duration or nil if
|
135
|
+
# no sleep is needed
|
136
|
+
#
|
137
|
+
# @raise [ArgumentError] if a negative sleep value is provided with less
|
138
|
+
# than 3 attempts
|
139
|
+
# @raise [TypeError] if the sleep argument is not Numeric, Proc, or nil
|
101
140
|
def interpret_sleep(sleep, attempts)
|
102
141
|
case sleep
|
103
142
|
when nil
|
data/lib/tins/bijection.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Tins
|
2
|
+
# A hash subclass that ensures bijection between keys and values
|
2
3
|
class Bijection < Hash
|
4
|
+
# Creates a new Bijection instance with key-value pairs.
|
5
|
+
#
|
6
|
+
# @param pairs [Array] an array of key-value pairs to populate the
|
7
|
+
# bijection
|
8
|
+
#
|
9
|
+
# @return [Bijection] a new bijection populated with the provided pairs
|
3
10
|
def self.[](*pairs)
|
4
11
|
pairs.size % 2 == 0 or
|
5
12
|
raise ArgumentError, "odd number of arguments for #{self}"
|
@@ -15,10 +22,20 @@ module Tins
|
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
25
|
+
# The initialize method sets up a new instance with an inverted bijection.
|
26
|
+
#
|
27
|
+
# @param inverted [ Bijection ] the inverted bijection object, defaults to
|
28
|
+
# a new Bijection instance
|
18
29
|
def initialize(inverted = Bijection.new(self))
|
19
30
|
@inverted = inverted
|
20
31
|
end
|
21
32
|
|
33
|
+
# The fill method populates the object with content from a block if it is
|
34
|
+
# empty.
|
35
|
+
#
|
36
|
+
# @return [ self ] returns the object itself after filling or if it was not
|
37
|
+
# empty
|
38
|
+
# @yield [ self ] yields self to the block for population
|
22
39
|
def fill
|
23
40
|
if empty?
|
24
41
|
yield self
|
@@ -27,6 +44,9 @@ module Tins
|
|
27
44
|
self
|
28
45
|
end
|
29
46
|
|
47
|
+
# The freeze method freezes the current object and its inverted attribute.
|
48
|
+
#
|
49
|
+
# @return [Object] the frozen object
|
30
50
|
def freeze
|
31
51
|
r = super
|
32
52
|
unless @inverted.frozen?
|
@@ -35,12 +55,26 @@ module Tins
|
|
35
55
|
r
|
36
56
|
end
|
37
57
|
|
58
|
+
# The []= method assigns a value to a key in the hash and maintains an
|
59
|
+
# inverted index.
|
60
|
+
#
|
61
|
+
# @param key [ Object ] the key to assign
|
62
|
+
# @param value [ Object ] the value to assign
|
63
|
+
#
|
64
|
+
# @return [ Object ] the assigned value
|
65
|
+
#
|
66
|
+
# @note This method will not overwrite existing keys, it will return early
|
67
|
+
# if the key already exists.
|
38
68
|
def []=(key, value)
|
39
69
|
key?(key) and return
|
40
70
|
super
|
41
71
|
@inverted[value] = key
|
42
72
|
end
|
43
73
|
|
74
|
+
# The inverted attribute returns the inverted state of the object.
|
75
|
+
# Creates a new Bijection instance filled with key-value pairs.
|
76
|
+
#
|
77
|
+
# @return [Bijection] a new bijection instance with the specified key-value pairs
|
44
78
|
attr_reader :inverted
|
45
79
|
end
|
46
80
|
end
|
data/lib/tins/case_predicate.rb
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides a predicate method for checking if a value matches
|
3
|
+
# any of the given cases.
|
2
4
|
module CasePredicate
|
5
|
+
# Checks if the object matches any of the given arguments using the ===
|
6
|
+
# operator.
|
7
|
+
#
|
8
|
+
# This method provides pattern matching functionality similar to Ruby's
|
9
|
+
# case/when statements, using the === operator for semantic equality
|
10
|
+
# checking.
|
11
|
+
#
|
12
|
+
# @example Basic type matching
|
13
|
+
# "hello".case?(String, Integer) # => String (matches first argument)
|
14
|
+
# 42.case?(String, Integer) # => Integer (matches first argument)
|
15
|
+
# nil.case?(String, Integer) # => nil (no matches)
|
16
|
+
#
|
17
|
+
# @example Range and pattern matching
|
18
|
+
# 15.case?(1..10, 11..20, 21..30) # => 11..20 (matches range)
|
19
|
+
# "hello world".case?(/foo/, /hello/) # => /hello/ (matches regex)
|
20
|
+
#
|
21
|
+
# @param args [Array] the arguments to check against using === operator
|
22
|
+
# @return [Object, nil] the first matching argument, or nil if no match is
|
23
|
+
# found
|
3
24
|
def case?(*args)
|
4
25
|
args.find { |a| a === self }
|
5
26
|
end
|