tins 1.43.0 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.contexts/code_comment.rb +5 -8
- data/.contexts/lib.rb +0 -2
- data/CHANGES.md +12 -0
- data/README.md +158 -6
- data/Rakefile +19 -16
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/turing.rb +3 -1
- data/lib/tins/alias.rb +1 -0
- data/lib/tins/annotate.rb +37 -27
- data/lib/tins/ask_and_send.rb +41 -0
- data/lib/tins/attempt.rb +39 -0
- data/lib/tins/bijection.rb +34 -0
- data/lib/tins/case_predicate.rb +21 -0
- data/lib/tins/complete.rb +16 -0
- data/lib/tins/concern.rb +64 -0
- data/lib/tins/date_dummy.rb +36 -4
- data/lib/tins/date_time_dummy.rb +34 -2
- data/lib/tins/deep_dup.rb +9 -2
- data/lib/tins/deprecate.rb +12 -0
- data/lib/tins/dslkit.rb +559 -83
- data/lib/tins/duration.rb +120 -5
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +104 -21
- data/lib/tins/find.rb +114 -11
- data/lib/tins/generator.rb +10 -2
- data/lib/tins/go.rb +81 -4
- data/lib/tins/hash_bfs.rb +4 -2
- data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
- data/lib/tins/hash_union.rb +47 -2
- data/lib/tins/if_predicate.rb +31 -0
- data/lib/tins/implement.rb +50 -0
- data/lib/tins/limited.rb +54 -5
- data/lib/tins/lines_file.rb +81 -2
- data/lib/tins/lru_cache.rb +54 -17
- data/lib/tins/memoize.rb +86 -58
- data/lib/tins/method_description.rb +87 -4
- data/lib/tins/minimize.rb +39 -11
- data/lib/tins/module_group.rb +27 -2
- data/lib/tins/named_set.rb +20 -0
- data/lib/tins/null.rb +86 -15
- data/lib/tins/once.rb +61 -4
- data/lib/tins/p.rb +44 -8
- data/lib/tins/partial_application.rb +66 -7
- data/lib/tins/proc_compose.rb +58 -1
- data/lib/tins/proc_prelude.rb +97 -10
- data/lib/tins/range_plus.rb +30 -2
- data/lib/tins/require_maybe.rb +36 -0
- data/lib/tins/responding.rb +39 -0
- data/lib/tins/secure_write.rb +24 -4
- data/lib/tins/sexy_singleton.rb +45 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +179 -10
- data/lib/tins/subhash.rb +35 -10
- data/lib/tins/temp_io.rb +7 -0
- data/lib/tins/temp_io_enum.rb +19 -0
- data/lib/tins/terminal.rb +31 -9
- data/lib/tins/thread_local.rb +67 -5
- data/lib/tins/time_dummy.rb +46 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +56 -1
- data/lib/tins/unit.rb +288 -149
- data/lib/tins/version.rb +1 -1
- data/lib/tins/write.rb +14 -3
- data/lib/tins/xt/blank.rb +81 -2
- data/lib/tins/xt/concern.rb +51 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/irb.rb +46 -2
- data/lib/tins/xt/method_description.rb +0 -12
- data/lib/tins/xt/minimize.rb +7 -0
- data/lib/tins/xt/named.rb +71 -16
- data/lib/tins/xt/proc_compose.rb +4 -0
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt.rb +1 -3
- data/lib/tins.rb +16 -3
- data/tests/duration_test.rb +4 -0
- data/tests/from_module_test.rb +30 -2
- data/tests/implement_test.rb +6 -8
- data/tests/lines_file_test.rb +2 -0
- data/tests/lru_cache_test.rb +12 -0
- data/tests/method_description_test.rb +14 -20
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/scope_test.rb +1 -1
- data/tests/string_version_test.rb +2 -0
- data/tests/to_test.rb +6 -6
- data/tins.gemspec +9 -9
- metadata +23 -41
- data/lib/tins/count_by.rb +0 -21
- data/lib/tins/deep_const_get.rb +0 -64
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -23
- data/lib/tins/xt/count_by.rb +0 -7
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -25
- data/tests/count_by_test.rb +0 -17
- data/tests/deep_const_get_test.rb +0 -37
- data/tests/uniq_by_test.rb +0 -31
- /data/{COPYING → LICENSE} +0 -0
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
#
|
109
|
-
#
|
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
|
-
#
|
119
|
-
#
|
120
|
-
#
|
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
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
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
|
-
#
|
155
|
-
#
|
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
|
-
#
|
158
|
-
#
|
159
|
-
#
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
#
|
301
|
-
#
|
302
|
-
# module
|
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
|
-
#
|
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
|
315
|
-
# symbols
|
316
|
-
#
|
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
|
-
#
|
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
|
326
|
-
#
|
327
|
-
#
|
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
|
-
|
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
|
-
#
|
562
|
-
#
|
563
|
-
#
|
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] }
|