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/implement.rb
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
module Tins
|
2
|
+
# Provides methods for defining abstract method implementations that raise
|
3
|
+
# NotImplementedError. Useful for creating interface-like modules or base
|
4
|
+
# classes where certain methods must be implemented by subclasses.
|
5
|
+
#
|
6
|
+
# @example Basic usage
|
7
|
+
# module MyInterface
|
8
|
+
# include Tins::Implement
|
9
|
+
# implement :process
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class MyClass
|
13
|
+
# include MyInterface
|
14
|
+
# # Must implement process method or it will raise NotImplementedError
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example With custom messages
|
18
|
+
# module ApiInterface
|
19
|
+
# include Tins::Implement
|
20
|
+
# implement :get_data, :subclass
|
21
|
+
# implement :post_data, :submodule
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see Tins::MethodDescription For method description integration
|
2
25
|
module Implement
|
26
|
+
# Predefined error message templates for different implementation contexts.
|
27
|
+
#
|
28
|
+
# These templates provide context-specific error messages that help
|
29
|
+
# developers understand where and how methods should be implemented.
|
3
30
|
MESSAGES = {
|
4
31
|
default: 'method %{method_name} not implemented in module %{module}',
|
5
32
|
subclass: 'method %{method_name} has to be implemented in '\
|
@@ -8,6 +35,21 @@ module Tins
|
|
8
35
|
'submodules of %{module}',
|
9
36
|
}
|
10
37
|
|
38
|
+
# Defines an implementation for a method that raises NotImplementedError.
|
39
|
+
#
|
40
|
+
# @param method_name [Symbol, String] The name of the method to implement
|
41
|
+
# @param msg [Symbol, String, Hash] The error message template or options
|
42
|
+
# @option msg [Symbol] :in When msg is a Hash, specifies which message key to use
|
43
|
+
#
|
44
|
+
# @example Basic usage
|
45
|
+
# implement :process
|
46
|
+
# # => Defines process method that raises NotImplementedError
|
47
|
+
#
|
48
|
+
# @example Custom message
|
49
|
+
# implement :calculate, 'Custom error message'
|
50
|
+
#
|
51
|
+
# @example Using predefined message template
|
52
|
+
# implement :validate, :subclass
|
11
53
|
def implement(method_name, msg = :default)
|
12
54
|
method_name.nil? and return
|
13
55
|
case msg
|
@@ -30,6 +72,14 @@ module Tins
|
|
30
72
|
end
|
31
73
|
end
|
32
74
|
|
75
|
+
# Defines an implementation for a method that raises NotImplementedError
|
76
|
+
# specifically for submodule implementations.
|
77
|
+
#
|
78
|
+
# @param method_name [Symbol, String] The name of the method to implement
|
79
|
+
#
|
80
|
+
# @example Usage
|
81
|
+
# implement_in_submodule :render
|
82
|
+
# # => Defines render method with submodule-specific error message
|
33
83
|
def implement_in_submodule(method_name)
|
34
84
|
implement method_name,
|
35
85
|
'method %{method_name} has to be implemented in submodules of'\
|
data/lib/tins/limited.rb
CHANGED
@@ -1,50 +1,126 @@
|
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
32
|
+
def initialize(maximum, name: nil)
|
33
|
+
@maximum = Integer(maximum)
|
10
34
|
raise ArgumentError, "maximum < 1" if @maximum < 1
|
11
|
-
@
|
12
|
-
@
|
35
|
+
@mutex = Mutex.new
|
36
|
+
@continue = ConditionVariable.new
|
37
|
+
@name = name
|
38
|
+
@count = 0
|
39
|
+
@tg = ThreadGroup.new
|
13
40
|
end
|
14
41
|
|
15
|
-
# 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
|
16
45
|
attr_reader :maximum
|
17
46
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
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
|
51
|
+
def execute(&block)
|
52
|
+
@tasks or raise ArgumentError, "start processing first"
|
53
|
+
@tasks << block
|
54
|
+
end
|
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]
|
64
|
+
def process
|
65
|
+
@tasks = Queue.new
|
66
|
+
@executor = create_executor
|
67
|
+
@executor.name = @name if @name
|
68
|
+
catch :stop do
|
21
69
|
loop do
|
22
|
-
|
23
|
-
@count += 1
|
24
|
-
Thread.new do
|
25
|
-
@tg.add Thread.current
|
26
|
-
yield
|
27
|
-
@mutex.synchronize { @count -= 1 }
|
28
|
-
@continue.signal
|
29
|
-
end
|
30
|
-
return
|
31
|
-
else
|
32
|
-
@continue.wait(@mutex)
|
33
|
-
end
|
70
|
+
yield self
|
34
71
|
end
|
72
|
+
ensure
|
73
|
+
wait until done?
|
74
|
+
@executor.kill
|
35
75
|
end
|
36
76
|
end
|
37
77
|
|
78
|
+
# Stop processing new tasks and wait for existing tasks to complete.
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def stop
|
82
|
+
throw :stop
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Check if all tasks and threads have completed.
|
88
|
+
#
|
89
|
+
# @return [Boolean] true if no tasks remain and no threads are running
|
90
|
+
def done?
|
91
|
+
@tasks.empty? && @tg.list.empty?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Wait for all threads in the thread group to complete.
|
95
|
+
#
|
96
|
+
# @return [void]
|
38
97
|
def wait
|
39
98
|
@tg.list.each(&:join)
|
40
99
|
end
|
41
100
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
101
|
+
# Create and start the executor thread that manages the worker pool.
|
102
|
+
#
|
103
|
+
# @return [Thread] The executor thread
|
104
|
+
def create_executor
|
105
|
+
Thread.new do
|
106
|
+
@mutex.synchronize do
|
107
|
+
loop do
|
108
|
+
if @count < @maximum
|
109
|
+
task = @tasks.pop
|
110
|
+
@count += 1
|
111
|
+
Thread.new do
|
112
|
+
@tg.add Thread.current
|
113
|
+
task.(Thread.current)
|
114
|
+
ensure
|
115
|
+
@count -= 1
|
116
|
+
@continue.signal
|
117
|
+
end
|
118
|
+
else
|
119
|
+
@continue.wait(@mutex)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
46
124
|
end
|
47
125
|
end
|
48
126
|
end
|
49
|
-
|
50
|
-
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
|