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/xt/blank.rb
CHANGED
@@ -1,34 +1,83 @@
|
|
1
1
|
module Tins
|
2
|
+
# The Tins::Blank module provides a consistent way to check if objects are
|
3
|
+
# "blank" (empty, nil, or contain only whitespace). This follows Rails'
|
4
|
+
# convention.
|
5
|
+
#
|
6
|
+
# @example Basic usage
|
7
|
+
# "".blank? # => true
|
8
|
+
# " ".blank? # => true
|
9
|
+
# "foo".blank? # => false
|
10
|
+
# [].blank? # => true
|
11
|
+
# [1, 2].blank? # => false
|
12
|
+
# nil.blank? # => true
|
13
|
+
# false.blank? # => true
|
14
|
+
# true.blank? # => false
|
15
|
+
#
|
16
|
+
# @example Using present?
|
17
|
+
# "".present? # => false
|
18
|
+
# "foo".present? # => true
|
2
19
|
module Blank
|
20
|
+
# Blank behavior for Object instances
|
3
21
|
module Object
|
22
|
+
# Provides a fallback implementation that checks for `empty?` method,
|
23
|
+
# falling back to negation of truthiness if not defined.
|
24
|
+
#
|
25
|
+
# @return [Boolean] true if the object is considered blank, false otherwise
|
4
26
|
def blank?
|
5
27
|
respond_to?(:empty?) ? empty? : !self
|
6
28
|
end
|
7
29
|
|
30
|
+
# Checks if the object is not blank
|
31
|
+
#
|
32
|
+
# @return [Boolean] true if the object is present, false otherwise
|
8
33
|
def present?
|
9
34
|
!blank?
|
10
35
|
end
|
11
36
|
end
|
12
37
|
|
38
|
+
# Blank behavior for NilClass instances
|
13
39
|
module NilClass
|
40
|
+
#
|
41
|
+
# Nil values are always considered blank.
|
42
|
+
#
|
43
|
+
# @return [Boolean] true (always)
|
14
44
|
def blank?
|
15
45
|
true
|
16
46
|
end
|
17
47
|
end
|
18
48
|
|
49
|
+
# Blank behavior for FalseClass instances
|
19
50
|
module FalseClass
|
51
|
+
# False values are always considered blank.
|
52
|
+
#
|
53
|
+
# @return [Boolean] true (always)
|
20
54
|
def blank?
|
21
55
|
true
|
22
56
|
end
|
23
57
|
end
|
24
58
|
|
59
|
+
# Blank behavior for TrueClass instances
|
25
60
|
module TrueClass
|
61
|
+
# True values are never considered blank.
|
62
|
+
#
|
63
|
+
# @return [Boolean] false (always)
|
26
64
|
def blank?
|
27
65
|
false
|
28
66
|
end
|
29
67
|
end
|
30
68
|
|
69
|
+
# Blank behavior for Array instances
|
70
|
+
#
|
71
|
+
# Arrays are blank if they are empty.
|
72
|
+
# This implementation aliases the `empty?` method to `blank?`.
|
31
73
|
module Array
|
74
|
+
# The included method is a hook that gets called when this module is
|
75
|
+
# included in another class or module.
|
76
|
+
#
|
77
|
+
# It sets up blank? behavior by aliasing the empty? method to blank? in
|
78
|
+
# the including class/module.
|
79
|
+
#
|
80
|
+
# @param modul [Object] the class or module that includes this module
|
32
81
|
def self.included(modul)
|
33
82
|
modul.module_eval do
|
34
83
|
alias_method :blank?, :empty?
|
@@ -36,7 +85,18 @@ module Tins
|
|
36
85
|
end
|
37
86
|
end
|
38
87
|
|
88
|
+
# Blank behavior for Hash instances
|
89
|
+
#
|
90
|
+
# Hashes are blank if they are empty.
|
91
|
+
# This implementation aliases the `empty?` method to `blank?`.
|
39
92
|
module Hash
|
93
|
+
# The included method is a hook that gets called when this module is
|
94
|
+
# included in another class or module.
|
95
|
+
#
|
96
|
+
# It sets up blank? behavior by aliasing the empty? method to blank? in
|
97
|
+
# the including class/module.
|
98
|
+
#
|
99
|
+
# @param modul [Object] the class or module that includes this module
|
40
100
|
def self.included(modul)
|
41
101
|
modul.module_eval do
|
42
102
|
alias_method :blank?, :empty?
|
@@ -44,20 +104,39 @@ module Tins
|
|
44
104
|
end
|
45
105
|
end
|
46
106
|
|
107
|
+
# Blank behavior for String instances
|
47
108
|
module String
|
109
|
+
# Strings are blank if they contain only whitespace characters.
|
110
|
+
# This uses a regex match against non-whitespace characters.
|
111
|
+
#
|
112
|
+
# @return [Boolean] true if the string contains only whitespace, false otherwise
|
48
113
|
def blank?
|
49
114
|
self !~ /\S/
|
50
115
|
end
|
51
116
|
end
|
52
117
|
|
118
|
+
# Blank behavior for Numeric instances
|
119
|
+
#
|
120
|
+
# Numbers are considered blank only if they are zero.
|
121
|
+
# This implementation aliases the `zero?` method to `blank?`.
|
53
122
|
module Numeric
|
54
|
-
|
55
|
-
|
123
|
+
# The included method is a hook that gets called when this module is
|
124
|
+
# included in another class or module.
|
125
|
+
#
|
126
|
+
# It sets up blank? behavior by aliasing the zero? method to blank? in
|
127
|
+
# the including class/module.
|
128
|
+
#
|
129
|
+
# @param modul [Object] the class or module that includes this module
|
130
|
+
def self.included(modul)
|
131
|
+
modul.module_eval do
|
132
|
+
alias_method :blank?, :zero?
|
133
|
+
end
|
56
134
|
end
|
57
135
|
end
|
58
136
|
end
|
59
137
|
end
|
60
138
|
|
139
|
+
# Extend constant classes with Blank behavior unless blank? is already defined
|
61
140
|
unless Object.respond_to?(:blank?)
|
62
141
|
for k in Tins::Blank.constants
|
63
142
|
Object.const_get(k).class_eval do
|
data/lib/tins/xt/concern.rb
CHANGED
@@ -1,19 +1,70 @@
|
|
1
1
|
require 'tins/concern'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Concern provides a mechanism for module configuration that persists across
|
5
|
+
# inheritance and inclusion boundaries using thread-local storage.
|
6
|
+
#
|
7
|
+
# This module implements a pattern where modules can be configured with
|
8
|
+
# arguments that are then available throughout the module's lifecycle in
|
9
|
+
# a thread-safe manner. It's particularly useful for implementing
|
10
|
+
# configuration-based concerns that need to maintain state across
|
11
|
+
# different scopes.
|
12
|
+
#
|
13
|
+
# @example Configured concern usage
|
14
|
+
# module MyConcern
|
15
|
+
# extend Tins::Concern
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Configure the concern with parameters
|
19
|
+
# include MyConcern.tins_concern_configure(:option1, :option2)
|
4
20
|
module Concern
|
21
|
+
# ModuleMixin provides thread-local storage for concern configuration.
|
22
|
+
#
|
23
|
+
# This mixin adds methods to any module that includes it, allowing for
|
24
|
+
# configuration of concerns through thread-local storage. The configuration
|
25
|
+
# is stored in the current thread's context and persists during the
|
26
|
+
# execution of code that uses this concern.
|
27
|
+
#
|
28
|
+
# @note This implementation relies on Thread.current which makes it
|
29
|
+
# thread-safe but scoped to individual threads.
|
5
30
|
module ModuleMixin
|
31
|
+
# Configures the module with the given arguments and returns self.
|
32
|
+
#
|
33
|
+
# This method stores the provided arguments in thread-local storage,
|
34
|
+
# making them available via {tins_concern_args}. It's designed to be
|
35
|
+
# chainable (returns self).
|
36
|
+
#
|
37
|
+
# @param args [Array] Arguments to configure this concern with
|
38
|
+
# @return [Module] The module itself, for chaining
|
39
|
+
# @example
|
40
|
+
# MyConcern.tins_concern_configure(:option1, :option2)
|
6
41
|
def tins_concern_configure(*args)
|
7
42
|
Thread.current[:tin_concern_args] = args
|
8
43
|
self
|
9
44
|
end
|
10
45
|
|
46
|
+
# Retrieves the current concern configuration arguments.
|
47
|
+
#
|
48
|
+
# This method fetches the arguments that were previously set using
|
49
|
+
# {tins_concern_configure}. If no configuration has been set, it returns
|
50
|
+
# nil.
|
51
|
+
#
|
52
|
+
# @return [Array, nil] The stored configuration arguments or nil
|
53
|
+
# @example
|
54
|
+
# MyConcern.tins_concern_configure(:option1, :option2)
|
55
|
+
# puts MyConcern.tins_concern_args # => [:option1, :option2]
|
11
56
|
def tins_concern_args
|
12
57
|
Thread.current[:tin_concern_args]
|
13
58
|
end
|
14
59
|
end
|
15
60
|
end
|
16
61
|
|
62
|
+
# Extends the core Module class with the concern functionality.
|
63
|
+
#
|
64
|
+
# This line makes the concern configuration methods available to all modules
|
65
|
+
# in the system, allowing any module to be configured as a concern.
|
66
|
+
#
|
67
|
+
# @see Tins::Concern::ModuleMixin
|
17
68
|
class ::Module
|
18
69
|
include Tins::Concern::ModuleMixin
|
19
70
|
end
|
data/lib/tins/xt/deep_dup.rb
CHANGED
data/lib/tins/xt/full.rb
CHANGED
@@ -1,18 +1,51 @@
|
|
1
1
|
require 'tins/xt/blank'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Provides methods for checking if objects are "full" (non-blank) and safely
|
5
|
+
# processing them in conditional contexts.
|
6
|
+
#
|
7
|
+
# This module adds the `full?` and `all_full?` methods to all objects, enabling
|
8
|
+
# clean, readable patterns for validation and conditional processing.
|
9
|
+
#
|
10
|
+
# @example Basic usage
|
11
|
+
# "hello".full? # => "hello"
|
12
|
+
# "".full? # => nil
|
13
|
+
#
|
14
|
+
# @example Method dispatch with block
|
15
|
+
# user.full?(:name) { |name| "Hello #{name}" } # Returns "Hello John" if name is full
|
16
|
+
#
|
17
|
+
# @example Safe assignment and processing
|
18
|
+
# if name = user.full?(:name)
|
19
|
+
# puts "Hello #{name}"
|
20
|
+
# end
|
4
21
|
module Full
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
22
|
+
# Checks if the object is not blank, returning the object itself if it's
|
23
|
+
# full, or nil if it's blank. If a method name is provided as +dispatch+,
|
24
|
+
# that method is called on the object and the result is checked for being
|
25
|
+
# full.
|
26
|
+
#
|
27
|
+
# @param dispatch [Symbol, nil] The method to call on the object (optional)
|
28
|
+
# @param args [Array] Arguments to pass to the dispatched method (optional)
|
29
|
+
# @yield [Object] Optional block to execute with the result if result or
|
30
|
+
# dispatched result not nil
|
31
|
+
# @return [Object, nil] The object itself if not blank, or the result of
|
32
|
+
# dispatching +dispatch+ if provided and valid, or nil if the object is blank
|
33
|
+
#
|
34
|
+
# @example Basic usage
|
35
|
+
# "hello".full? # => "hello"
|
36
|
+
# "".full? # => nil
|
37
|
+
#
|
38
|
+
# @example Method dispatch
|
39
|
+
# user.full?(:name) # Returns user.name if not blank, nil otherwise
|
40
|
+
#
|
41
|
+
# @example Method dispatch with arguments
|
42
|
+
# user.full?(:method_with_args, arg1, arg2)
|
43
|
+
#
|
44
|
+
# @example With block execution
|
45
|
+
# user.full?(:name) { |name| "Hello #{name}" }
|
11
46
|
def full?(dispatch = nil, *args)
|
12
47
|
if blank?
|
13
48
|
obj = nil
|
14
|
-
#elsif Module === dispatch # TODO
|
15
|
-
# dispatch.found?(self)
|
16
49
|
elsif dispatch
|
17
50
|
obj = __send__(dispatch, *args)
|
18
51
|
obj = nil if obj.blank?
|
@@ -26,14 +59,26 @@ module Tins
|
|
26
59
|
end
|
27
60
|
end
|
28
61
|
|
62
|
+
# Checks if all elements in a collection are "full" (not blank). If the
|
63
|
+
# object responds to +all?+ and all elements pass the +full?+ test, then
|
64
|
+
# the block is executed with the collection itself or the collection is returned.
|
65
|
+
#
|
66
|
+
# @return [Object, nil] The collection if all elements are full, otherwise nil
|
67
|
+
#
|
68
|
+
# @example Basic usage
|
69
|
+
# [1,2,3].all_full? # => [1,2,3]
|
70
|
+
# [1,nil,3].all_full? # => nil
|
71
|
+
#
|
72
|
+
# @example With block execution
|
73
|
+
# [1,2,3].all_full? { |array| array.sum }
|
29
74
|
def all_full?
|
30
75
|
if respond_to?(:all?) && all?(&:full?)
|
31
76
|
block_given? ? yield(self) : self
|
32
77
|
end
|
33
78
|
end
|
34
|
-
end
|
35
79
|
|
36
|
-
|
37
|
-
|
80
|
+
class ::Object
|
81
|
+
include Full
|
82
|
+
end
|
38
83
|
end
|
39
84
|
end
|
data/lib/tins/xt/irb.rb
CHANGED
@@ -1,9 +1,42 @@
|
|
1
1
|
require 'irb'
|
2
2
|
|
3
|
+
# Provides interactive debugging capabilities through the IRB console.
|
4
|
+
#
|
5
|
+
# This module adds an `examine` method to all objects, allowing developers to
|
6
|
+
# quickly drop into an interactive IRB session with the current binding or
|
7
|
+
# examine specific objects. It's particularly useful for debugging and exploring
|
8
|
+
# data structures during development.
|
9
|
+
#
|
10
|
+
# @example Basic usage
|
11
|
+
# # Drop into IRB with current context
|
12
|
+
# examine
|
13
|
+
#
|
14
|
+
# @example Examine a specific object
|
15
|
+
# data = [1, 2, 3]
|
16
|
+
# examine data # Inspects just the 'data' variable
|
17
|
+
#
|
18
|
+
# @example Use from within methods
|
19
|
+
# def process_data
|
20
|
+
# result = expensive_operation
|
21
|
+
# examine result # Debug the result immediately
|
22
|
+
# end
|
3
23
|
module Tins
|
24
|
+
# We have our own IRB as well.
|
4
25
|
IRB = ::IRB
|
5
26
|
|
27
|
+
# We extend the top level IRB module
|
6
28
|
module ::IRB
|
29
|
+
# Starts an interactive IRB session with the given binding context. This
|
30
|
+
# method creates a new IRB instance and evaluates input from it, allowing for
|
31
|
+
# interactive exploration of variables and objects.
|
32
|
+
#
|
33
|
+
# @param binding [Binding, nil] The binding context to examine (defaults to TOPLEVEL_BINDING)
|
34
|
+
#
|
35
|
+
# @example Start IRB with current context
|
36
|
+
# examine
|
37
|
+
#
|
38
|
+
# @example Examine specific binding
|
39
|
+
# examine some_binding
|
7
40
|
def self.examine(binding = TOPLEVEL_BINDING)
|
8
41
|
setup nil
|
9
42
|
workspace = WorkSpace.new binding
|
@@ -13,9 +46,20 @@ module Tins
|
|
13
46
|
rescue Interrupt
|
14
47
|
exit
|
15
48
|
end
|
16
|
-
end
|
17
49
|
|
18
|
-
|
50
|
+
# Starts an interactive IRB session examining the current object and its context.
|
51
|
+
# This instance method provides a convenient way to debug objects without
|
52
|
+
# explicitly passing bindings.
|
53
|
+
#
|
54
|
+
# @param binding [Binding, nil] The binding context to examine (defaults to TOPLEVEL_BINDING)
|
55
|
+
# @return [void]
|
56
|
+
#
|
57
|
+
# @example Examine the current object
|
58
|
+
# my_object.examine
|
59
|
+
#
|
60
|
+
# @example Examine a specific variable
|
61
|
+
# data = [1, 2, 3]
|
62
|
+
# data.examine # Inspects just the 'data' variable
|
19
63
|
def examine(binding = TOPLEVEL_BINDING)
|
20
64
|
IRB.examine(binding)
|
21
65
|
end
|
@@ -3,21 +3,9 @@ require 'tins/method_description'
|
|
3
3
|
module Tins
|
4
4
|
class ::UnboundMethod
|
5
5
|
include MethodDescription
|
6
|
-
|
7
|
-
alias to_s description
|
8
|
-
|
9
|
-
def inspect
|
10
|
-
"#<#{self.class}: #{description}>"
|
11
|
-
end
|
12
6
|
end
|
13
7
|
|
14
8
|
class ::Method
|
15
9
|
include MethodDescription
|
16
|
-
|
17
|
-
alias to_s description
|
18
|
-
|
19
|
-
def inspect
|
20
|
-
"#<#{self.class}: #{description}>"
|
21
|
-
end
|
22
10
|
end
|
23
11
|
end
|
data/lib/tins/xt/named.rb
CHANGED
@@ -1,23 +1,78 @@
|
|
1
|
-
|
1
|
+
module Tins
|
2
|
+
# A dynamically created module class used internally by the
|
3
|
+
# {Tins::Object#named} and {Tins::Module#named} methods to create dynamic
|
4
|
+
# methods.
|
5
|
+
#
|
6
|
+
# This class inherits from Module and serves as a template for creating
|
7
|
+
# dynamically scoped methods that can be extended or included into objects
|
8
|
+
# or classes.
|
9
|
+
Named = ::Class.new(::Module)
|
2
10
|
|
3
|
-
class Object
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
11
|
+
class ::Object
|
12
|
+
# Adds a dynamically created method to the object instance. The method will
|
13
|
+
# call the specified +method+ with optional +args+ and combine any provided
|
14
|
+
# +named_block+ with runtime blocks.
|
15
|
+
#
|
16
|
+
# @param name [Symbol] The name of the method to create
|
17
|
+
# @param method [Symbol] The existing method to delegate to
|
18
|
+
# @param args [Array] Optional arguments to pre-bind to the delegated method
|
19
|
+
# @yield [Object] Optional block to be used as the method's block
|
20
|
+
# @return [Object] self
|
21
|
+
# @example Create a method that maps elements
|
22
|
+
# a = [1, 2, 3]
|
23
|
+
# a.named(:double, :map) { |x| x * 2 }
|
24
|
+
# a.double # => [2, 4, 6]
|
25
|
+
#
|
26
|
+
# @example Pre-bind arguments to a method
|
27
|
+
# def process(data, multiplier, &block)
|
28
|
+
# data.map { |x| block.call(x * multiplier) }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# Object.named(:process_by_10, :process, 10) do |result|
|
32
|
+
# result + 1
|
33
|
+
# end
|
34
|
+
# process_by_10([1, 2, 3]) { |x| x * 2 } # => [21, 41, 61]
|
35
|
+
def named(name, method, *args, &named_block)
|
36
|
+
name = name.to_sym
|
37
|
+
m = Tins::Named.new {
|
38
|
+
define_method(name) do |*rest, &block|
|
39
|
+
block = named_block if named_block
|
40
|
+
__send__(method, *(args + rest), &block)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
if m.respond_to?(:set_temporary_name)
|
44
|
+
m.set_temporary_name "#{m.class} for method #{name.inspect}"
|
9
45
|
end
|
10
|
-
|
46
|
+
extend m
|
47
|
+
end
|
11
48
|
end
|
12
|
-
end
|
13
49
|
|
14
|
-
class Module
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
50
|
+
class ::Module
|
51
|
+
# Adds a dynamically created method to all instances of the class. The
|
52
|
+
# method will call the specified +method+ with optional +args+ and combine
|
53
|
+
# any provided +named_block+ with runtime blocks.
|
54
|
+
#
|
55
|
+
# @param name [Symbol] The name of the method to create
|
56
|
+
# @param method [Symbol] The existing method to delegate to
|
57
|
+
# @param args [Array] Optional arguments to pre-bind to the delegated method
|
58
|
+
# @yield [Object] Optional block to be used as the method's block
|
59
|
+
# @return [Module] self
|
60
|
+
#
|
61
|
+
# @example Create a class-level method
|
62
|
+
# Array.named(:sum_all, :reduce) { |acc, x| acc + x }
|
63
|
+
# [1, 2, 3].sum_all # => 6
|
64
|
+
def named(name, method, *args, &named_block)
|
65
|
+
name = name.to_sym
|
66
|
+
m = Tins::Named.new {
|
67
|
+
define_method(name) do |*rest, &block|
|
68
|
+
block = named_block if named_block
|
69
|
+
__send__(method, *(args + rest), &block)
|
70
|
+
end
|
71
|
+
}
|
72
|
+
if m.respond_to?(:set_temporary_name)
|
73
|
+
m.set_temporary_name "#{m.class} for method #{name.inspect}"
|
20
74
|
end
|
21
|
-
|
75
|
+
include m
|
76
|
+
end
|
22
77
|
end
|
23
78
|
end
|
data/lib/tins/xt/proc_compose.rb
CHANGED
data/lib/tins/xt/secure_write.rb
CHANGED
data/lib/tins/xt/string.rb
CHANGED
data/lib/tins/xt/subhash.rb
CHANGED
@@ -4,6 +4,17 @@ module Tins
|
|
4
4
|
class ::Hash
|
5
5
|
include Tins::Subhash
|
6
6
|
|
7
|
+
# The subhash! method creates a filtered subset of this hash based on the
|
8
|
+
# given patterns and replaces the current hash with the result.
|
9
|
+
#
|
10
|
+
# This method works by first calling subhash with the provided patterns to
|
11
|
+
# create a new hash containing only the matching key-value pairs, then
|
12
|
+
# replacing the contents of the current hash with those pairs.
|
13
|
+
#
|
14
|
+
# @param patterns [Array<Object>] One or more patterns to match against
|
15
|
+
# keys
|
16
|
+
# @return [Hash] Returns self after replacing its contents with the
|
17
|
+
# filtered subset
|
7
18
|
def subhash!(*patterns)
|
8
19
|
replace subhash(*patterns)
|
9
20
|
end
|
data/lib/tins/xt/time_freezer.rb
CHANGED
@@ -2,12 +2,49 @@ require 'tins/xt/time_dummy'
|
|
2
2
|
require 'tins/xt/date_time_dummy'
|
3
3
|
require 'tins/xt/date_dummy'
|
4
4
|
|
5
|
-
module Tins
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
module Tins
|
6
|
+
# TimeFreezer provides a mechanism to temporarily freeze time across multiple
|
7
|
+
# time-related classes.
|
8
|
+
#
|
9
|
+
# This module allows you to temporarily replace the behavior of Time, DateTime,
|
10
|
+
# and Date classes with dummy implementations that always return a specific
|
11
|
+
# time value. This is particularly useful for testing code that depends on
|
12
|
+
# current time values.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# Tins::TimeFreezer.freeze(Time.new(2023, 1, 1)) do
|
16
|
+
# # All Time, DateTime, and Date calls will return the frozen time
|
17
|
+
# puts Time.now # => 2023-01-01 00:00:00 +0000
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example With DateTime and Date
|
21
|
+
# Tins::TimeFreezer.freeze(Time.new(2023, 1, 1)) do
|
22
|
+
# puts DateTime.now # => 2023-01-01T00:00:00+00:00
|
23
|
+
# puts Date.today # => 2023-01-01
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @example Using time string (will be parsed by Time.parse)
|
27
|
+
# Tins::TimeFreezer.freeze("2023-01-01 12:00:00") do
|
28
|
+
# # Time.now will return the parsed time
|
29
|
+
# puts Time.now # => 2023-01-01 12:00:00 +0000
|
30
|
+
# end
|
31
|
+
module TimeFreezer
|
32
|
+
# Freezes time for the duration of the given block.
|
33
|
+
#
|
34
|
+
# This method temporarily replaces the behavior of Time, DateTime, and Date
|
35
|
+
# classes with dummy implementations that always return the specified time
|
36
|
+
# value.
|
37
|
+
#
|
38
|
+
# @param time [Time, DateTime, Date] The time value to freeze all
|
39
|
+
# time-related classes to
|
40
|
+
# @yield [] The block of code to execute with frozen time
|
41
|
+
# @return [Object] The return value of the yielded block
|
42
|
+
def self.freeze(time)
|
43
|
+
Time.dummy(time) do
|
44
|
+
DateTime.dummy(time) do
|
45
|
+
Date.dummy(time) do
|
46
|
+
yield
|
47
|
+
end
|
11
48
|
end
|
12
49
|
end
|
13
50
|
end
|