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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ba81a48e00f9a9ae7eaadaeeebc69b0b0eb6820308cf3540d4c6158f8f86f93
4
- data.tar.gz: 40fc9c05a472b234c0601ddd0ba22c091bab1ae655a2fe4f73cdb1c88daa5042
3
+ metadata.gz: 10b0d776e1885f0202af59a976790f05b95819127acc39a51440ca3234922787
4
+ data.tar.gz: 756e1c2582fb33347ab1abf0e2db0964170e23de4ebfe1d4853aae45a2594195
5
5
  SHA512:
6
- metadata.gz: 9f35bd33d3d9bce8952a18c2aa9852a936fb0dfa1e18c7bfc8942da63435fea10da153079817991cd333ab67099349763037d7f1ff1b34faf269cad95bc2d856
7
- data.tar.gz: 1c6f77c57ceda3dc375a50d988418ea55c63048ee4f47adf8432ad1262f03d63664de5b8546b199761bbbaf4e0d0a56ae53b2e40280ec1653a2577388b9b2e0d
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
- # Attempts code in block *attempts* times, sleeping according to *sleep*
6
- # between attempts and catching the exception(s) in *exception_class*.
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
- # *sleep* is either a Proc returning a floating point number for duration
9
- # as seconds or a Numeric >= 0 or < 0. In the former case this is the
10
- # duration directly, in the latter case -*sleep* is the total number of
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
- # Iff *reraise* is true the caught exception is reraised after running out
15
- # of attempts.
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
- # include Tins::Eigenclass
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
- # include Tins::Eigenclass
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
- # include Tins::ClassMethod
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 thread-global variables that maintain
129
- # their state per thread within a given module or class scope. These variables
130
- # are initialized lazily and provide thread safety through mutex synchronization.
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
- # Thread global variables maintain their state per thread within the scope
165
- # of the module or class where they are defined. The initialization is lazy,
166
- # meaning the default value or block is only executed when first accessed.
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, stored in an
735
- # instance variable or returned by a method call.
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
- # It's used like this:
748
+ # @example Delegate to an instance variable
738
749
  # class A
739
- # delegate :method_here, :@obj, :method_there
750
+ # extend Tins::Delegate
751
+ # delegate :method_here, to: :@obj
740
752
  # end
741
- # or:
753
+ #
754
+ # @example Delegate with an alias
742
755
  # class A
743
- # delegate :method_here, :method_call, :method_there
756
+ # extend Tins::Delegate
757
+ # delegate :method_here, to: :@obj, as: :method_there
744
758
  # end
745
759
  #
746
- # _other_method_name_ defaults to method_name, if it wasn't given.
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
- # include Tins::ParameterizedModule
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
- # Scope.scope_push("context1")
937
- # Scope.scope_push("context2")
938
- # Scope.scope_top # => "context2"
939
- # Scope.scope_pop # => "context2"
940
- # Scope.scope_top # => "context1"
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
- # Scope.scope_block("request_context") do
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
- # Scope.scope_block("outer_context") do
950
- # Scope.scope_block("inner_context") do
951
- # Scope.scope_top # => "inner_context"
972
+ # ctx.scope_block("outer_context") do
973
+ # ctx.scope_block("inner_context") do
974
+ # ctx.scope_top # => "inner_context"
952
975
  # end
953
- # Scope.scope_top # => "outer_context"
976
+ # ctx.scope_top # => "outer_context"
954
977
  # end
955
- # Scope.scope_top # => nil (empty stack)
978
+ # ctx.scope_top # => nil (empty stack)
956
979
  #
957
980
  # @example Multiple named scopes
958
- # Scope.scope_push("frame1", :database)
959
- # Scope.scope_push("frame2", :database)
960
- # Scope.scope_get(:database) # => ["frame1", "frame2"]
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
- # include Tins::DynamicScope
1067
+ # class MyDsl
1068
+ # include Tins::DynamicScope
1045
1069
  #
1046
- # dynamic_scope do
1047
- # self.foo = "value" # Sets variable in current scope
1048
- # puts foo # => "value" (reads from current scope)
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
- # include Tins::DynamicScope
1053
- #
1054
- # dynamic_scope do
1055
- # self.foo = "outer"
1056
- # dynamic_scope do
1057
- # self.foo = "inner" # Shadows outer foo only in inner scope
1058
- # puts foo # => "inner"
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
- # include Tins::DynamicScope
1065
- #
1066
- # dynamic_scope do
1067
- # assert_equal false, dynamic_defined?(:foo)
1068
- # self.foo = "value"
1069
- # assert_equal true, dynamic_defined?(:foo)
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.deflect(Calculator, :add) do |obj, method, *args|
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.add(1, 2, 3) # => 6 (sum instead of addition)
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