tins 1.61.0 → 1.62.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/CHANGES.md +67 -0
- data/lib/tins/attempt.rb +47 -9
- data/lib/tins/dslkit.rb +91 -52
- data/lib/tins/duration.rb +342 -53
- data/lib/tins/enum_joined.rb +68 -0
- data/lib/tins/enum_squeeze.rb +33 -0
- data/lib/tins/null.rb +1 -1
- data/lib/tins/version.rb +7 -2
- data/lib/tins/xt/enum_joined.rb +7 -0
- data/lib/tins/xt.rb +1 -0
- data/tests/duration_test.rb +313 -0
- data/tests/enum_joined_test.rb +59 -0
- data/tins.gemspec +7 -7
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 10b0d776e1885f0202af59a976790f05b95819127acc39a51440ca3234922787
|
|
4
|
+
data.tar.gz: 756e1c2582fb33347ab1abf0e2db0964170e23de4ebfe1d4853aae45a2594195
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d83cac4e43f7920378533ded76f233b1fae4f90d263ee1eb8e8d9d0aac200b1f48bced843eb78f0e711a40eea18cc6b3b41a016b157e461d21fa54f45e1c8620
|
|
7
|
+
data.tar.gz: 6abcf2a734c6f57f9c954c71a87c7031b20230971525f3a4f92284ab3b1c00f59fe69ed36fb3c239b7a503f19615700d9788e5264864cc4d2d6cdbe90645b293
|
data/CHANGES.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-11-06 v1.62.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Added `Tins::EnumJoined` module providing `#joined` for human-friendly
|
|
8
|
+
sentence-style joining of `Enumerable` objects.
|
|
9
|
+
- Added `Tins::Duration#to_human` for generating human-readable duration
|
|
10
|
+
strings with configurable styles and connectors.
|
|
11
|
+
- Added `Tins::Duration.parse_human` class method to parse human-readable
|
|
12
|
+
duration strings (inverse of `#to_human`).
|
|
13
|
+
- Added `Tins::Duration.from_string` class method to create a `Duration`
|
|
14
|
+
instance from a parsed string.
|
|
15
|
+
- `Tins::Duration.parse` now accepts an `Array` of template strings, trying
|
|
16
|
+
them in order until a match is found.
|
|
17
|
+
- Added `Tins::Duration::HUMAN_CONNECTORS` constant with `:none`, `:spaces`,
|
|
18
|
+
and `:prose` presets.
|
|
19
|
+
- Added `Tins::Duration::STYLE_DEFAULT_CONNECTOR` constant mapping styles to
|
|
20
|
+
default connector presets.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
|
|
24
|
+
- `Tins::Duration.parse`: Fixed a bug where a literal mismatch raised a
|
|
25
|
+
`RuntimeError` instead of `Tins::Duration::ParserError`.
|
|
26
|
+
- `Tins::Null`: Fixed documentation example to correctly show `user.name.to_s`
|
|
27
|
+
instead of `user.name`.
|
|
28
|
+
|
|
29
|
+
### Improvements
|
|
30
|
+
|
|
31
|
+
- `Tins::Duration`: Refactored parsing logic to extract `parse_template` as a
|
|
32
|
+
private method.
|
|
33
|
+
- `Tins::Duration`: Updated `to_f` documentation to clarify that it returns the
|
|
34
|
+
magnitude (always non-negative) and that sign is tracked via `negative?`.
|
|
35
|
+
- `Tins::Duration`: Changed `template:` default in `parse` to `nil` to support
|
|
36
|
+
multi-template detection.
|
|
37
|
+
- `Tins::Duration`: Extracted connector resolution logic into a private
|
|
38
|
+
`ConnectorResolver` module.
|
|
39
|
+
|
|
40
|
+
### Build & CI
|
|
41
|
+
|
|
42
|
+
- Bumped development dependency `gem_hadar` to require version **2.21.0** or
|
|
43
|
+
later.
|
|
44
|
+
|
|
45
|
+
### Testing
|
|
46
|
+
|
|
47
|
+
- Added tests for `Tins::EnumJoined#joined` covering empty, single, multiple
|
|
48
|
+
elements, custom connectors, and type coercion.
|
|
49
|
+
- Added tests for `Tins::Duration#to_human` covering styles, connectors, max
|
|
50
|
+
units, and negative durations.
|
|
51
|
+
- Added tests for `Tins::Duration.parse_human` covering roundtrips, custom
|
|
52
|
+
connectors, and error cases.
|
|
53
|
+
- Added tests for `Tins::Duration.from_string` covering default and custom
|
|
54
|
+
templates.
|
|
55
|
+
- Added tests for `Tins::Duration.parse` with template arrays, including order
|
|
56
|
+
sensitivity and error handling.
|
|
57
|
+
- Added tests for `Tins::Duration::ParserError` inheritance and specific
|
|
58
|
+
mismatch scenarios.
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
|
|
62
|
+
- `Tins::Attempt`: Rewrote documentation using YARD tags and added examples for
|
|
63
|
+
fixed sleep, exponential backoff, and return-value mode.
|
|
64
|
+
- `Tins::EnumSqueeze`: Added module-level documentation explaining
|
|
65
|
+
`squeeze`/`squeeze!` semantics and usage examples.
|
|
66
|
+
- `Tins::DslKit`: Fixed documentation examples for `Eigenclass`, `ClassMethod`,
|
|
67
|
+
`ThreadGlobal`, `Delegate`, `Scope`, `DynamicScope`, and `Deflect` to
|
|
68
|
+
accurately reflect API usage.
|
|
69
|
+
|
|
3
70
|
## 2026-10-11 v1.61.0
|
|
4
71
|
|
|
5
72
|
* `only_once`/`try_only_once` now accept a `touch:` option to automatically
|
data/lib/tins/attempt.rb
CHANGED
|
@@ -2,17 +2,55 @@ module Tins
|
|
|
2
2
|
# A module that provides functionality for attempting operations with error
|
|
3
3
|
# handling and retry logic.
|
|
4
4
|
module Attempt
|
|
5
|
-
#
|
|
6
|
-
# between
|
|
5
|
+
# Executes the block up to a configured number of attempts, with
|
|
6
|
+
# optional sleeping between retries and exception-based success
|
|
7
|
+
# detection.
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# seconds that is slept before giving up, and every attempt is retried
|
|
12
|
-
# after a exponentially increasing duration of seconds.
|
|
9
|
+
# When +:exception_class+ is set (default +StandardError+), the block
|
|
10
|
+
# succeeds if it returns without raising. When it is +nil+, the block's
|
|
11
|
+
# truthy return value determines success instead.
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
13
|
+
# @param opts [Hash, Numeric] Configuration hash, or a legacy
|
|
14
|
+
# attempt count.
|
|
15
|
+
# @option opts [Integer] :attempts (1) Number of tries.
|
|
16
|
+
# @option opts [Class, Array<Class>, nil] :exception_class
|
|
17
|
+
# (StandardError) Exception(s) to rescue. Pass +nil+ to use
|
|
18
|
+
# return-value mode instead.
|
|
19
|
+
# @option opts [Numeric, Proc, nil] :sleep (nil) Sleep between
|
|
20
|
+
# attempts. A non-negative Numeric is a fixed duration in seconds.
|
|
21
|
+
# A negative Numeric is a total budget — each retry sleeps an
|
|
22
|
+
# exponentially increasing fraction of that budget. Think of
|
|
23
|
+
# `sleep: -10` as "retry for at most 10 seconds total".
|
|
24
|
+
# A Proc receives the 1-based attempt count and returns the
|
|
25
|
+
# duration in seconds.
|
|
26
|
+
# @option opts [true, Proc, Class] :reraise (nil) Behaviour when all
|
|
27
|
+
# attempts are exhausted. +true+ re-raises the last exception.
|
|
28
|
+
# A +Proc+ is called with the exception (does not re-raise).
|
|
29
|
+
# A +Class+ raises that class with the original message.
|
|
30
|
+
#
|
|
31
|
+
# @yield [count, prev_exception]
|
|
32
|
+
# @yieldparam count [Integer] 1-based attempt number
|
|
33
|
+
# @yieldparam prev_exception [Exception, nil] Exception from the
|
|
34
|
+
# previous attempt, or +nil+ on the first try
|
|
35
|
+
# @return [Boolean, nil] +true+ on success, +false+ on exhaustion,
|
|
36
|
+
# +nil+ when attempts <= 0
|
|
37
|
+
# @raise [ArgumentError] If +:attempts+ is less than 1
|
|
38
|
+
# @raise [TypeError] If +:sleep+ is not Numeric, Proc, or nil
|
|
39
|
+
#
|
|
40
|
+
# @example Fixed sleep, re-raise on failure
|
|
41
|
+
# attempt(attempts: 3, sleep: 0.5, reraise: true) do |n, prev|
|
|
42
|
+
# flaky_api.call
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Exponential backoff within a 5-second budget
|
|
46
|
+
# attempt(attempts: 5, sleep: -5) do |n, prev|
|
|
47
|
+
# unstable_socket.read
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# @example Return-value mode (no exception catching)
|
|
51
|
+
# attempt(attempts: 10, exception_class: nil) do |n, _|
|
|
52
|
+
# parse_line(buffer)
|
|
53
|
+
# end
|
|
16
54
|
def attempt(opts = {}, &block)
|
|
17
55
|
sleep = nil
|
|
18
56
|
exception_class = StandardError
|
data/lib/tins/dslkit.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Tins
|
|
|
13
13
|
#
|
|
14
14
|
# @example Using eigenclass alias
|
|
15
15
|
# class MyClass
|
|
16
|
-
#
|
|
16
|
+
# extend Tins::Eigenclass
|
|
17
17
|
# end
|
|
18
18
|
#
|
|
19
19
|
# puts MyClass.eigenclass # => MyClass singleton class
|
|
@@ -33,7 +33,7 @@ module Tins
|
|
|
33
33
|
# @return [Object] The result of the last expression in the block
|
|
34
34
|
# @example Defining class methods using attr_accessor on a class
|
|
35
35
|
# class MyClass
|
|
36
|
-
#
|
|
36
|
+
# extend Tins::Eigenclass
|
|
37
37
|
# end
|
|
38
38
|
#
|
|
39
39
|
# MyClass.eigenclass_eval { attr_accessor :foo }
|
|
@@ -71,7 +71,7 @@ module Tins
|
|
|
71
71
|
#
|
|
72
72
|
# @example Using ClassMethod module to define class methods dynamically
|
|
73
73
|
# class MyClass
|
|
74
|
-
#
|
|
74
|
+
# extend Tins::ClassMethod
|
|
75
75
|
# end
|
|
76
76
|
#
|
|
77
77
|
# # Define class methods using the ClassMethod helpers
|
|
@@ -125,9 +125,10 @@ module Tins
|
|
|
125
125
|
|
|
126
126
|
# Provides thread-safe global variable functionality for modules and classes.
|
|
127
127
|
#
|
|
128
|
-
# This module enables the definition of
|
|
129
|
-
#
|
|
130
|
-
#
|
|
128
|
+
# This module enables the definition of global variables within a given
|
|
129
|
+
# module or class scope. Values are shared across all threads, and access
|
|
130
|
+
# is synchronized through a mutex for thread safety. Variables are
|
|
131
|
+
# initialized lazily on first access.
|
|
131
132
|
#
|
|
132
133
|
# @example Basic usage with module-level thread globals
|
|
133
134
|
# class MyClass
|
|
@@ -161,9 +162,10 @@ module Tins
|
|
|
161
162
|
# Define a thread global variable named _name_ in this module/class. If the
|
|
162
163
|
# value _value_ is given, it is used to initialize the variable.
|
|
163
164
|
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# meaning the default value or block is only
|
|
165
|
+
# The value is shared across all threads within the scope of the module
|
|
166
|
+
# or class where it is defined. Access is synchronized via a mutex. The
|
|
167
|
+
# initialization is lazy, meaning the default value or block is only
|
|
168
|
+
# executed when first accessed.
|
|
167
169
|
#
|
|
168
170
|
# @param name [Symbol, String] The name of the thread global variable to define
|
|
169
171
|
# @param default_value [Object] The default value for the variable (optional)
|
|
@@ -731,19 +733,35 @@ module Tins
|
|
|
731
733
|
|
|
732
734
|
private_constant :UNSET
|
|
733
735
|
|
|
734
|
-
# A method to easily delegate methods to an object
|
|
735
|
-
# instance variable or returned by a
|
|
736
|
+
# A method to easily delegate methods to an object stored in an
|
|
737
|
+
# instance variable, class variable, constant, or returned by a
|
|
738
|
+
# method call.
|
|
739
|
+
#
|
|
740
|
+
# @param method_name [Symbol] The method to delegate
|
|
741
|
+
# @param opts [Hash] Configuration options
|
|
742
|
+
# @option opts [String] :to The delegation target: an instance
|
|
743
|
+
# variable ("@var"), class variable ("@@var"), constant
|
|
744
|
+
# ("SomeClass"), or method name
|
|
745
|
+
# @option opts [Symbol] :as Alias for the delegated method
|
|
746
|
+
# (defaults to +method_name+)
|
|
736
747
|
#
|
|
737
|
-
#
|
|
748
|
+
# @example Delegate to an instance variable
|
|
738
749
|
# class A
|
|
739
|
-
#
|
|
750
|
+
# extend Tins::Delegate
|
|
751
|
+
# delegate :method_here, to: :@obj
|
|
740
752
|
# end
|
|
741
|
-
#
|
|
753
|
+
#
|
|
754
|
+
# @example Delegate with an alias
|
|
742
755
|
# class A
|
|
743
|
-
#
|
|
756
|
+
# extend Tins::Delegate
|
|
757
|
+
# delegate :method_here, to: :@obj, as: :method_there
|
|
744
758
|
# end
|
|
745
759
|
#
|
|
746
|
-
#
|
|
760
|
+
# @example Delegate to a method call result
|
|
761
|
+
# class A
|
|
762
|
+
# extend Tins::Delegate
|
|
763
|
+
# delegate :method_here, to: :current_target
|
|
764
|
+
# end
|
|
747
765
|
def delegate(method_name, opts = {})
|
|
748
766
|
to = opts[:to] || UNSET
|
|
749
767
|
as = opts[:as] || method_name
|
|
@@ -837,7 +855,7 @@ module Tins
|
|
|
837
855
|
#
|
|
838
856
|
# @example Basic usage with a custom parameterizable module
|
|
839
857
|
# module MyModule
|
|
840
|
-
#
|
|
858
|
+
# extend Tins::ParameterizedModule
|
|
841
859
|
#
|
|
842
860
|
# def self.parameterize(options = {})
|
|
843
861
|
# # Custom parameterization logic
|
|
@@ -933,31 +951,36 @@ module Tins
|
|
|
933
951
|
# tracking nested contexts in multi-threaded applications.
|
|
934
952
|
#
|
|
935
953
|
# @example Basic stack operations
|
|
936
|
-
#
|
|
937
|
-
#
|
|
938
|
-
#
|
|
939
|
-
#
|
|
940
|
-
#
|
|
954
|
+
# class MyCtx
|
|
955
|
+
# include Tins::Scope
|
|
956
|
+
# end
|
|
957
|
+
#
|
|
958
|
+
# ctx = MyCtx.new
|
|
959
|
+
# ctx.scope_push("context1")
|
|
960
|
+
# ctx.scope_push("context2")
|
|
961
|
+
# ctx.scope_top # => "context2"
|
|
962
|
+
# ctx.scope_pop
|
|
963
|
+
# ctx.scope_top # => "context1"
|
|
941
964
|
#
|
|
942
965
|
# @example Block-based context management
|
|
943
|
-
#
|
|
966
|
+
# ctx.scope_block("request_context") do
|
|
944
967
|
# # Within this block, "request_context" is active
|
|
945
968
|
# end
|
|
946
969
|
# # Automatically cleaned up when block exits
|
|
947
970
|
#
|
|
948
971
|
# @example Nested scope blocks
|
|
949
|
-
#
|
|
950
|
-
#
|
|
951
|
-
#
|
|
972
|
+
# ctx.scope_block("outer_context") do
|
|
973
|
+
# ctx.scope_block("inner_context") do
|
|
974
|
+
# ctx.scope_top # => "inner_context"
|
|
952
975
|
# end
|
|
953
|
-
#
|
|
976
|
+
# ctx.scope_top # => "outer_context"
|
|
954
977
|
# end
|
|
955
|
-
#
|
|
978
|
+
# ctx.scope_top # => nil (empty stack)
|
|
956
979
|
#
|
|
957
980
|
# @example Multiple named scopes
|
|
958
|
-
#
|
|
959
|
-
#
|
|
960
|
-
#
|
|
981
|
+
# ctx.scope_push("frame1", :database)
|
|
982
|
+
# ctx.scope_push("frame2", :database)
|
|
983
|
+
# ctx.scope_get(:database) # => ["frame1", "frame2"]
|
|
961
984
|
module Scope
|
|
962
985
|
# Pushes a scope frame onto the top of the specified scope stack.
|
|
963
986
|
#
|
|
@@ -1041,32 +1064,44 @@ module Tins
|
|
|
1041
1064
|
# template engines, and context-sensitive applications.
|
|
1042
1065
|
#
|
|
1043
1066
|
# @example Basic dynamic scoping
|
|
1044
|
-
#
|
|
1067
|
+
# class MyDsl
|
|
1068
|
+
# include Tins::DynamicScope
|
|
1045
1069
|
#
|
|
1046
|
-
#
|
|
1047
|
-
#
|
|
1048
|
-
#
|
|
1070
|
+
# def run
|
|
1071
|
+
# dynamic_scope do
|
|
1072
|
+
# self.foo = "value" # Sets variable in current scope
|
|
1073
|
+
# puts foo # => "value" (reads from current scope)
|
|
1074
|
+
# end
|
|
1075
|
+
# end
|
|
1049
1076
|
# end
|
|
1050
1077
|
#
|
|
1051
1078
|
# @example Nested scopes with variable shadowing
|
|
1052
|
-
#
|
|
1053
|
-
#
|
|
1054
|
-
#
|
|
1055
|
-
#
|
|
1056
|
-
#
|
|
1057
|
-
#
|
|
1058
|
-
#
|
|
1079
|
+
# class MyDsl
|
|
1080
|
+
# include Tins::DynamicScope
|
|
1081
|
+
#
|
|
1082
|
+
# def run
|
|
1083
|
+
# dynamic_scope do
|
|
1084
|
+
# self.foo = "outer"
|
|
1085
|
+
# dynamic_scope do
|
|
1086
|
+
# self.foo = "inner" # Shadows in inner scope
|
|
1087
|
+
# puts foo # => "inner"
|
|
1088
|
+
# end
|
|
1089
|
+
# puts foo # => "outer" (restored)
|
|
1090
|
+
# end
|
|
1059
1091
|
# end
|
|
1060
|
-
# puts foo # => "outer" (restored from outer scope)
|
|
1061
1092
|
# end
|
|
1062
1093
|
#
|
|
1063
1094
|
# @example Variable existence checking
|
|
1064
|
-
#
|
|
1065
|
-
#
|
|
1066
|
-
#
|
|
1067
|
-
#
|
|
1068
|
-
#
|
|
1069
|
-
#
|
|
1095
|
+
# class MyDsl
|
|
1096
|
+
# include Tins::DynamicScope
|
|
1097
|
+
#
|
|
1098
|
+
# def run
|
|
1099
|
+
# dynamic_scope do
|
|
1100
|
+
# raise unless dynamic_defined?(:foo) == false
|
|
1101
|
+
# self.foo = "value"
|
|
1102
|
+
# raise unless dynamic_defined?(:foo) == true
|
|
1103
|
+
# end
|
|
1104
|
+
# end
|
|
1070
1105
|
# end
|
|
1071
1106
|
module DynamicScope
|
|
1072
1107
|
# A specialized Hash subclass for dynamic scope contexts.
|
|
@@ -1179,18 +1214,22 @@ end
|
|
|
1179
1214
|
#
|
|
1180
1215
|
# @example Method deflection
|
|
1181
1216
|
# class Calculator
|
|
1217
|
+
# include Tins::Deflect
|
|
1218
|
+
#
|
|
1182
1219
|
# def add(a, b)
|
|
1183
1220
|
# a + b
|
|
1184
1221
|
# end
|
|
1185
1222
|
# end
|
|
1186
1223
|
#
|
|
1187
1224
|
# calc = Calculator.new
|
|
1188
|
-
# Tins::Deflect.
|
|
1189
|
-
# # Custom behavior for add method
|
|
1225
|
+
# deflector = Tins::Deflect::Deflector.new do |obj, meth, *args|
|
|
1190
1226
|
# args.sum
|
|
1191
1227
|
# end
|
|
1192
1228
|
#
|
|
1193
|
-
# calc.
|
|
1229
|
+
# calc.deflect(Calculator, :add, deflector) do
|
|
1230
|
+
# calc.add(1, 2, 3) # => 6 (sum instead of addition)
|
|
1231
|
+
# end
|
|
1232
|
+
# calc.add(1, 2) # => 3 (deflection stopped)
|
|
1194
1233
|
#
|
|
1195
1234
|
# @see Tins::DSLAccessor
|
|
1196
1235
|
# @see Tins::Constant
|