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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.contexts/code_comment.rb +5 -8
  3. data/.contexts/lib.rb +0 -2
  4. data/CHANGES.md +12 -0
  5. data/README.md +158 -6
  6. data/Rakefile +19 -16
  7. data/examples/let.rb +8 -40
  8. data/examples/mail.rb +0 -1
  9. data/examples/turing.rb +3 -1
  10. data/lib/tins/alias.rb +1 -0
  11. data/lib/tins/annotate.rb +37 -27
  12. data/lib/tins/ask_and_send.rb +41 -0
  13. data/lib/tins/attempt.rb +39 -0
  14. data/lib/tins/bijection.rb +34 -0
  15. data/lib/tins/case_predicate.rb +21 -0
  16. data/lib/tins/complete.rb +16 -0
  17. data/lib/tins/concern.rb +64 -0
  18. data/lib/tins/date_dummy.rb +36 -4
  19. data/lib/tins/date_time_dummy.rb +34 -2
  20. data/lib/tins/deep_dup.rb +9 -2
  21. data/lib/tins/deprecate.rb +12 -0
  22. data/lib/tins/dslkit.rb +559 -83
  23. data/lib/tins/duration.rb +120 -5
  24. data/lib/tins/expose.rb +54 -5
  25. data/lib/tins/extract_last_argument_options.rb +9 -0
  26. data/lib/tins/file_binary.rb +104 -21
  27. data/lib/tins/find.rb +114 -11
  28. data/lib/tins/generator.rb +10 -2
  29. data/lib/tins/go.rb +81 -4
  30. data/lib/tins/hash_bfs.rb +4 -2
  31. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  32. data/lib/tins/hash_union.rb +47 -2
  33. data/lib/tins/if_predicate.rb +31 -0
  34. data/lib/tins/implement.rb +50 -0
  35. data/lib/tins/limited.rb +54 -5
  36. data/lib/tins/lines_file.rb +81 -2
  37. data/lib/tins/lru_cache.rb +54 -17
  38. data/lib/tins/memoize.rb +86 -58
  39. data/lib/tins/method_description.rb +87 -4
  40. data/lib/tins/minimize.rb +39 -11
  41. data/lib/tins/module_group.rb +27 -2
  42. data/lib/tins/named_set.rb +20 -0
  43. data/lib/tins/null.rb +86 -15
  44. data/lib/tins/once.rb +61 -4
  45. data/lib/tins/p.rb +44 -8
  46. data/lib/tins/partial_application.rb +66 -7
  47. data/lib/tins/proc_compose.rb +58 -1
  48. data/lib/tins/proc_prelude.rb +97 -10
  49. data/lib/tins/range_plus.rb +30 -2
  50. data/lib/tins/require_maybe.rb +36 -0
  51. data/lib/tins/responding.rb +39 -0
  52. data/lib/tins/secure_write.rb +24 -4
  53. data/lib/tins/sexy_singleton.rb +45 -48
  54. data/lib/tins/string_byte_order_mark.rb +33 -2
  55. data/lib/tins/string_camelize.rb +31 -2
  56. data/lib/tins/string_underscore.rb +33 -2
  57. data/lib/tins/string_version.rb +179 -10
  58. data/lib/tins/subhash.rb +35 -10
  59. data/lib/tins/temp_io.rb +7 -0
  60. data/lib/tins/temp_io_enum.rb +19 -0
  61. data/lib/tins/terminal.rb +31 -9
  62. data/lib/tins/thread_local.rb +67 -5
  63. data/lib/tins/time_dummy.rb +46 -21
  64. data/lib/tins/to.rb +15 -0
  65. data/lib/tins/to_proc.rb +17 -4
  66. data/lib/tins/token.rb +56 -1
  67. data/lib/tins/unit.rb +288 -149
  68. data/lib/tins/version.rb +1 -1
  69. data/lib/tins/write.rb +14 -3
  70. data/lib/tins/xt/blank.rb +81 -2
  71. data/lib/tins/xt/concern.rb +51 -0
  72. data/lib/tins/xt/full.rb +56 -11
  73. data/lib/tins/xt/irb.rb +46 -2
  74. data/lib/tins/xt/method_description.rb +0 -12
  75. data/lib/tins/xt/minimize.rb +7 -0
  76. data/lib/tins/xt/named.rb +71 -16
  77. data/lib/tins/xt/proc_compose.rb +4 -0
  78. data/lib/tins/xt/subhash.rb +11 -0
  79. data/lib/tins/xt/time_freezer.rb +43 -6
  80. data/lib/tins/xt.rb +1 -3
  81. data/lib/tins.rb +16 -3
  82. data/tests/duration_test.rb +4 -0
  83. data/tests/from_module_test.rb +30 -2
  84. data/tests/implement_test.rb +6 -8
  85. data/tests/lines_file_test.rb +2 -0
  86. data/tests/lru_cache_test.rb +12 -0
  87. data/tests/method_description_test.rb +14 -20
  88. data/tests/partial_application_test.rb +4 -0
  89. data/tests/proc_prelude_test.rb +1 -1
  90. data/tests/scope_test.rb +1 -1
  91. data/tests/string_version_test.rb +2 -0
  92. data/tests/to_test.rb +6 -6
  93. data/tins.gemspec +9 -9
  94. metadata +23 -41
  95. data/lib/tins/count_by.rb +0 -21
  96. data/lib/tins/deep_const_get.rb +0 -64
  97. data/lib/tins/timed_cache.rb +0 -51
  98. data/lib/tins/uniq_by.rb +0 -23
  99. data/lib/tins/xt/count_by.rb +0 -7
  100. data/lib/tins/xt/deep_const_get.rb +0 -7
  101. data/lib/tins/xt/uniq_by.rb +0 -25
  102. data/tests/count_by_test.rb +0 -17
  103. data/tests/deep_const_get_test.rb +0 -37
  104. data/tests/uniq_by_test.rb +0 -31
  105. /data/{COPYING → LICENSE} +0 -0
data/lib/tins/dslkit.rb CHANGED
@@ -10,45 +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.
22
+ #
23
+ # @return [Class] The eigenclass (singleton class) of the receiver
15
24
  alias eigenclass singleton_class
16
25
 
17
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"
18
42
  def eigenclass_eval(&block)
19
43
  eigenclass.instance_eval(&block)
20
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
21
67
  end
22
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"
23
84
  module ClassMethod
24
85
  include Eigenclass
25
86
 
26
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]
27
92
  def class_define_method(name, &block)
28
93
  eigenclass_eval { define_method(name, &block) }
29
94
  end
30
95
 
31
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]
32
100
  def class_attr_accessor(*ids)
33
101
  eigenclass_eval { attr_accessor(*ids) }
34
102
  end
35
103
 
104
+ # Alias for {class_attr_accessor}
105
+ #
106
+ # @see class_attr_accessor
107
+ alias class_attr class_attr_accessor
108
+
36
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]
37
113
  def class_attr_reader(*ids)
38
114
  eigenclass_eval { attr_reader(*ids) }
39
115
  end
40
116
 
41
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]
42
121
  def class_attr_writer(*ids)
43
122
  eigenclass_eval { attr_writer(*ids) }
44
123
  end
45
-
46
- # I boycott attr!
47
124
  end
48
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
49
160
  module ThreadGlobal
50
161
  # Define a thread global variable named _name_ in this module/class. If the
51
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
52
181
  def thread_global(name, default_value = nil, &default)
53
182
  is_a?(Module) or raise TypeError, "receiver has to be a Module"
54
183
 
@@ -84,6 +213,22 @@ module Tins
84
213
  # Define a thread global variable for the current instance with name
85
214
  # _name_. If the value _value_ is given, it is used to initialize the
86
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
87
232
  def instance_thread_global(name, value = nil)
88
233
  sc = class << self
89
234
  extend Tins::ThreadGlobal
@@ -94,30 +239,46 @@ module Tins
94
239
  end
95
240
  end
96
241
 
97
- module InstanceExec
98
- def self.included(*)
99
- super
100
- warn "#{self} is deprecated, but included at #{caller.first[/(.*):/, 1]}"
101
- end
102
- end
103
-
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
104
271
  module Interpreter
105
272
  # Interpret the string _source_ as a body of a block, while passing
106
273
  # <i>*args</i> into the block.
107
274
  #
108
- # A small example explains how the method is supposed to be used and how
109
- # the <i>*args</i> can be fetched:
110
- #
111
- # class A
112
- # include Tins::Interpreter
113
- # def c
114
- # 3
115
- # end
116
- # end
275
+ # This method automatically creates a binding from the current context,
276
+ # making all instance variables and methods available to the interpreted code.
117
277
  #
118
- # A.new.interpret('|a,b| a + b + c', 1, 2) # => 6
119
- #
120
- # 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
121
282
  def interpret(source, *args)
122
283
  interpret_with_binding(source, binding, *args)
123
284
  end
@@ -125,21 +286,14 @@ module Tins
125
286
  # Interpret the string _source_ as a body of a block, while passing
126
287
  # <i>*args</i> into the block and using _my_binding_ for evaluation.
127
288
  #
128
- # A small example:
129
- #
130
- # class A
131
- # include Tins::Interpreter
132
- # def c
133
- # 3
134
- # end
135
- # def foo
136
- # b = 2
137
- # interpret_with_binding('|a| a + b + c', binding, 1) # => 6
138
- # end
139
- # end
140
- # A.new.foo # => 6
141
- #
142
- # 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
143
297
  def interpret_with_binding(source, my_binding, *args)
144
298
  path = '(interpret)'
145
299
  if source.respond_to? :to_io
@@ -151,12 +305,35 @@ module Tins
151
305
  end
152
306
  end
153
307
 
154
- # This module contains the _constant_ method. For small example of its usage
155
- # 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.
156
327
  module Constant
157
- # Create a constant named _name_, that refers to value _value_. _value is
158
- # frozen, if this is possible. If you want to modify/exchange a value use
159
- # 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]
160
337
  def constant(name, value = name)
161
338
  value = value.freeze rescue value
162
339
  define_method(name) { value }
@@ -239,35 +416,35 @@ module Tins
239
416
  end
240
417
  end
241
418
 
242
- # The dsl_lazy_accessor method defines a lazy-loaded accessor method with
243
- # a default block.
244
- #
245
- # This method creates a dynamic accessor that initializes its value
246
- # lazily when first accessed. It stores the default value as a block in
247
- # an instance variable and evaluates it on first access. If a block is
248
- # passed to the accessor, it sets the instance variable to that block for
249
- # future use.
250
- #
251
- # @param name [ Object ] the name of the accessor method to define
252
- # @yield [ default ] optional block that provides the default value for initialization
253
- #
254
- # @return [ Symbol ] returns name of the defined method.
255
- def dsl_lazy_accessor(name, &default)
256
- variable = "@#{name}"
257
- define_method(name) do |*args, &block|
258
- if !block && args.empty?
259
- if instance_variable_defined?(variable)
260
- instance_eval(&instance_variable_get(variable))
261
- elsif default
262
- instance_eval(&default)
263
- end
264
- elsif block
265
- instance_variable_set(variable, block)
266
- else
267
- raise ArgumentError, '&block argument is required'
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)
268
440
  end
441
+ elsif block
442
+ instance_variable_set(variable, block)
443
+ else
444
+ raise ArgumentError, '&block argument is required'
269
445
  end
270
446
  end
447
+ end
271
448
 
272
449
  # This method creates a dsl reader accessor, that behaves exactly like a
273
450
  # #dsl_accessor but can only be read not set.
@@ -297,11 +474,59 @@ module Tins
297
474
  end
298
475
  end
299
476
 
300
- # This module can be included in another module/class. It generates a symbol
301
- # for every missing method that was called in the context of this
302
- # 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.
303
518
  module SymbolMaker
304
- # 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)
305
530
  def method_missing(id, *args)
306
531
  if args.empty?
307
532
  id
@@ -311,20 +536,60 @@ module Tins
311
536
  end
312
537
  end
313
538
 
314
- # This module can be used to extend another module/class. It generates
315
- # symbols for every missing constant under the namespace of this
316
- # 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
317
552
  module ConstantMaker
318
- # 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
319
563
  def const_missing(id)
320
564
  id
321
565
  end
322
566
  end
323
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
324
583
  module BlankSlate
325
- # Creates an anonymous blank slate class, that only responds to the methods
326
- # <i>*ids</i>. ids can be Symbols, Strings, and Regexps that have to match
327
- # 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
328
593
  def self.with(*ids)
329
594
  opts = Hash === ids.last ? ids.pop : {}
330
595
  ids = ids.map { |id| Regexp === id ? id : id.to_s }
@@ -464,6 +729,8 @@ module Tins
464
729
  module Delegate
465
730
  UNSET = Object.new
466
731
 
732
+ private_constant :UNSET
733
+
467
734
  # A method to easily delegate methods to an object, stored in an
468
735
  # instance variable or returned by a method call.
469
736
  #
@@ -477,7 +744,6 @@ module Tins
477
744
  # end
478
745
  #
479
746
  # _other_method_name_ defaults to method_name, if it wasn't given.
480
- #def delegate(method_name, to: UNSET, as: method_name)
481
747
  def delegate(method_name, opts = {})
482
748
  to = opts[:to] || UNSET
483
749
  as = opts[:as] || method_name
@@ -498,7 +764,7 @@ module Tins
498
764
  end
499
765
  when (?A..?Z).include?(to[0])
500
766
  define_method(as) do |*args, &block|
501
- Tins::DeepConstGet.deep_const_get(to).__send__(method_name, *args, &block)
767
+ Object.const_get(to).__send__(method_name, *args, &block)
502
768
  end
503
769
  else
504
770
  define_method(as) do |*args, &block|
@@ -530,6 +796,12 @@ module Tins
530
796
  module DelegatorModule
531
797
  include Tins::MethodMissingDelegator
532
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
533
805
  def initialize(delegator, *a, &b)
534
806
  self.method_missing_delegator = delegator
535
807
  super(*a, &b) if defined? super
@@ -557,20 +829,87 @@ module Tins
557
829
  end
558
830
  end
559
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"}
560
852
  module ParameterizedModule
561
- # Pass _args_ and _block_ to configure the module and then return it after
562
- # calling the parameterize method has been called with these arguments. The
563
- # _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
564
862
  def parameterize_for(*args, &block)
565
863
  respond_to?(:parameterize) ? parameterize(*args, &block) : self
566
864
  end
567
865
  end
568
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
569
897
  module FromModule
570
898
  include ParameterizedModule
571
899
 
572
900
  alias from parameterize_for
573
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
574
913
  def parameterize(opts = {})
575
914
  modul = opts[:module] or raise ArgumentError, 'option :module is required'
576
915
  import_methods = Array(opts[:methods])
@@ -580,32 +919,95 @@ module Tins
580
919
  begin
581
920
  result.__send__ :remove_method, m
582
921
  rescue NameError
922
+ # Method might already be removed or not exist
583
923
  end
584
924
  end
585
925
  result
586
926
  end
587
927
  end
588
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"]
589
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
590
967
  def scope_push(scope_frame, name = :default)
591
968
  scope_get(name).push scope_frame
592
969
  self
593
970
  end
594
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
595
979
  def scope_pop(name = :default)
596
980
  scope_get(name).pop
597
981
  scope_get(name).empty? and Thread.current[name] = nil
598
982
  self
599
983
  end
600
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
601
989
  def scope_top(name = :default)
602
990
  scope_get(name).last
603
991
  end
604
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
605
998
  def scope_reverse(name = :default, &block)
606
999
  scope_get(name).reverse_each(&block)
607
1000
  end
608
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
609
1011
  def scope_block(scope_frame, name = :default)
610
1012
  scope_push(scope_frame, name)
611
1013
  yield
@@ -613,21 +1015,71 @@ module Tins
613
1015
  scope_pop(name)
614
1016
  end
615
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
616
1022
  def scope_get(name = :default)
617
1023
  Thread.current[name] ||= []
618
1024
  end
619
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
620
1030
  def scope(name = :default)
621
1031
  scope_get(name).dup
622
1032
  end
623
1033
  end
624
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
625
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.
626
1076
  class Context < Hash
1077
+ # Retrieves a value by symbolized key.
627
1078
  def [](name)
628
1079
  super name.to_sym
629
1080
  end
630
1081
 
1082
+ # Sets a value with a symbolized key.
631
1083
  def []=(name, value)
632
1084
  super name.to_sym, value
633
1085
  end
@@ -635,19 +1087,43 @@ module Tins
635
1087
 
636
1088
  include Scope
637
1089
 
1090
+ # The name of the dynamic scope to use (defaults to :variables).
638
1091
  attr_accessor :dynamic_scope_name
639
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
640
1097
  def dynamic_defined?(id)
641
1098
  self.dynamic_scope_name ||= :variables
642
1099
  scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return true }
643
1100
  false
644
1101
  end
645
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
646
1111
  def dynamic_scope(&block)
647
1112
  self.dynamic_scope_name ||= :variables
648
1113
  scope_block(Context.new, dynamic_scope_name, &block)
649
1114
  end
650
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
651
1127
  def method_missing(id, *args)
652
1128
  self.dynamic_scope_name ||= :variables
653
1129
  if args.empty? and scope_reverse(dynamic_scope_name) { |c| c.key?(id) and return c[id] }