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/limited.rb
CHANGED
@@ -1,8 +1,34 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Tins::Limited provides a thread pool implementation that limits the number
|
5
|
+
# of concurrent threads running simultaneously.
|
6
|
+
#
|
7
|
+
# This class implements a producer-consumer pattern where you can submit
|
8
|
+
# tasks that will be executed by a fixed number of worker threads, preventing
|
9
|
+
# resource exhaustion from too many concurrent operations.
|
10
|
+
#
|
11
|
+
# @example Basic usage
|
12
|
+
# limited = Tins::Limited.new(3) # Limit to 3 concurrent threads
|
13
|
+
#
|
14
|
+
# limited.process do |l|
|
15
|
+
# 10.times do
|
16
|
+
# l.execute { puts "Task #{Thread.current.object_id}" }
|
17
|
+
# end
|
18
|
+
# l.stop # Stop processing new tasks
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example With named thread group
|
22
|
+
# limited = Tins::Limited.new(5, name: 'worker_pool')
|
23
|
+
# # Threads will be named 'worker_pool'
|
4
24
|
class Limited
|
5
|
-
# Create a Limited instance
|
25
|
+
# Create a Limited instance that runs at most _maximum_ threads
|
26
|
+
# simultaneously.
|
27
|
+
#
|
28
|
+
# @param maximum [Integer] The maximum number of concurrent worker threads
|
29
|
+
# @param name [String, nil] Optional name for the thread group
|
30
|
+
# @raise [ArgumentError] if maximum is less than 1
|
31
|
+
# @raise [TypeError] if maximum cannot be converted to Integer
|
6
32
|
def initialize(maximum, name: nil)
|
7
33
|
@maximum = Integer(maximum)
|
8
34
|
raise ArgumentError, "maximum < 1" if @maximum < 1
|
@@ -13,15 +39,28 @@ module Tins
|
|
13
39
|
@tg = ThreadGroup.new
|
14
40
|
end
|
15
41
|
|
16
|
-
# The maximum number of worker threads.
|
42
|
+
# The maximum number of worker threads that can run concurrently.
|
43
|
+
#
|
44
|
+
# @return [Integer] The maximum concurrent thread limit
|
17
45
|
attr_reader :maximum
|
18
46
|
|
19
|
-
#
|
47
|
+
# Submit a task to be executed by the thread pool.
|
48
|
+
#
|
49
|
+
# @yield [Thread] The block to execute as a task
|
50
|
+
# @raise [ArgumentError] if called before process has been started
|
20
51
|
def execute(&block)
|
21
52
|
@tasks or raise ArgumentError, "start processing first"
|
22
53
|
@tasks << block
|
23
54
|
end
|
24
55
|
|
56
|
+
# Start processing tasks with the configured thread pool.
|
57
|
+
#
|
58
|
+
# This method blocks until all tasks are completed and the processing is
|
59
|
+
# stopped. The provided block is called repeatedly to submit tasks via
|
60
|
+
# execute().
|
61
|
+
#
|
62
|
+
# @yield [Limited] The limited instance for submitting tasks
|
63
|
+
# @return [void]
|
25
64
|
def process
|
26
65
|
@tasks = Queue.new
|
27
66
|
@executor = create_executor
|
@@ -36,20 +75,32 @@ module Tins
|
|
36
75
|
end
|
37
76
|
end
|
38
77
|
|
78
|
+
# Stop processing new tasks and wait for existing tasks to complete.
|
79
|
+
#
|
80
|
+
# @return [void]
|
39
81
|
def stop
|
40
82
|
throw :stop
|
41
83
|
end
|
42
84
|
|
43
85
|
private
|
44
86
|
|
87
|
+
# Check if all tasks and threads have completed.
|
88
|
+
#
|
89
|
+
# @return [Boolean] true if no tasks remain and no threads are running
|
45
90
|
def done?
|
46
91
|
@tasks.empty? && @tg.list.empty?
|
47
92
|
end
|
48
93
|
|
94
|
+
# Wait for all threads in the thread group to complete.
|
95
|
+
#
|
96
|
+
# @return [void]
|
49
97
|
def wait
|
50
98
|
@tg.list.each(&:join)
|
51
99
|
end
|
52
100
|
|
101
|
+
# Create and start the executor thread that manages the worker pool.
|
102
|
+
#
|
103
|
+
# @return [Thread] The executor thread
|
53
104
|
def create_executor
|
54
105
|
Thread.new do
|
55
106
|
@mutex.synchronize do
|
@@ -73,5 +124,3 @@ module Tins
|
|
73
124
|
end
|
74
125
|
end
|
75
126
|
end
|
76
|
-
|
77
|
-
require 'tins/alias'
|
data/lib/tins/lines_file.rb
CHANGED
@@ -1,29 +1,74 @@
|
|
1
1
|
module Tins
|
2
|
+
# Tins::LinesFile provides enhanced file line processing capabilities.
|
3
|
+
#
|
4
|
+
# This class wraps file content in a way that allows for rich line-level
|
5
|
+
# operations, including tracking line numbers, filenames, and providing
|
6
|
+
# convenient navigation and matching methods. It's particularly useful for
|
7
|
+
# log processing, configuration files, or any scenario where you need to
|
8
|
+
# work with structured text data while maintaining context about line
|
9
|
+
# positions.
|
10
|
+
#
|
11
|
+
# @example Basic usage
|
12
|
+
# lines_file = Tins::LinesFile.for_filename('example.txt')
|
13
|
+
# lines_file.each do |line|
|
14
|
+
# puts line.file_linenumber # => "example.txt:1"
|
15
|
+
# puts line.line_number # => 1
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Line navigation and matching
|
19
|
+
# lines_file = Tins::LinesFile.for_filename('example.txt')
|
20
|
+
# lines_file.next! # Move to next line
|
21
|
+
# lines_file.match_forward(/pattern/) # Match forward from current position
|
2
22
|
class LinesFile
|
23
|
+
# Extension module that adds line metadata to individual lines.
|
24
|
+
#
|
25
|
+
# This module is automatically mixed into each line when a LinesFile is created,
|
26
|
+
# providing access to line number and filename information directly from the line objects.
|
3
27
|
module LineExtension
|
28
|
+
# @return [Integer] The line number (1-based) of this line
|
4
29
|
attr_reader :line_number
|
5
30
|
|
31
|
+
# @return [String] The filename associated with this line's source
|
6
32
|
def filename
|
7
33
|
lines_file.filename.dup
|
8
34
|
end
|
9
35
|
end
|
10
36
|
|
37
|
+
# Create a LinesFile instance from a filename.
|
38
|
+
#
|
39
|
+
# @param filename [String] Path to the file to read
|
40
|
+
# @param line_number [Integer] Starting line number (default: 1)
|
41
|
+
# @return [LinesFile] A new LinesFile instance
|
11
42
|
def self.for_filename(filename, line_number = nil)
|
12
43
|
obj = new(File.readlines(filename), line_number)
|
13
44
|
obj.filename = filename
|
14
45
|
obj
|
15
46
|
end
|
16
47
|
|
48
|
+
# Create a LinesFile instance from an already opened file.
|
49
|
+
#
|
50
|
+
# @param file [File] An open File object to read from
|
51
|
+
# @param line_number [Integer] Starting line number (default: 1)
|
52
|
+
# @return [LinesFile] A new LinesFile instance
|
17
53
|
def self.for_file(file, line_number = nil)
|
18
54
|
obj = new(file.readlines, line_number)
|
19
55
|
obj.filename = file.path
|
20
56
|
obj
|
21
57
|
end
|
22
58
|
|
59
|
+
# Create a LinesFile instance from an array of lines.
|
60
|
+
#
|
61
|
+
# @param lines [Array<String>] Array of line strings
|
62
|
+
# @param line_number [Integer] Starting line number (default: 1)
|
63
|
+
# @return [LinesFile] A new LinesFile instance
|
23
64
|
def self.for_lines(lines, line_number = nil)
|
24
65
|
new(lines, line_number)
|
25
66
|
end
|
26
67
|
|
68
|
+
# Initialize a LinesFile with lines and optional starting line number.
|
69
|
+
#
|
70
|
+
# @param lines [Array<String>] Array of line strings to process
|
71
|
+
# @param line_number [Integer] Starting line number (default: 1)
|
27
72
|
def initialize(lines, line_number = nil)
|
28
73
|
@lines = lines
|
29
74
|
@lines.each_with_index do |line, i|
|
@@ -34,27 +79,42 @@ module Tins
|
|
34
79
|
instance_variable_set :@line_number, line_number || (@lines.empty? ? 0 : 1)
|
35
80
|
end
|
36
81
|
|
82
|
+
# @return [String] The filename associated with this LinesFile
|
37
83
|
attr_accessor :filename
|
38
84
|
|
85
|
+
# @return [Integer] The current line number (1-based)
|
39
86
|
attr_reader :line_number
|
40
87
|
|
88
|
+
# Reset the current line number to the beginning.
|
89
|
+
#
|
90
|
+
# @return [LinesFile] Returns self for chaining
|
41
91
|
def rewind
|
42
92
|
self.line_number = 1
|
43
93
|
self
|
44
94
|
end
|
45
95
|
|
96
|
+
# Move to the next line.
|
97
|
+
#
|
98
|
+
# @return [LinesFile, nil] Returns self if successful, nil if at end of file
|
46
99
|
def next!
|
47
100
|
old = line_number
|
48
101
|
self.line_number += 1
|
49
102
|
line_number > old ? self : nil
|
50
103
|
end
|
51
104
|
|
105
|
+
# Move to the previous line.
|
106
|
+
#
|
107
|
+
# @return [LinesFile, nil] Returns self if successful, nil if at beginning
|
52
108
|
def previous!
|
53
109
|
old = line_number
|
54
110
|
self.line_number -= 1
|
55
111
|
line_number < old ? self : nil
|
56
112
|
end
|
57
113
|
|
114
|
+
# Set the current line number.
|
115
|
+
#
|
116
|
+
# @param number [Integer] The new line number to set
|
117
|
+
# @return [void]
|
58
118
|
def line_number=(number)
|
59
119
|
number = number.to_i
|
60
120
|
if number > 0 && number <= last_line_number
|
@@ -62,14 +122,21 @@ module Tins
|
|
62
122
|
end
|
63
123
|
end
|
64
124
|
|
125
|
+
# @return [Integer] The total number of lines in this file
|
65
126
|
def last_line_number
|
66
127
|
@lines.size
|
67
128
|
end
|
68
129
|
|
130
|
+
# @return [Boolean] True if the file has no lines
|
69
131
|
def empty?
|
70
132
|
@lines.empty?
|
71
133
|
end
|
72
134
|
|
135
|
+
# Iterate through all lines, setting the current line number for each.
|
136
|
+
#
|
137
|
+
# @yield [line] Each line in the file
|
138
|
+
# @yieldparam line [String] The current line object with line metadata
|
139
|
+
# @return [LinesFile] Returns self for chaining
|
73
140
|
def each(&block)
|
74
141
|
empty? and return self
|
75
142
|
old_line_number = line_number
|
@@ -83,15 +150,22 @@ module Tins
|
|
83
150
|
end
|
84
151
|
include Enumerable
|
85
152
|
|
153
|
+
# @return [String, nil] The current line content or nil if out of bounds
|
86
154
|
def line
|
87
155
|
index = line_number - 1
|
88
156
|
@lines[index] if index >= 0
|
89
157
|
end
|
90
158
|
|
159
|
+
# @return [String] Formatted filename and line number (e.g., "file.txt:5")
|
91
160
|
def file_linenumber
|
92
161
|
"#{filename}:#{line_number}"
|
93
162
|
end
|
94
163
|
|
164
|
+
# Match a regular expression backward from current position.
|
165
|
+
#
|
166
|
+
# @param regexp [Regexp] The regular expression to match
|
167
|
+
# @param previous_after_match [Boolean] Whether to move back one line after match
|
168
|
+
# @return [Array<String>, nil] Captured groups or nil if no match
|
95
169
|
def match_backward(regexp, previous_after_match = false)
|
96
170
|
begin
|
97
171
|
if line =~ regexp
|
@@ -101,6 +175,11 @@ module Tins
|
|
101
175
|
end while previous!
|
102
176
|
end
|
103
177
|
|
178
|
+
# Match a regular expression forward from current position.
|
179
|
+
#
|
180
|
+
# @param regexp [Regexp] The regular expression to match
|
181
|
+
# @param next_after_match [Boolean] Whether to move forward one line after match
|
182
|
+
# @return [Array<String>, nil] Captured groups or nil if no match
|
104
183
|
def match_forward(regexp, next_after_match = false)
|
105
184
|
begin
|
106
185
|
if line =~ regexp
|
@@ -110,14 +189,14 @@ module Tins
|
|
110
189
|
end while next!
|
111
190
|
end
|
112
191
|
|
192
|
+
# @return [String] String representation including line number and content
|
113
193
|
def to_s
|
114
194
|
"#{line_number} #{line.chomp}"
|
115
195
|
end
|
116
196
|
|
197
|
+
# @return [String] Detailed inspection string
|
117
198
|
def inspect
|
118
199
|
"#<#{self.class}: #{to_s.inspect}>"
|
119
200
|
end
|
120
201
|
end
|
121
202
|
end
|
122
|
-
|
123
|
-
require 'tins/alias'
|
data/lib/tins/lru_cache.rb
CHANGED
@@ -1,33 +1,57 @@
|
|
1
1
|
module Tins
|
2
|
+
# An LRU (Least Recently Used) cache implementation.
|
3
|
+
#
|
4
|
+
# This cache maintains a fixed-size collection of key-value pairs,
|
5
|
+
# automatically removing the least recently accessed item when the capacity
|
6
|
+
# is exceeded.
|
2
7
|
class LRUCache
|
3
8
|
include Enumerable
|
4
9
|
|
5
|
-
|
6
|
-
|
10
|
+
# Our "nil" value if it wasn' set by clients
|
11
|
+
NOT_EXIST = Object.new.freeze
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
define_method(:not_exist) do
|
11
|
-
my_nil
|
12
|
-
end
|
13
|
-
end
|
13
|
+
private_constant :NOT_EXIST
|
14
14
|
|
15
|
+
# Initializes a new LRU cache with the specified capacity.
|
16
|
+
#
|
17
|
+
# @param capacity [Integer] maximum number of items the cache can hold
|
15
18
|
def initialize(capacity)
|
16
|
-
@capacity = capacity
|
19
|
+
@capacity = Integer(capacity)
|
20
|
+
@capacity >= 1 or
|
21
|
+
raise ArgumentError, "capacity should be >= 1, was #@capacity"
|
17
22
|
@data = {}
|
18
23
|
end
|
19
24
|
|
25
|
+
# Returns the maximum capacity of the cache.
|
26
|
+
#
|
27
|
+
# @return [Integer] the cache capacity
|
20
28
|
attr_reader :capacity
|
21
29
|
|
30
|
+
# Retrieves the value associated with the given key.
|
31
|
+
#
|
32
|
+
# If the key exists, it is moved to the most recently used position.
|
33
|
+
# Returns nil if the key does not exist.
|
34
|
+
#
|
35
|
+
# @param key [Object] the key to look up
|
36
|
+
# @return [Object, nil] the value for the key or nil if not found
|
22
37
|
def [](key)
|
23
|
-
case value = @data.delete(key){
|
24
|
-
when
|
38
|
+
case value = @data.delete(key) { NOT_EXIST }
|
39
|
+
when NOT_EXIST
|
25
40
|
nil
|
26
41
|
else
|
27
42
|
@data[key] = value
|
28
43
|
end
|
29
44
|
end
|
30
45
|
|
46
|
+
# Associates a value with a key in the cache.
|
47
|
+
#
|
48
|
+
# If the key already exists, its position is updated to most recently used.
|
49
|
+
# If adding this item exceeds the capacity, the least recently used item is
|
50
|
+
# removed.
|
51
|
+
#
|
52
|
+
# @param key [Object] the key to set
|
53
|
+
# @param value [Object] the value to associate with the key
|
54
|
+
# @return [Object] the assigned value
|
31
55
|
def []=(key, value)
|
32
56
|
@data.delete(key)
|
33
57
|
@data[key] = value
|
@@ -37,26 +61,39 @@ module Tins
|
|
37
61
|
value
|
38
62
|
end
|
39
63
|
|
64
|
+
# Iterates over all key-value pairs in the cache.
|
65
|
+
#
|
66
|
+
# Items are yielded in order from most recently used to least recently used.
|
67
|
+
#
|
68
|
+
# @yield [key, value] yields each key-value pair
|
69
|
+
# @yieldparam key [Object] the key
|
70
|
+
# @yieldparam value [Object] the value
|
71
|
+
# @return [Enumerator] if no block is given
|
40
72
|
def each(&block)
|
41
73
|
@data.reverse_each(&block)
|
42
74
|
end
|
43
75
|
|
76
|
+
# Removes and returns the value associated with the given key.
|
77
|
+
#
|
78
|
+
# @param key [Object] the key to delete
|
79
|
+
# @return [Object, nil] the removed value or nil if not found
|
44
80
|
def delete(key)
|
45
81
|
@data.delete(key)
|
46
82
|
end
|
47
83
|
|
84
|
+
# Removes all items from the cache.
|
85
|
+
#
|
86
|
+
# @return [Tins::LRUCache] self
|
48
87
|
def clear
|
49
88
|
@data.clear
|
89
|
+
self
|
50
90
|
end
|
51
91
|
|
92
|
+
# Returns the number of items currently in the cache.
|
93
|
+
#
|
94
|
+
# @return [Integer] the current size of the cache
|
52
95
|
def size
|
53
96
|
@data.size
|
54
97
|
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def not_exist
|
59
|
-
self.class.send(:not_exist)
|
60
|
-
end
|
61
98
|
end
|
62
99
|
end
|
data/lib/tins/memoize.rb
CHANGED
@@ -1,91 +1,119 @@
|
|
1
1
|
require 'tins/extract_last_argument_options'
|
2
|
+
require 'mize'
|
2
3
|
|
3
4
|
module Tins
|
5
|
+
# Provides memoization functionality for methods and functions with support
|
6
|
+
# for instance-level and class-level caching respectively.
|
7
|
+
#
|
8
|
+
# @example Basic method memoization
|
9
|
+
# class Calculator
|
10
|
+
# def expensive_calculation(x, y)
|
11
|
+
# # Some expensive computation
|
12
|
+
# x * y
|
13
|
+
# end
|
14
|
+
# memoize_method :expensive_calculation
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Function memoization (shared across instances)
|
18
|
+
# class MathUtils
|
19
|
+
# def self.factorial(n)
|
20
|
+
# n <= 1 ? 1 : n * factorial(n - 1)
|
21
|
+
# end
|
22
|
+
# memoize_function :factorial
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example With freezing results
|
26
|
+
# class DataProcessor
|
27
|
+
# def process_data(input)
|
28
|
+
# # Process data and return result
|
29
|
+
# input.dup
|
30
|
+
# end
|
31
|
+
# memoize_method :process_data, freeze: true
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @note This module is deprecated in favor of the {https://github.com/flori/mize mize} gem.
|
35
|
+
# Use `memoize method:` or `memoize function:` from the mize gem directly.
|
4
36
|
module Memoize
|
37
|
+
# Provides cache management methods for memoized functions and methods.
|
38
|
+
# This module is included in classes that use memoization functionality.
|
39
|
+
#
|
40
|
+
# @example Using cache methods directly
|
41
|
+
# class Example
|
42
|
+
# include Tins::Memoize::CacheMethods
|
43
|
+
#
|
44
|
+
# def expensive_method
|
45
|
+
# # Some expensive computation
|
46
|
+
# end
|
47
|
+
# memoize_method :expensive_method
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# obj = Example.new
|
51
|
+
# obj.memoize_cache_clear # Clear all cached values
|
52
|
+
# obj.__memoize_cache__ # Access the internal cache object
|
5
53
|
module CacheMethods
|
6
|
-
# Return the cache object.
|
54
|
+
# Return the cache object used for memoization.
|
55
|
+
#
|
56
|
+
# @return [Object] The cache instance
|
7
57
|
def __memoize_cache__
|
8
|
-
@__memoize_cache__
|
58
|
+
if @__memoize_cache__
|
59
|
+
@__memoize_cache__
|
60
|
+
else
|
61
|
+
@__memoize_cache__ = __mize_cache__
|
62
|
+
def @__memoize_cache__.empty?
|
63
|
+
@data.empty?
|
64
|
+
end
|
65
|
+
@__memoize_cache__
|
66
|
+
end
|
9
67
|
end
|
10
68
|
|
11
|
-
# Clear cached values for all methods/functions.
|
69
|
+
# Clear cached values for all methods/functions of this object.
|
70
|
+
#
|
71
|
+
# @return [self] For chaining
|
12
72
|
def memoize_cache_clear
|
13
73
|
__memoize_cache__.clear
|
14
74
|
self
|
15
75
|
end
|
16
|
-
|
17
|
-
def memoize_apply_visibility(id)
|
18
|
-
visibility = instance_eval do
|
19
|
-
case
|
20
|
-
when private_method_defined?(id)
|
21
|
-
:private
|
22
|
-
when protected_method_defined?(id)
|
23
|
-
:protected
|
24
|
-
end
|
25
|
-
end
|
26
|
-
yield
|
27
|
-
ensure
|
28
|
-
visibility and __send__(visibility, id)
|
29
|
-
end
|
30
76
|
end
|
31
77
|
|
32
78
|
class ::Module
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
79
|
+
# Memoize a method so that its return value is cached based on the arguments
|
80
|
+
# and the object instance. Each instance maintains its own cache.
|
81
|
+
#
|
82
|
+
# @param method_ids [Array<Symbol>] One or more method names to memoize
|
83
|
+
# @option opts [Boolean] :freeze (false) Whether to freeze results
|
84
|
+
# @return [Symbol, Array<Symbol>] The memoized method name(s)
|
85
|
+
#
|
86
|
+
# @deprecated Use `memoize method:` from the mize gem instead.
|
36
87
|
def memoize_method(*method_ids)
|
88
|
+
warn "[DEPRECATION] `memoize_method` is deprecated. Please use `memoize method:` from mize gem."
|
37
89
|
method_ids.extend(ExtractLastArgumentOptions)
|
38
90
|
method_ids, opts = method_ids.extract_last_argument_options
|
39
|
-
|
40
|
-
|
41
|
-
method_id = method_id.to_s.to_sym
|
42
|
-
memoize_apply_visibility method_id do
|
43
|
-
orig_method = instance_method(method_id)
|
44
|
-
__send__(:define_method, method_id) do |*args|
|
45
|
-
mc = __memoize_cache__
|
46
|
-
if mc.key?(method_id) and result = mc[method_id][args]
|
47
|
-
result
|
48
|
-
else
|
49
|
-
(mc[method_id] ||= {})[args] = result = orig_method.bind(self).call(*args)
|
50
|
-
$DEBUG and warn "#{self.class} cached method #{method_id}(#{args.inspect unless args.empty?}) = #{result.inspect} [#{__id__}]"
|
51
|
-
opts[:freeze] and result.freeze
|
52
|
-
end
|
53
|
-
result
|
54
|
-
end
|
55
|
-
end
|
91
|
+
method_ids.each do |method|
|
92
|
+
memoize method:, **opts.slice(:freeze)
|
56
93
|
end
|
94
|
+
include CacheMethods
|
57
95
|
method_ids.size == 1 ? method_ids.first : method_ids
|
58
96
|
end
|
59
97
|
|
60
98
|
include CacheMethods
|
61
99
|
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
100
|
+
# Memoize a function (class method) so that its return value is cached
|
101
|
+
# based only on the arguments, shared across all instances of the class.
|
102
|
+
#
|
103
|
+
# @param function_ids [Array<Symbol>] One or more function names to memoize
|
104
|
+
# @option opts [Boolean] :freeze (false) Whether to freeze results
|
105
|
+
# @return [Symbol, Array<Symbol>] The memoized function name(s)
|
106
|
+
#
|
107
|
+
# @deprecated Use `memoize function:` from the mize gem instead.
|
65
108
|
def memoize_function(*function_ids)
|
109
|
+
warn "[DEPRECATION] `memoize_function` is deprecated. Please use `memoize function:` from mize gem."
|
66
110
|
function_ids.extend(ExtractLastArgumentOptions)
|
67
111
|
function_ids, opts = function_ids.extract_last_argument_options
|
68
|
-
|
69
|
-
|
70
|
-
function_id = function_id.to_s.to_sym
|
71
|
-
memoize_apply_visibility function_id do
|
72
|
-
orig_function = instance_method(function_id)
|
73
|
-
__send__(:define_method, function_id) do |*args|
|
74
|
-
if mc.key?(function_id) and result = mc[function_id][args]
|
75
|
-
result
|
76
|
-
else
|
77
|
-
(mc[function_id] ||= {})[args] = result = orig_function.bind(self).call(*args)
|
78
|
-
opts[:freeze] and result.freeze
|
79
|
-
$DEBUG and warn "#{self.class} cached function #{function_id}(#{args.inspect unless args.empty?}) = #{result.inspect}"
|
80
|
-
end
|
81
|
-
result
|
82
|
-
end
|
83
|
-
end
|
112
|
+
function_ids.each do |function|
|
113
|
+
memoize function:, **opts.slice(:freeze)
|
84
114
|
end
|
85
115
|
function_ids.size == 1 ? function_ids.first : function_ids
|
86
116
|
end
|
87
117
|
end
|
88
118
|
end
|
89
119
|
end
|
90
|
-
|
91
|
-
require 'tins/alias'
|