tins 1.32.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.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.contexts/code_comment.rb +23 -0
  3. data/.contexts/full.rb +31 -0
  4. data/.contexts/lib.rb +24 -0
  5. data/.contexts/yard.md +92 -0
  6. data/.github/workflows/codeql-analysis.yml +72 -0
  7. data/CHANGES.md +194 -0
  8. data/README.md +161 -90
  9. data/Rakefile +23 -19
  10. data/examples/let.rb +8 -40
  11. data/examples/mail.rb +0 -1
  12. data/examples/ones_difference.stm +0 -1
  13. data/examples/turing.rb +3 -1
  14. data/lib/tins/alias.rb +1 -0
  15. data/lib/tins/annotate.rb +37 -27
  16. data/lib/tins/ask_and_send.rb +41 -0
  17. data/lib/tins/attempt.rb +39 -0
  18. data/lib/tins/bijection.rb +34 -0
  19. data/lib/tins/case_predicate.rb +21 -0
  20. data/lib/tins/complete.rb +16 -0
  21. data/lib/tins/concern.rb +100 -0
  22. data/lib/tins/date_dummy.rb +36 -4
  23. data/lib/tins/date_time_dummy.rb +34 -2
  24. data/lib/tins/deep_dup.rb +9 -2
  25. data/lib/tins/deprecate.rb +27 -0
  26. data/lib/tins/dslkit.rb +563 -59
  27. data/lib/tins/duration.rb +160 -3
  28. data/lib/tins/expose.rb +54 -5
  29. data/lib/tins/extract_last_argument_options.rb +9 -0
  30. data/lib/tins/file_binary.rb +108 -25
  31. data/lib/tins/find.rb +114 -11
  32. data/lib/tins/generator.rb +10 -2
  33. data/lib/tins/go.rb +81 -4
  34. data/lib/tins/hash_bfs.rb +69 -0
  35. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  36. data/lib/tins/hash_union.rb +47 -2
  37. data/lib/tins/if_predicate.rb +31 -0
  38. data/lib/tins/implement.rb +50 -0
  39. data/lib/tins/limited.rb +105 -29
  40. data/lib/tins/lines_file.rb +81 -2
  41. data/lib/tins/lru_cache.rb +54 -17
  42. data/lib/tins/memoize.rb +86 -58
  43. data/lib/tins/method_description.rb +87 -4
  44. data/lib/tins/minimize.rb +39 -11
  45. data/lib/tins/module_group.rb +27 -2
  46. data/lib/tins/named_set.rb +20 -0
  47. data/lib/tins/null.rb +86 -15
  48. data/lib/tins/once.rb +61 -4
  49. data/lib/tins/p.rb +44 -8
  50. data/lib/tins/partial_application.rb +66 -7
  51. data/lib/tins/proc_compose.rb +58 -1
  52. data/lib/tins/proc_prelude.rb +97 -10
  53. data/lib/tins/range_plus.rb +30 -2
  54. data/lib/tins/require_maybe.rb +36 -0
  55. data/lib/tins/responding.rb +39 -0
  56. data/lib/tins/secure_write.rb +25 -5
  57. data/lib/tins/sexy_singleton.rb +46 -48
  58. data/lib/tins/string_byte_order_mark.rb +33 -2
  59. data/lib/tins/string_camelize.rb +31 -2
  60. data/lib/tins/string_named_placeholders.rb +70 -0
  61. data/lib/tins/string_underscore.rb +33 -2
  62. data/lib/tins/string_version.rb +183 -10
  63. data/lib/tins/subhash.rb +35 -10
  64. data/lib/tins/temp_io.rb +7 -0
  65. data/lib/tins/temp_io_enum.rb +19 -0
  66. data/lib/tins/terminal.rb +34 -12
  67. data/lib/tins/thread_local.rb +69 -11
  68. data/lib/tins/time_dummy.rb +47 -21
  69. data/lib/tins/to.rb +15 -0
  70. data/lib/tins/to_proc.rb +17 -4
  71. data/lib/tins/token.rb +61 -2
  72. data/lib/tins/unit.rb +288 -149
  73. data/lib/tins/version.rb +1 -1
  74. data/lib/tins/write.rb +14 -3
  75. data/lib/tins/xt/blank.rb +81 -2
  76. data/lib/tins/xt/concern.rb +51 -0
  77. data/lib/tins/xt/deep_dup.rb +4 -2
  78. data/lib/tins/xt/deprecate.rb +5 -0
  79. data/lib/tins/xt/full.rb +56 -11
  80. data/lib/tins/xt/hash_bfs.rb +7 -0
  81. data/lib/tins/xt/irb.rb +46 -2
  82. data/lib/tins/xt/method_description.rb +0 -12
  83. data/lib/tins/xt/minimize.rb +7 -0
  84. data/lib/tins/xt/named.rb +71 -16
  85. data/lib/tins/xt/proc_compose.rb +4 -0
  86. data/lib/tins/xt/secure_write.rb +0 -4
  87. data/lib/tins/xt/string.rb +1 -0
  88. data/lib/tins/xt/string_camelize.rb +4 -2
  89. data/lib/tins/xt/string_named_placeholders.rb +7 -0
  90. data/lib/tins/xt/string_underscore.rb +4 -2
  91. data/lib/tins/xt/subhash.rb +11 -0
  92. data/lib/tins/xt/time_freezer.rb +43 -6
  93. data/lib/tins/xt/write.rb +0 -4
  94. data/lib/tins/xt.rb +3 -3
  95. data/lib/tins.rb +19 -3
  96. data/tests/annotate_test.rb +0 -1
  97. data/tests/bijection_test.rb +0 -1
  98. data/tests/concern_test.rb +63 -4
  99. data/tests/date_dummy_test.rb +0 -1
  100. data/tests/date_time_dummy_test.rb +0 -1
  101. data/tests/delegate_test.rb +0 -1
  102. data/tests/deprecate_test.rb +41 -0
  103. data/tests/dslkit_test.rb +15 -1
  104. data/tests/duration_test.rb +23 -2
  105. data/tests/dynamic_scope_test.rb +0 -1
  106. data/tests/extract_last_argument_options_test.rb +0 -1
  107. data/tests/find_test.rb +0 -1
  108. data/tests/from_module_test.rb +30 -3
  109. data/tests/generator_test.rb +0 -1
  110. data/tests/go_test.rb +0 -1
  111. data/tests/hash_bfs_test.rb +34 -0
  112. data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
  113. data/tests/implement_test.rb +6 -9
  114. data/tests/limited_test.rb +12 -12
  115. data/tests/lines_file_test.rb +2 -1
  116. data/tests/lru_cache_test.rb +12 -1
  117. data/tests/memoize_test.rb +0 -1
  118. data/tests/method_description_test.rb +14 -20
  119. data/tests/minimize_test.rb +0 -1
  120. data/tests/module_group_test.rb +0 -1
  121. data/tests/named_set_test.rb +0 -1
  122. data/tests/null_test.rb +0 -1
  123. data/tests/partial_application_test.rb +4 -0
  124. data/tests/proc_prelude_test.rb +1 -1
  125. data/tests/require_maybe_test.rb +0 -1
  126. data/tests/scope_test.rb +1 -2
  127. data/tests/secure_write_test.rb +6 -1
  128. data/tests/sexy_singleton_test.rb +1 -1
  129. data/tests/string_named_placeholders.rb +109 -0
  130. data/tests/string_version_test.rb +3 -1
  131. data/tests/subhash_test.rb +0 -1
  132. data/tests/test_helper.rb +4 -7
  133. data/tests/time_dummy_test.rb +0 -1
  134. data/tests/time_freezer_test.rb +1 -1
  135. data/tests/to_test.rb +6 -6
  136. data/tests/token_test.rb +0 -1
  137. data/tests/unit_test.rb +0 -1
  138. data/tins.gemspec +18 -30
  139. metadata +55 -38
  140. data/lib/tins/count_by.rb +0 -8
  141. data/lib/tins/deep_const_get.rb +0 -50
  142. data/lib/tins/timed_cache.rb +0 -51
  143. data/lib/tins/uniq_by.rb +0 -10
  144. data/lib/tins/xt/count_by.rb +0 -11
  145. data/lib/tins/xt/deep_const_get.rb +0 -7
  146. data/lib/tins/xt/uniq_by.rb +0 -15
  147. data/tests/count_by_test.rb +0 -17
  148. data/tests/deep_const_get_test.rb +0 -37
  149. data/tests/uniq_by_test.rb +0 -31
  150. /data/{COPYING → LICENSE} +0 -0
data/lib/tins/dslkit.rb CHANGED
@@ -10,47 +10,174 @@ module Tins
10
10
  # names.
11
11
  #
12
12
  # The module can be included into other modules/classes to make the methods available.
13
+ #
14
+ # @example Using eigenclass alias
15
+ # class MyClass
16
+ # include Tins::Eigenclass
17
+ # end
18
+ #
19
+ # puts MyClass.eigenclass # => MyClass singleton class
13
20
  module Eigenclass
14
21
  # Returns the eigenclass of this object.
15
- def eigenclass
16
- end
22
+ #
23
+ # @return [Class] The eigenclass (singleton class) of the receiver
17
24
  alias eigenclass singleton_class
18
25
 
19
26
  # Evaluates the _block_ in context of the eigenclass of this object.
27
+ #
28
+ # This method allows you to define singleton methods or access singleton
29
+ # class methods directly on an object.
30
+ #
31
+ # @yield [] The block to evaluate in the eigenclass context
32
+ # @yieldparam obj [Object] The object whose eigenclass is being evaluated
33
+ # @return [Object] The result of the last expression in the block
34
+ # @example Defining class methods using attr_accessor on a class
35
+ # class MyClass
36
+ # include Tins::Eigenclass
37
+ # end
38
+ #
39
+ # MyClass.eigenclass_eval { attr_accessor :foo }
40
+ # MyClass.foo = "bar"
41
+ # MyClass.foo # => "bar"
20
42
  def eigenclass_eval(&block)
21
43
  eigenclass.instance_eval(&block)
22
44
  end
45
+
46
+
47
+ # Evaluates the _block_ in context of the eigenclass's class context.
48
+ #
49
+ # This method allows you to define class methods on the eigenclass itself,
50
+ # which is different from +eigenclass_eval+ that defines singleton methods
51
+ # on the object instance.
52
+ #
53
+ # @yield [] The block to evaluate in the eigenclass's class context
54
+ # @return [Object] The result of the last expression in the block
55
+ #
56
+ # @example Defining class methods on eigenclass
57
+ # class MyClass
58
+ # include Tins::Eigenclass
59
+ # end
60
+ #
61
+ # obj = MyClass.new
62
+ # obj.eigenclass_class_eval { def foo; "bar"; end }
63
+ # obj.foo # => "bar"
64
+ def eigenclass_class_eval(&block)
65
+ eigenclass.class_eval(&block)
66
+ end
23
67
  end
24
68
 
69
+ # This module provides convenient helpers for defining class methods and
70
+ # attributes on classes themselves.
71
+ #
72
+ # @example Using ClassMethod module to define class methods dynamically
73
+ # class MyClass
74
+ # include Tins::ClassMethod
75
+ # end
76
+ #
77
+ # # Define class methods using the ClassMethod helpers
78
+ # MyClass.class_attr_accessor :foo
79
+ # MyClass.foo = "bar"
80
+ # MyClass.foo # => "bar"
81
+ #
82
+ # MyClass.class_define_method(:baz) { "qux" }
83
+ # MyClass.baz # => "qux"
25
84
  module ClassMethod
26
85
  include Eigenclass
27
86
 
28
87
  # Define a class method named _name_ using _block_.
88
+ #
89
+ # @param name [Symbol] The name of the method to define
90
+ # @yield [Object] The block that defines the method's behavior
91
+ # @return [void]
29
92
  def class_define_method(name, &block)
30
93
  eigenclass_eval { define_method(name, &block) }
31
94
  end
32
95
 
33
96
  # Define reader and writer attribute methods for all <i>*ids</i>.
97
+ #
98
+ # @param ids [Array<Symbol>] The names of the attributes to define
99
+ # @return [void]
34
100
  def class_attr_accessor(*ids)
35
101
  eigenclass_eval { attr_accessor(*ids) }
36
102
  end
37
103
 
104
+ # Alias for {class_attr_accessor}
105
+ #
106
+ # @see class_attr_accessor
107
+ alias class_attr class_attr_accessor
108
+
38
109
  # Define reader attribute methods for all <i>*ids</i>.
110
+ #
111
+ # @param ids [Array<Symbol>] The names of the attributes to define
112
+ # @return [void]
39
113
  def class_attr_reader(*ids)
40
114
  eigenclass_eval { attr_reader(*ids) }
41
115
  end
42
116
 
43
117
  # Define writer attribute methods for all <i>*ids</i>.
118
+ #
119
+ # @param ids [Array<Symbol>] The names of the attributes to define
120
+ # @return [void]
44
121
  def class_attr_writer(*ids)
45
122
  eigenclass_eval { attr_writer(*ids) }
46
123
  end
47
-
48
- # I boycott attr!
49
124
  end
50
125
 
126
+ # Provides thread-safe global variable functionality for modules and classes.
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.
131
+ #
132
+ # @example Basic usage with module-level thread globals
133
+ # class MyClass
134
+ # extend Tins::ThreadGlobal
135
+ #
136
+ # thread_global :counter, 0
137
+ # end
138
+ #
139
+ # mc = MyClass.new
140
+ # mc2 = MyClass.new
141
+ # mc.counter = 5
142
+ # puts mc.counter # => 5
143
+ # puts mc2.counter # => 5
144
+ # mc2.counter = 6
145
+ # puts mc.counter # => 6
146
+ #
147
+ # @example Usage with instance-level thread globals
148
+ # require 'securerandom'
149
+ #
150
+ # class MyClass
151
+ # include Tins::ThreadGlobal
152
+ #
153
+ # def initialize
154
+ # instance_thread_global :session_id, SecureRandom.uuid
155
+ # end
156
+ # end
157
+ #
158
+ # obj = MyClass.new
159
+ # puts obj.session_id # => unique UUID per instance
51
160
  module ThreadGlobal
52
161
  # Define a thread global variable named _name_ in this module/class. If the
53
162
  # value _value_ is given, it is used to initialize the variable.
163
+ #
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.
167
+ #
168
+ # @param name [Symbol, String] The name of the thread global variable to define
169
+ # @param default_value [Object] The default value for the variable (optional)
170
+ # @yield [Object] A block that returns the default value for lazy initialization
171
+ # @yieldparam args [Array] Arguments passed to the default block
172
+ # @return [self] Returns self to allow method chaining
173
+ # @raise [TypeError] If the receiver is not a Module
174
+ # @raise [ArgumentError] If both default_value and default block are provided
175
+ # @example Define with default value
176
+ # thread_global :counter, 0
177
+ # @example Define with lazy initialization block
178
+ # thread_global :config do
179
+ # { timeout: 30, retries: 3 }
180
+ # end
54
181
  def thread_global(name, default_value = nil, &default)
55
182
  is_a?(Module) or raise TypeError, "receiver has to be a Module"
56
183
 
@@ -86,6 +213,22 @@ module Tins
86
213
  # Define a thread global variable for the current instance with name
87
214
  # _name_. If the value _value_ is given, it is used to initialize the
88
215
  # variable.
216
+ #
217
+ # This method creates thread-global variables at the instance level by
218
+ # extending the singleton class of the current object. It's useful when
219
+ # you need per-instance global state that's still thread-safe.
220
+ #
221
+ # @param name [Symbol, String] The name of the thread global variable to define
222
+ # @param value [Object] The initial value for the variable (optional)
223
+ # @return [self] Returns self to allow method chaining
224
+ # @example Create instance thread global
225
+ # class MyClass
226
+ # include Tins::ThreadGlobal
227
+ #
228
+ # def initialize
229
+ # instance_thread_global :session_id, SecureRandom.uuid
230
+ # end
231
+ # end
89
232
  def instance_thread_global(name, value = nil)
90
233
  sc = class << self
91
234
  extend Tins::ThreadGlobal
@@ -96,30 +239,46 @@ module Tins
96
239
  end
97
240
  end
98
241
 
99
- module InstanceExec
100
- def self.included(*)
101
- super
102
- warn "#{self} is deprecated, but included at #{caller.first[/(.*):/, 1]}"
103
- end
104
- end
105
-
242
+ # Provides dynamic code interpretation capabilities for evaluating string-based
243
+ # code within the context of an object instance.
244
+ #
245
+ # This module enables the execution of Ruby code snippets as if they were
246
+ # blocks, maintaining access to the current binding and instance variables.
247
+ #
248
+ # @example Basic usage with automatic binding
249
+ # class A
250
+ # include Tins::Interpreter
251
+ # def c
252
+ # 3
253
+ # end
254
+ # end
255
+ #
256
+ # A.new.interpret('|a,b| a + b + c', 1, 2) # => 6
257
+ #
258
+ # @example Usage with explicit binding
259
+ # class A
260
+ # include Tins::Interpreter
261
+ # def c
262
+ # 3
263
+ # end
264
+ # def foo
265
+ # b = 2
266
+ # interpret_with_binding('|a| a + b + c', binding, 1) # => 6
267
+ # end
268
+ # end
269
+ #
270
+ # A.new.foo # => 6
106
271
  module Interpreter
107
272
  # Interpret the string _source_ as a body of a block, while passing
108
273
  # <i>*args</i> into the block.
109
274
  #
110
- # A small example explains how the method is supposed to be used and how
111
- # the <i>*args</i> can be fetched:
112
- #
113
- # class A
114
- # include Tins::Interpreter
115
- # def c
116
- # 3
117
- # end
118
- # end
275
+ # This method automatically creates a binding from the current context,
276
+ # making all instance variables and methods available to the interpreted code.
119
277
  #
120
- # A.new.interpret('|a,b| a + b + c', 1, 2) # => 6
121
- #
122
- # To use a specified binding see #interpret_with_binding.
278
+ # @param source [String, IO] The Ruby code to evaluate as a block body
279
+ # @param args [Array] Arguments to pass to the interpreted block
280
+ # @return [Object] The result of evaluating the interpreted code
281
+ # @see #interpret_with_binding
123
282
  def interpret(source, *args)
124
283
  interpret_with_binding(source, binding, *args)
125
284
  end
@@ -127,21 +286,14 @@ module Tins
127
286
  # Interpret the string _source_ as a body of a block, while passing
128
287
  # <i>*args</i> into the block and using _my_binding_ for evaluation.
129
288
  #
130
- # A small example:
131
- #
132
- # class A
133
- # include Tins::Interpreter
134
- # def c
135
- # 3
136
- # end
137
- # def foo
138
- # b = 2
139
- # interpret_with_binding('|a| a + b + c', binding, 1) # => 6
140
- # end
141
- # end
142
- # A.new.foo # => 6
143
- #
144
- # See also #interpret.
289
+ # This method allows explicit control over the binding context, enabling
290
+ # access to specific local variables from a particular scope.
291
+ #
292
+ # @param source [String, IO] The Ruby code to evaluate as a block body
293
+ # @param my_binding [Binding] The binding object to use for evaluation
294
+ # @param args [Array] Arguments to pass to the interpreted block
295
+ # @return [Object] The result of evaluating the interpreted code
296
+ # @see #interpret
145
297
  def interpret_with_binding(source, my_binding, *args)
146
298
  path = '(interpret)'
147
299
  if source.respond_to? :to_io
@@ -153,12 +305,35 @@ module Tins
153
305
  end
154
306
  end
155
307
 
156
- # This module contains the _constant_ method. For small example of its usage
157
- # see the documentation of the DSLAccessor module.
308
+ # A module that provides method-based DSL constant creation functionality.
309
+ # These constants are particularly useful for creating DSLs and configuration
310
+ # systems where symbolic names improve readability and maintainability.
311
+ #
312
+ # @example Basic constant creation
313
+ # class MyClass
314
+ # extend Tins::Constant
315
+ #
316
+ # constant :yes, true
317
+ # constant :no, false
318
+ # end
319
+ #
320
+ # MyClass.instance_eval do
321
+ # yes # => true
322
+ # no # => false
323
+ # end
324
+ #
325
+ # @see DSLAccessor#dsl_accessor For mutable accessor alternatives and a more
326
+ # advanced example.
158
327
  module Constant
159
- # Create a constant named _name_, that refers to value _value_. _value is
160
- # frozen, if this is possible. If you want to modify/exchange a value use
161
- # DSLAccessor#dsl_reader/DSLAccessor#dsl_accessor instead.
328
+ # Creates a method-based constant named _name_ that returns _value_.
329
+ #
330
+ # This method defines a method with the given name that always returns the
331
+ # specified value. The value is attempted to be frozen for immutability,
332
+ # though this will fail gracefully if freezing isn't possible for the value.
333
+ #
334
+ # @param name [Symbol] The name of the constant method to define
335
+ # @param value [Object] The value the constant should return (defaults to name)
336
+ # @return [void]
162
337
  def constant(name, value = name)
163
338
  value = value.freeze rescue value
164
339
  define_method(name) { value }
@@ -241,6 +416,36 @@ module Tins
241
416
  end
242
417
  end
243
418
 
419
+ # The dsl_lazy_accessor method defines a lazy-loaded accessor method with
420
+ # a default block.
421
+ #
422
+ # This method creates a dynamic accessor that initializes its value
423
+ # lazily when first accessed. It stores the default value as a block in
424
+ # an instance variable and evaluates it on first access. If a block is
425
+ # passed to the accessor, it sets the instance variable to that block for
426
+ # future use.
427
+ #
428
+ # @param name [ Object ] the name of the accessor method to define
429
+ # @yield [ default ] optional block that provides the default value for initialization
430
+ #
431
+ # @return [ Symbol ] returns name of the defined method.
432
+ def dsl_lazy_accessor(name, &default)
433
+ variable = "@#{name}"
434
+ define_method(name) do |*args, &block|
435
+ if !block && args.empty?
436
+ if instance_variable_defined?(variable)
437
+ instance_eval(&instance_variable_get(variable))
438
+ elsif default
439
+ instance_eval(&default)
440
+ end
441
+ elsif block
442
+ instance_variable_set(variable, block)
443
+ else
444
+ raise ArgumentError, '&block argument is required'
445
+ end
446
+ end
447
+ end
448
+
244
449
  # This method creates a dsl reader accessor, that behaves exactly like a
245
450
  # #dsl_accessor but can only be read not set.
246
451
  def dsl_reader(name, *default, &block)
@@ -269,11 +474,59 @@ module Tins
269
474
  end
270
475
  end
271
476
 
272
- # This module can be included in another module/class. It generates a symbol
273
- # for every missing method that was called in the context of this
274
- # module/class.
477
+ # A module that provides method missing handler for symbolic method calls.
478
+ #
479
+ # This module enables dynamic method resolution by converting missing method
480
+ # calls into symbols. When a method is called that doesn't exist, instead of
481
+ # raising NoMethodError, the method name is returned as a symbol. This is
482
+ # particularly useful for creating DSLs, configuration systems, and symbolic
483
+ # interfaces.
484
+ #
485
+ # @example Combined with DSLAccessor for powerful configuration
486
+ # class CoffeeMaker
487
+ # include Tins::SymbolMaker
488
+ # extend Tins::DSLAccessor
489
+ #
490
+ # dsl_accessor(:state) { :off }
491
+ # dsl_accessor :allowed_states, :on, :off
492
+ #
493
+ # def process
494
+ # allowed_states.include?(state) or fail "Explode!!!"
495
+ # if state == on
496
+ # puts "Make coffee."
497
+ # else
498
+ # puts "Idle..."
499
+ # end
500
+ # end
501
+ # end
502
+ #
503
+ # cm = CoffeeMaker.new
504
+ # cm.instance_eval do
505
+ # state # => :off
506
+ # state on # Sets state to :on
507
+ # # state tnt # should be avoided
508
+ # state # => :on
509
+ # process # => outputs "Make coffee."
510
+ # end
511
+ #
512
+ # @note Tins::SymbolMaker is an alternative for Tins::Constant in this example.
513
+ # While both approaches can be used to create symbolic references,
514
+ # SymbolMaker makes method calls return symbols, whereas Constant creates
515
+ # only the required constants. SymbolMaker can make debugging more
516
+ # difficult because it's less clear when a method call is returning a
517
+ # symbol versus being a real method call, typo, etc.
275
518
  module SymbolMaker
276
- # Returns a symbol (_id_) for every missing method named _id_.
519
+ # Handles missing method calls by returning the method name as a symbol.
520
+ #
521
+ # This method is called when Ruby cannot find a method in the current
522
+ # object. Instead of raising NoMethodError, it returns the method name as a
523
+ # symbol, enabling symbolic method handling throughout the application.
524
+ # Only methods with no arguments are converted to symbols; methods with
525
+ # arguments will raise the normal NoMethodError.
526
+ #
527
+ # @param id [Symbol] The missing method name
528
+ # @param args [Array] Arguments passed to the missing method
529
+ # @return [Symbol] The method name as a symbol (when no arguments)
277
530
  def method_missing(id, *args)
278
531
  if args.empty?
279
532
  id
@@ -283,20 +536,60 @@ module Tins
283
536
  end
284
537
  end
285
538
 
286
- # This module can be used to extend another module/class. It generates
287
- # symbols for every missing constant under the namespace of this
288
- # module/class.
539
+ # This module enables dynamic constant resolution by converting missing
540
+ # constant references into symbols. When a constant is not found, instead of
541
+ # raising NameError, the constant name is returned as a symbol. This is
542
+ # particularly useful for creating DSLs, configuration systems, and symbolic
543
+ # interfaces.
544
+ #
545
+ # @example Basic usage with missing constants
546
+ # class MyClass
547
+ # extend Tins::ConstantMaker
548
+ # end
549
+ #
550
+ # # Missing constants return their names as symbols
551
+ # MyClass.const_get(:UNKNOWN_CONSTANT) # => :UNKNOWN_CONSTANT
289
552
  module ConstantMaker
290
- # Returns a symbol (_id_) for every missing constant named _id_.
553
+ # Handles missing constant references by returning the constant name as a
554
+ # symbol.
555
+ #
556
+ # This method is called when Ruby cannot find a constant in the current
557
+ # namespace. Instead of raising a NameError, it returns the constant name
558
+ # as a symbol, enabling symbolic constant handling throughout the
559
+ # application.
560
+ #
561
+ # @param id [Symbol] The missing constant name
562
+ # @return [Symbol] The constant name as a symbol
291
563
  def const_missing(id)
292
564
  id
293
565
  end
294
566
  end
295
567
 
568
+ # A module that provides blank slate class creation functionality.
569
+ #
570
+ # This module enables the creation of anonymous classes with restricted
571
+ # method sets, allowing for precise control over object interfaces. Blank
572
+ # slates are useful for security, DSL construction, testing, and creating
573
+ # clean API surfaces.
574
+ #
575
+ # @example Basic usage with method whitelisting
576
+ # # Create a class that only responds to :length and :upcase methods
577
+ # RestrictedClass = Tins::BlankSlate.with(:length, :upcase, superclass: String)
578
+ #
579
+ # obj = RestrictedClass.new('foo')
580
+ # obj.length # => 3
581
+ # obj.upcase # => 'FOO'
582
+ # obj.strip # NoMethodError
296
583
  module BlankSlate
297
- # Creates an anonymous blank slate class, that only responds to the methods
298
- # <i>*ids</i>. ids can be Symbols, Strings, and Regexps that have to match
299
- # the method name with #===.
584
+ # Creates an anonymous blank slate class with restricted method set.
585
+ #
586
+ # This method generates a new class that inherits from the specified superclass
587
+ # (or Object by default) and removes all methods except those explicitly allowed.
588
+ # The allowed methods can be specified as symbols, strings, or regular expressions.
589
+ #
590
+ # @param ids [Array<Symbol,String,Regexp>] Method names or patterns to allow
591
+ # @option opts [Class] :superclass (Object) The base class to inherit from
592
+ # @return [Class] A new anonymous class with only the specified methods available
300
593
  def self.with(*ids)
301
594
  opts = Hash === ids.last ? ids.pop : {}
302
595
  ids = ids.map { |id| Regexp === id ? id : id.to_s }
@@ -436,6 +729,8 @@ module Tins
436
729
  module Delegate
437
730
  UNSET = Object.new
438
731
 
732
+ private_constant :UNSET
733
+
439
734
  # A method to easily delegate methods to an object, stored in an
440
735
  # instance variable or returned by a method call.
441
736
  #
@@ -449,7 +744,6 @@ module Tins
449
744
  # end
450
745
  #
451
746
  # _other_method_name_ defaults to method_name, if it wasn't given.
452
- #def delegate(method_name, to: UNSET, as: method_name)
453
747
  def delegate(method_name, opts = {})
454
748
  to = opts[:to] || UNSET
455
749
  as = opts[:as] || method_name
@@ -470,7 +764,7 @@ module Tins
470
764
  end
471
765
  when (?A..?Z).include?(to[0])
472
766
  define_method(as) do |*args, &block|
473
- Tins::DeepConstGet.deep_const_get(to).__send__(method_name, *args, &block)
767
+ Object.const_get(to).__send__(method_name, *args, &block)
474
768
  end
475
769
  else
476
770
  define_method(as) do |*args, &block|
@@ -502,6 +796,12 @@ module Tins
502
796
  module DelegatorModule
503
797
  include Tins::MethodMissingDelegator
504
798
 
799
+ # The initialize method sets up the delegator and forwards additional
800
+ # arguments to the superclass.
801
+ #
802
+ # @param delegator [ Object ] the object to delegate method calls to
803
+ # @param a [ Array ] additional arguments to pass to the superclass initializer
804
+ # @param b [ Proc ] optional block to be passed to the superclass initializer
505
805
  def initialize(delegator, *a, &b)
506
806
  self.method_missing_delegator = delegator
507
807
  super(*a, &b) if defined? super
@@ -529,20 +829,87 @@ module Tins
529
829
  end
530
830
  end
531
831
 
832
+ # A module that provides parameterization capabilities for other modules.
833
+ #
834
+ # This module enables dynamic configuration of modules through a common
835
+ # interface, allowing for flexible composition and customization of module
836
+ # behavior at runtime.
837
+ #
838
+ # @example Basic usage with a custom parameterizable module
839
+ # module MyModule
840
+ # include Tins::ParameterizedModule
841
+ #
842
+ # def self.parameterize(options = {})
843
+ # # Custom parameterization logic
844
+ # @options = options
845
+ # self
846
+ # end
847
+ # end
848
+ #
849
+ # # Usage
850
+ # configured_module = MyModule.parameterize_for(some_option: 'value')
851
+ # MyModule.instance_variable_get(:@options) # => {some_option: "value"}
532
852
  module ParameterizedModule
533
- # Pass _args_ and _block_ to configure the module and then return it after
534
- # calling the parameterize method has been called with these arguments. The
535
- # _parameterize_ method should return a configured module.
853
+ # Configures the module using the provided arguments and optional block.
854
+ #
855
+ # This method checks if the including module responds to `parameterize` and calls
856
+ # it with the given arguments. If no `parameterize` method exists, it returns
857
+ # the module itself unchanged.
858
+ #
859
+ # @param args [Array] Arguments to pass to the parameterize method
860
+ # @yield [block] Optional block to be passed to the parameterize method
861
+ # @return [Module] The configured module or self if no parameterization occurs
536
862
  def parameterize_for(*args, &block)
537
863
  respond_to?(:parameterize) ? parameterize(*args, &block) : self
538
864
  end
539
865
  end
540
866
 
867
+ # A module that provides parameterized module creation capabilities.
868
+ #
869
+ # This module enables the creation of new modules by filtering methods from
870
+ # existing modules, allowing for flexible composition and interface
871
+ # segregation at runtime.
872
+ #
873
+ # @example
874
+ # class MixedClass
875
+ # extend Tins::FromModule
876
+ #
877
+ # include MyModule # has foo, bar and baz methods
878
+ # include from module: MyIncludedModule, methods: :foo
879
+ # include from module: MyIncludedModule2, methods: :bar
880
+ # end
881
+ #
882
+ # c = MixedClass.new
883
+ # assert_equal :foo, c.foo # from MyIncludedModule
884
+ # assert_equal :bar2, c.bar # from MyIncludedModule2
885
+ # assert_equal :baz, c.baz # from MyModule
886
+ #
887
+ # @example Create a stack-like class using Array methods
888
+ # class Stack < Class.from(module: Array, methods: %i[ push pop last ])
889
+ # end
890
+ #
891
+ # s = Stack.new
892
+ # s.push(1)
893
+ # s.push(2)
894
+ # s.last # => 2
895
+ # s.pop # => 2
896
+ # s.last # => 1
541
897
  module FromModule
542
898
  include ParameterizedModule
543
899
 
544
900
  alias from parameterize_for
545
901
 
902
+ # Creates a new module by filtering methods from an existing module.
903
+ #
904
+ # This method duplicates the specified module and removes all methods
905
+ # except those explicitly listed in the :methods option. This enables
906
+ # creating specialized interfaces
907
+ # from existing modules.
908
+ #
909
+ # @param opts [Hash] Configuration options
910
+ # @option opts [Module] :module (required) The source module (or class) to filter methods from
911
+ # @option opts [Array<Symbol>] :methods (required) Array of method names to preserve
912
+ # @return [Module] A new module with only the specified methods available
546
913
  def parameterize(opts = {})
547
914
  modul = opts[:module] or raise ArgumentError, 'option :module is required'
548
915
  import_methods = Array(opts[:methods])
@@ -552,32 +919,95 @@ module Tins
552
919
  begin
553
920
  result.__send__ :remove_method, m
554
921
  rescue NameError
922
+ # Method might already be removed or not exist
555
923
  end
556
924
  end
557
925
  result
558
926
  end
559
927
  end
560
928
 
929
+ # A module that provides thread-local stack-based scoping functionality.
930
+ #
931
+ # This module implements a context management system where each thread
932
+ # maintains its own isolated scope stacks. It's particularly useful for
933
+ # tracking nested contexts in multi-threaded applications.
934
+ #
935
+ # @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"
941
+ #
942
+ # @example Block-based context management
943
+ # Scope.scope_block("request_context") do
944
+ # # Within this block, "request_context" is active
945
+ # end
946
+ # # Automatically cleaned up when block exits
947
+ #
948
+ # @example Nested scope blocks
949
+ # Scope.scope_block("outer_context") do
950
+ # Scope.scope_block("inner_context") do
951
+ # Scope.scope_top # => "inner_context"
952
+ # end
953
+ # Scope.scope_top # => "outer_context"
954
+ # end
955
+ # Scope.scope_top # => nil (empty stack)
956
+ #
957
+ # @example Multiple named scopes
958
+ # Scope.scope_push("frame1", :database)
959
+ # Scope.scope_push("frame2", :database)
960
+ # Scope.scope_get(:database) # => ["frame1", "frame2"]
561
961
  module Scope
962
+ # Pushes a scope frame onto the top of the specified scope stack.
963
+ #
964
+ # @param scope_frame [Object] The object to push onto the stack
965
+ # @param name [Symbol] The name of the scope to use (defaults to :default)
966
+ # @return [self] Returns self to enable method chaining
562
967
  def scope_push(scope_frame, name = :default)
563
968
  scope_get(name).push scope_frame
564
969
  self
565
970
  end
566
971
 
972
+ # Pops a scope frame from the top of the specified scope stack.
973
+ #
974
+ # If the scope becomes empty after popping, it is automatically removed
975
+ # from Thread.current to prevent memory leaks.
976
+ #
977
+ # @param name [Symbol] The name of the scope to use (defaults to :default)
978
+ # @return [self] Returns self to enable method chaining
567
979
  def scope_pop(name = :default)
568
980
  scope_get(name).pop
569
981
  scope_get(name).empty? and Thread.current[name] = nil
570
982
  self
571
983
  end
572
984
 
985
+ # Returns the top element of the specified scope stack without removing it.
986
+ #
987
+ # @param name [Symbol] The name of the scope to use (defaults to :default)
988
+ # @return [Object, nil] The top element of the stack or nil if empty
573
989
  def scope_top(name = :default)
574
990
  scope_get(name).last
575
991
  end
576
992
 
993
+ # Iterates through the specified scope stack in reverse order.
994
+ #
995
+ # @param name [Symbol] The name of the scope to use (defaults to :default)
996
+ # @yield [frame] Yields each frame from top to bottom
997
+ # @return [Enumerator] If no block is given, returns an enumerator
577
998
  def scope_reverse(name = :default, &block)
578
999
  scope_get(name).reverse_each(&block)
579
1000
  end
580
1001
 
1002
+ # Executes a block within the context of a scope frame.
1003
+ #
1004
+ # Automatically pushes the scope frame before yielding and pops it after
1005
+ # the block completes, even if an exception occurs.
1006
+ #
1007
+ # @param scope_frame [Object] The scope frame to push
1008
+ # @param name [Symbol] The name of the scope to use (defaults to :default)
1009
+ # @yield [void] The block to execute within the scope
1010
+ # @return [Object] The result of the block execution
581
1011
  def scope_block(scope_frame, name = :default)
582
1012
  scope_push(scope_frame, name)
583
1013
  yield
@@ -585,21 +1015,71 @@ module Tins
585
1015
  scope_pop(name)
586
1016
  end
587
1017
 
1018
+ # Retrieves or initializes the specified scope stack.
1019
+ #
1020
+ # @param name [Symbol] The name of the scope to retrieve (defaults to :default)
1021
+ # @return [Array] The scope stack for the given name
588
1022
  def scope_get(name = :default)
589
1023
  Thread.current[name] ||= []
590
1024
  end
591
1025
 
1026
+ # Returns a copy of the specified scope stack.
1027
+ #
1028
+ # @param name [Symbol] The name of the scope to retrieve (defaults to :default)
1029
+ # @return [Array] A duplicate of the scope stack
592
1030
  def scope(name = :default)
593
1031
  scope_get(name).dup
594
1032
  end
595
1033
  end
596
1034
 
1035
+ # A module that provides dynamic scope-based variable binding with hash
1036
+ # contexts.
1037
+ #
1038
+ # This module extends the basic Scope functionality to enable dynamic
1039
+ # variable binding where variables can be set and accessed like methods, but
1040
+ # stored in hash-based contexts. It's particularly useful for building DSLs,
1041
+ # template engines, and context-sensitive applications.
1042
+ #
1043
+ # @example Basic dynamic scoping
1044
+ # include Tins::DynamicScope
1045
+ #
1046
+ # dynamic_scope do
1047
+ # self.foo = "value" # Sets variable in current scope
1048
+ # puts foo # => "value" (reads from current scope)
1049
+ # end
1050
+ #
1051
+ # @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"
1059
+ # end
1060
+ # puts foo # => "outer" (restored from outer scope)
1061
+ # end
1062
+ #
1063
+ # @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)
1070
+ # end
597
1071
  module DynamicScope
1072
+ # A specialized Hash subclass for dynamic scope contexts.
1073
+ #
1074
+ # This class automatically converts string keys to symbols for more
1075
+ # convenient variable access.
598
1076
  class Context < Hash
1077
+ # Retrieves a value by symbolized key.
599
1078
  def [](name)
600
1079
  super name.to_sym
601
1080
  end
602
1081
 
1082
+ # Sets a value with a symbolized key.
603
1083
  def []=(name, value)
604
1084
  super name.to_sym, value
605
1085
  end
@@ -607,19 +1087,43 @@ module Tins
607
1087
 
608
1088
  include Scope
609
1089
 
1090
+ # The name of the dynamic scope to use (defaults to :variables).
610
1091
  attr_accessor :dynamic_scope_name
611
1092
 
1093
+ # Checks if a variable is defined in any active dynamic scope.
1094
+ #
1095
+ # @param id [Symbol] The variable name to check
1096
+ # @return [Boolean] true if the variable is defined in any active scope, false otherwise
612
1097
  def dynamic_defined?(id)
613
1098
  self.dynamic_scope_name ||= :variables
614
1099
  scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return true }
615
1100
  false
616
1101
  end
617
1102
 
1103
+ # Creates a new dynamic scope context.
1104
+ #
1105
+ # This method pushes a new Context object onto the scope stack and yields
1106
+ # to the provided block. When the block completes, the context is automatically
1107
+ # popped from the stack.
1108
+ #
1109
+ # @yield [void] The block to execute within the dynamic scope
1110
+ # @return [Object] The result of the block execution
618
1111
  def dynamic_scope(&block)
619
1112
  self.dynamic_scope_name ||= :variables
620
1113
  scope_block(Context.new, dynamic_scope_name, &block)
621
1114
  end
622
1115
 
1116
+ # Handles method calls that don't match existing methods.
1117
+ #
1118
+ # This implements the core dynamic variable binding behavior:
1119
+ # - For read operations (no arguments): Looks up the method name in active scopes
1120
+ # and returns the value if found, otherwise delegates to super for normal method resolution
1121
+ # - For write operations (one argument + = suffix): Sets the value in current scope
1122
+ # - For all other cases: Delegates to super for normal method resolution
1123
+ #
1124
+ # @param id [Symbol] The method name being called
1125
+ # @param args [Array] Arguments passed to the method
1126
+ # @return [Object] The result of the dynamic variable access or delegation
623
1127
  def method_missing(id, *args)
624
1128
  self.dynamic_scope_name ||= :variables
625
1129
  if args.empty? and scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return c[id] }