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.
- checksums.yaml +4 -4
- data/.contexts/code_comment.rb +23 -0
- data/.contexts/full.rb +31 -0
- data/.contexts/lib.rb +24 -0
- data/.contexts/yard.md +92 -0
- data/.github/workflows/codeql-analysis.yml +72 -0
- data/CHANGES.md +194 -0
- data/README.md +161 -90
- data/Rakefile +23 -19
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/ones_difference.stm +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 +100 -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 +27 -0
- data/lib/tins/dslkit.rb +563 -59
- data/lib/tins/duration.rb +160 -3
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +108 -25
- 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 +69 -0
- 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 +105 -29
- 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 +25 -5
- data/lib/tins/sexy_singleton.rb +46 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_named_placeholders.rb +70 -0
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +183 -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 +34 -12
- data/lib/tins/thread_local.rb +69 -11
- data/lib/tins/time_dummy.rb +47 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +61 -2
- 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/deep_dup.rb +4 -2
- data/lib/tins/xt/deprecate.rb +5 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/hash_bfs.rb +7 -0
- 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/secure_write.rb +0 -4
- data/lib/tins/xt/string.rb +1 -0
- data/lib/tins/xt/string_camelize.rb +4 -2
- data/lib/tins/xt/string_named_placeholders.rb +7 -0
- data/lib/tins/xt/string_underscore.rb +4 -2
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt/write.rb +0 -4
- data/lib/tins/xt.rb +3 -3
- data/lib/tins.rb +19 -3
- data/tests/annotate_test.rb +0 -1
- data/tests/bijection_test.rb +0 -1
- data/tests/concern_test.rb +63 -4
- data/tests/date_dummy_test.rb +0 -1
- data/tests/date_time_dummy_test.rb +0 -1
- data/tests/delegate_test.rb +0 -1
- data/tests/deprecate_test.rb +41 -0
- data/tests/dslkit_test.rb +15 -1
- data/tests/duration_test.rb +23 -2
- data/tests/dynamic_scope_test.rb +0 -1
- data/tests/extract_last_argument_options_test.rb +0 -1
- data/tests/find_test.rb +0 -1
- data/tests/from_module_test.rb +30 -3
- data/tests/generator_test.rb +0 -1
- data/tests/go_test.rb +0 -1
- data/tests/hash_bfs_test.rb +34 -0
- data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
- data/tests/implement_test.rb +6 -9
- data/tests/limited_test.rb +12 -12
- data/tests/lines_file_test.rb +2 -1
- data/tests/lru_cache_test.rb +12 -1
- data/tests/memoize_test.rb +0 -1
- data/tests/method_description_test.rb +14 -20
- data/tests/minimize_test.rb +0 -1
- data/tests/module_group_test.rb +0 -1
- data/tests/named_set_test.rb +0 -1
- data/tests/null_test.rb +0 -1
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/require_maybe_test.rb +0 -1
- data/tests/scope_test.rb +1 -2
- data/tests/secure_write_test.rb +6 -1
- data/tests/sexy_singleton_test.rb +1 -1
- data/tests/string_named_placeholders.rb +109 -0
- data/tests/string_version_test.rb +3 -1
- data/tests/subhash_test.rb +0 -1
- data/tests/test_helper.rb +4 -7
- data/tests/time_dummy_test.rb +0 -1
- data/tests/time_freezer_test.rb +1 -1
- data/tests/to_test.rb +6 -6
- data/tests/token_test.rb +0 -1
- data/tests/unit_test.rb +0 -1
- data/tins.gemspec +18 -30
- metadata +55 -38
- data/lib/tins/count_by.rb +0 -8
- data/lib/tins/deep_const_get.rb +0 -50
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -10
- data/lib/tins/xt/count_by.rb +0 -11
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -15
- 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,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
|
-
|
16
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
#
|
111
|
-
#
|
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
|
-
#
|
121
|
-
#
|
122
|
-
#
|
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
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
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
|
-
#
|
157
|
-
#
|
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
|
-
#
|
160
|
-
#
|
161
|
-
#
|
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
|
-
#
|
273
|
-
#
|
274
|
-
# 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.
|
275
518
|
module SymbolMaker
|
276
|
-
#
|
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
|
287
|
-
# symbols
|
288
|
-
#
|
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
|
-
#
|
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
|
298
|
-
#
|
299
|
-
#
|
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
|
-
|
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
|
-
#
|
534
|
-
#
|
535
|
-
#
|
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] }
|