utils 0.90.0 → 0.91.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/lib/utils/irb/irb_server.rb +1 -1
- data/lib/utils/irb/regexp.rb +32 -0
- data/lib/utils/irb/shell/wrappers.rb +183 -0
- data/lib/utils/irb/shell.rb +632 -0
- data/lib/utils/irb/string.rb +36 -0
- data/lib/utils/irb.rb +4 -865
- data/lib/utils/line_formatter.rb +12 -6
- data/lib/utils/version.rb +1 -1
- data/lib/utils/xt/source_location_extension.rb +13 -4
- data/lib/utils.rb +19 -1
- data/utils.gemspec +4 -4
- metadata +9 -1
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
module Utils::IRB::Shell
|
|
2
|
+
end
|
|
3
|
+
require 'utils/irb/shell/wrappers'
|
|
4
|
+
# A module that extends Regexp functionality with additional pattern
|
|
5
|
+
# matching and display capabilities.
|
|
6
|
+
#
|
|
7
|
+
# Provides enhanced regexp operations including match highlighting and
|
|
8
|
+
# shell command integration.
|
|
9
|
+
module Utils::IRB::Shell
|
|
10
|
+
include SearchUI
|
|
11
|
+
include FileUtils
|
|
12
|
+
include Tins::Find
|
|
13
|
+
|
|
14
|
+
# The receiver_unless_main method retrieves the receiver name of a method
|
|
15
|
+
# unless it is the main object, optionally executing a block with the
|
|
16
|
+
# receiver name.
|
|
17
|
+
#
|
|
18
|
+
# @param method [ Method ] the method object to inspect
|
|
19
|
+
# @param block [ Proc ] an optional block to execute with the receiver name
|
|
20
|
+
#
|
|
21
|
+
# @return [ String, nil ] the receiver name if it is not 'main', otherwise nil
|
|
22
|
+
def receiver_unless_main(method, &block)
|
|
23
|
+
receiver_name = method.receiver.to_s
|
|
24
|
+
if receiver_name != 'main'
|
|
25
|
+
if block
|
|
26
|
+
block.(receiver_name)
|
|
27
|
+
else
|
|
28
|
+
receiver_name
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
private :receiver_unless_main
|
|
33
|
+
|
|
34
|
+
# The ri method invokes the ri documentation tool to display help
|
|
35
|
+
# information for the specified patterns. It automatically determines the
|
|
36
|
+
# pattern to search for when none are provided.
|
|
37
|
+
# The method handles different types of patterns including modules,
|
|
38
|
+
# objects that respond to to_str, and other objects. Documentation is
|
|
39
|
+
# displayed through the system's ri command with output piped to the
|
|
40
|
+
# pager.
|
|
41
|
+
#
|
|
42
|
+
# @param patterns [ Array ] the patterns to search for in the documentation
|
|
43
|
+
# @param doc [ String ] the documentation command to execute (defaults to 'ri')
|
|
44
|
+
def ri(*patterns, doc: 'ri')
|
|
45
|
+
patterns.empty? and
|
|
46
|
+
receiver_unless_main(method(__method__)) do |pattern|
|
|
47
|
+
return ri(pattern, doc: doc)
|
|
48
|
+
end
|
|
49
|
+
patterns.map! { |p|
|
|
50
|
+
case
|
|
51
|
+
when Module === p
|
|
52
|
+
p.name
|
|
53
|
+
when p.respond_to?(:to_str)
|
|
54
|
+
p.to_str
|
|
55
|
+
else
|
|
56
|
+
p.class.name
|
|
57
|
+
end
|
|
58
|
+
}
|
|
59
|
+
system "#{doc} #{patterns.map { |p| "'#{p}'" } * ' ' } | #$pager"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The yri method invokes the ri documentation tool with yri as the
|
|
63
|
+
# documenter to display help information for the specified patterns.
|
|
64
|
+
#
|
|
65
|
+
# @param patterns [ Array<String> ] the patterns to look up documentation for
|
|
66
|
+
def yri(*patterns)
|
|
67
|
+
ri(*patterns, doc: 'yri')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The ai method interacts with an Ollama chat service to process queries
|
|
71
|
+
# and optionally return responses.
|
|
72
|
+
#
|
|
73
|
+
# This method constructs command-line arguments for the ollama_chat_send
|
|
74
|
+
# utility based on the provided options, executes the command with the
|
|
75
|
+
# query as input, and returns the response if requested.
|
|
76
|
+
#
|
|
77
|
+
# @param query [ String ] the input query to send to the Ollama chat
|
|
78
|
+
# service
|
|
79
|
+
# @param command [ TrueClass, FalseClass ] whether to treat the query as
|
|
80
|
+
# a command
|
|
81
|
+
# @param respond [ TrueClass, FalseClass ] whether to capture and return
|
|
82
|
+
# the response from the service
|
|
83
|
+
# @param parse [ TrueClass, FalseClass ] whether to parse the response
|
|
84
|
+
# @param dir [ String ] the directory to use for the operation
|
|
85
|
+
#
|
|
86
|
+
# @return [ String, nil ] the response from the Ollama chat service if
|
|
87
|
+
# respond is true, otherwise nil
|
|
88
|
+
def ai(query, command: false, respond: false, parse: false, dir: ?.)
|
|
89
|
+
dir = File.expand_path(dir)
|
|
90
|
+
args = {
|
|
91
|
+
?r => respond,
|
|
92
|
+
?t => command,
|
|
93
|
+
?p => parse,
|
|
94
|
+
?d => dir,
|
|
95
|
+
}
|
|
96
|
+
args = args.map { |k, v|
|
|
97
|
+
v == false and next
|
|
98
|
+
v == true ? "-#{k}" : [ "-#{k}", v.to_s ]
|
|
99
|
+
}.flatten.compact
|
|
100
|
+
args.unshift 'ollama_chat_send'
|
|
101
|
+
response = nil
|
|
102
|
+
IO.popen(Shellwords.join(args), 'r+') do |io|
|
|
103
|
+
io.write query
|
|
104
|
+
io.close_write
|
|
105
|
+
if respond
|
|
106
|
+
response = io.read
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
response
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The irb_open method opens a URL or executes a block to capture output
|
|
113
|
+
# and open it.
|
|
114
|
+
#
|
|
115
|
+
# This method provides a way to open URLs or capture the output of a
|
|
116
|
+
# block and open it in the default application. If a URL is provided, it
|
|
117
|
+
# directly opens the URL. If a block is given, it captures the output of
|
|
118
|
+
# the block, writes it to a temporary file, and opens that file. If
|
|
119
|
+
# neither is provided, it raises an error.
|
|
120
|
+
#
|
|
121
|
+
# @param url [ String, nil ] the URL to open
|
|
122
|
+
# @param block [ Proc, nil ] the block to capture output from
|
|
123
|
+
def irb_open(url = nil, &block)
|
|
124
|
+
case
|
|
125
|
+
when url
|
|
126
|
+
system 'open', url
|
|
127
|
+
when block
|
|
128
|
+
Tempfile.open('wb') do |t|
|
|
129
|
+
t.write capture_output(&block)
|
|
130
|
+
t.rewind
|
|
131
|
+
system 'open', t.path
|
|
132
|
+
end
|
|
133
|
+
when url = receiver_unless_main(method(__method__))
|
|
134
|
+
irb_open url
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, 'need an url or block'
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# This method obtains the complete list of instance methods available for
|
|
141
|
+
# the specified object's class, then processes them through the
|
|
142
|
+
# irb_wrap_methods helper to prepare them for interactive use in IRB.
|
|
143
|
+
#
|
|
144
|
+
# @param obj [ Object ] the object whose class instance methods are to be retrieved
|
|
145
|
+
#
|
|
146
|
+
# @return [ Array ] an array of wrapped method objects suitable for IRB interaction
|
|
147
|
+
def irb_all_class_instance_methods(obj = self)
|
|
148
|
+
methods = obj.class.instance_methods
|
|
149
|
+
irb_wrap_methods obj, methods
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# The irb_class_instance_methods method retrieves instance methods
|
|
153
|
+
# defined directly in the class of the given object, excluding inherited
|
|
154
|
+
# methods, and wraps them for enhanced interactive exploration in IRB
|
|
155
|
+
# environment.
|
|
156
|
+
#
|
|
157
|
+
# @param obj [ Object ] the object whose class instance methods are to be retrieved
|
|
158
|
+
#
|
|
159
|
+
# @return [ Array ] an array of wrapped method objects suitable for IRB interaction
|
|
160
|
+
def irb_class_instance_methods(obj = self)
|
|
161
|
+
methods = obj.class.instance_methods(false)
|
|
162
|
+
irb_wrap_methods obj, methods
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The irb_all_instance_methods method retrieves all instance methods
|
|
166
|
+
# defined on a module.
|
|
167
|
+
#
|
|
168
|
+
# This method collects the instance methods from the specified module and
|
|
169
|
+
# wraps them for enhanced interactive exploration in IRB. It is designed
|
|
170
|
+
# to provide a more user-friendly interface for examining module methods
|
|
171
|
+
# within the interactive Ruby environment.
|
|
172
|
+
#
|
|
173
|
+
# @param modul [ Object ] the module from which to retrieve instance methods
|
|
174
|
+
#
|
|
175
|
+
# @return [ Array ] an array of wrapped method objects suitable for IRB interaction
|
|
176
|
+
def irb_all_instance_methods(modul = self)
|
|
177
|
+
methods = modul.instance_methods
|
|
178
|
+
irb_wrap_methods modul, methods, true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Return instance methods defined in module modul without the inherited/mixed
|
|
182
|
+
# in methods.
|
|
183
|
+
# The irb_instance_methods method retrieves instance methods defined directly in a module.
|
|
184
|
+
#
|
|
185
|
+
# This method fetches all instance methods that are explicitly defined within the specified module,
|
|
186
|
+
# excluding inherited methods. It then wraps these methods for enhanced interactive exploration
|
|
187
|
+
# within the IRB environment.
|
|
188
|
+
#
|
|
189
|
+
# @param modul [ Object ] the module from which to retrieve instance methods
|
|
190
|
+
#
|
|
191
|
+
# @return [ Array ] an array of wrapped method objects suitable for IRB interaction
|
|
192
|
+
def irb_instance_methods(modul = self)
|
|
193
|
+
methods = modul.instance_methods(false)
|
|
194
|
+
irb_wrap_methods modul, methods, true
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The irb_all_methods method retrieves all methods available on an
|
|
198
|
+
# object.
|
|
199
|
+
#
|
|
200
|
+
# This method collects all methods associated with the given object
|
|
201
|
+
# (including its singleton methods) and wraps them for enhanced
|
|
202
|
+
# interactive exploration in IRB. It provides a comprehensive list
|
|
203
|
+
# of methods that can be used to understand the object's capabilities and
|
|
204
|
+
# interface.
|
|
205
|
+
#
|
|
206
|
+
# @param obj [ Object ] the object whose methods are to be retrieved
|
|
207
|
+
#
|
|
208
|
+
# @return [ Array ] an array of wrapped method objects for interactive use
|
|
209
|
+
def irb_all_methods(obj = self)
|
|
210
|
+
methods = obj.methods
|
|
211
|
+
irb_wrap_methods obj, methods
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The irb_methods method retrieves instance methods defined in the class
|
|
215
|
+
# hierarchy excluding those inherited from ancestor classes.
|
|
216
|
+
#
|
|
217
|
+
# This method computes a list of instance methods that are directly
|
|
218
|
+
# defined in the class of the given object, excluding any methods that
|
|
219
|
+
# are inherited from its superclass or modules. It then wraps these
|
|
220
|
+
# methods for enhanced display in IRB.
|
|
221
|
+
#
|
|
222
|
+
# @param obj [ Object ] the object whose class methods are to be examined
|
|
223
|
+
#
|
|
224
|
+
# @return [ Array ] an array of wrapped method objects for display in IRB
|
|
225
|
+
def irb_methods(obj = self)
|
|
226
|
+
methods = obj.class.ancestors[1..-1].inject(obj.methods) do |all, a|
|
|
227
|
+
all -= a.instance_methods
|
|
228
|
+
end
|
|
229
|
+
irb_wrap_methods obj, methods
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# The irb_singleton_methods method retrieves singleton methods associated
|
|
233
|
+
# with an object.
|
|
234
|
+
#
|
|
235
|
+
# This method collects all singleton methods defined on the specified object,
|
|
236
|
+
# excluding inherited methods, and prepares them for display in an interactive
|
|
237
|
+
# Ruby environment.
|
|
238
|
+
#
|
|
239
|
+
# @param obj [ Object ] the object whose singleton methods are to be retrieved
|
|
240
|
+
#
|
|
241
|
+
# @return [ Array ] an array of singleton method names associated with the object
|
|
242
|
+
def irb_singleton_methods(obj = self)
|
|
243
|
+
irb_wrap_methods obj, obj.methods(false)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The irb_wrap_methods method creates wrapped method objects for introspection.
|
|
247
|
+
#
|
|
248
|
+
# This method takes a set of method names and wraps them in a way that allows
|
|
249
|
+
# for easier inspection and display within an IRB session. It handles
|
|
250
|
+
# potential errors during the wrapping process by rescuing exceptions and
|
|
251
|
+
# filtering out invalid entries.
|
|
252
|
+
#
|
|
253
|
+
# @param obj [ Object ] the object whose methods are being wrapped
|
|
254
|
+
# @param methods [ Array ] the array of method names to wrap
|
|
255
|
+
# @param modul [ TrueClass, FalseClass ] flag indicating if the methods are module methods
|
|
256
|
+
#
|
|
257
|
+
# @return [ Array ] an array of wrapped method objects sorted in ascending order
|
|
258
|
+
def irb_wrap_methods(obj = self, methods = methods(), modul = false)
|
|
259
|
+
methods.map do |name|
|
|
260
|
+
MethodWrapper.new(obj, name, modul) rescue nil
|
|
261
|
+
end.compact.sort!
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# The irb_constants method retrieves and wraps all constants from a given
|
|
265
|
+
# module.
|
|
266
|
+
#
|
|
267
|
+
# This method collects all constants defined in the specified module,
|
|
268
|
+
# creates ConstantWrapper instances for each constant, and returns them
|
|
269
|
+
# sorted in ascending order.
|
|
270
|
+
#
|
|
271
|
+
# @param modul [ Object ] the module from which to retrieve constants
|
|
272
|
+
#
|
|
273
|
+
# @return [ Array<ConstantWrapper> ] an array of ConstantWrapper objects
|
|
274
|
+
# representing the constants in the module, sorted alphabetically
|
|
275
|
+
def irb_constants(modul = self)
|
|
276
|
+
if modul.respond_to?(:constants)
|
|
277
|
+
modul.constants.map { |c| ConstantWrapper.new(modul.const_get(c), c) }.sort
|
|
278
|
+
else
|
|
279
|
+
warn "#{modul} does not respond to constants method"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# The irb_subclasses method retrieves and wraps subclass information for
|
|
284
|
+
# a given class.
|
|
285
|
+
#
|
|
286
|
+
# This method fetches the subclasses of the specified class and creates
|
|
287
|
+
# ConstantWrapper instances for each subclass, allowing them to be sorted
|
|
288
|
+
# and displayed in a structured format.
|
|
289
|
+
#
|
|
290
|
+
# @param klass [ Object ] the class object to retrieve subclasses from
|
|
291
|
+
#
|
|
292
|
+
# @return [ Array<ConstantWrapper> ] an array of ConstantWrapper objects
|
|
293
|
+
# representing the subclasses
|
|
294
|
+
def irb_subclasses(klass = self)
|
|
295
|
+
klass.subclasses.map { |c| ConstantWrapper.new(eval(c), c) }.sort
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
unless Object.const_defined?(:Infinity)
|
|
299
|
+
Infinity = 1.0 / 0 # I like to define the infinite.
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# The capture_output method captures stdout and optionally stderr output
|
|
303
|
+
# during code execution.
|
|
304
|
+
#
|
|
305
|
+
# This method temporarily redirects standard output (and optionally
|
|
306
|
+
# standard error) to a temporary file, executes the provided block, and
|
|
307
|
+
# then returns the captured output as a string.
|
|
308
|
+
#
|
|
309
|
+
# @param with_stderr [ TrueClass, FalseClass ] whether to also capture standard error output
|
|
310
|
+
#
|
|
311
|
+
# @yield [ void ] the block of code to execute while capturing output
|
|
312
|
+
#
|
|
313
|
+
# @return [ String ] the captured output as a string
|
|
314
|
+
def capture_output(with_stderr = false)
|
|
315
|
+
begin
|
|
316
|
+
old_stdout, $stdout = $stdout, Tempfile.new('irb')
|
|
317
|
+
if with_stderr
|
|
318
|
+
old_stderr, $stderr = $stderr, $stdout
|
|
319
|
+
end
|
|
320
|
+
yield
|
|
321
|
+
ensure
|
|
322
|
+
$stdout, temp = old_stdout, $stdout
|
|
323
|
+
with_stderr and $stderr = old_stderr
|
|
324
|
+
end
|
|
325
|
+
temp.rewind
|
|
326
|
+
temp.read
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Use pager on the output of the commands given in the block. The less
|
|
330
|
+
# method executes a block and outputs its result through the pager.
|
|
331
|
+
#
|
|
332
|
+
# This method runs the provided block in a controlled environment,
|
|
333
|
+
# captures its output, and streams that output through the system's
|
|
334
|
+
# configured pager for display.
|
|
335
|
+
#
|
|
336
|
+
# @param with_stderr [ TrueClass, FalseClass ] whether to include standard error in the capture
|
|
337
|
+
#
|
|
338
|
+
# @yield [ void ]
|
|
339
|
+
def less(with_stderr = false, &block)
|
|
340
|
+
IO.popen($pager, 'w') do |f|
|
|
341
|
+
f.write capture_output(with_stderr, &block)
|
|
342
|
+
f.close_write
|
|
343
|
+
end
|
|
344
|
+
nil
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# The irb_time method measures the execution time of a block and outputs
|
|
348
|
+
# the duration to standard error.
|
|
349
|
+
#
|
|
350
|
+
# @param n [ Integer ] the number of times to execute the block, defaults
|
|
351
|
+
# to 1
|
|
352
|
+
#
|
|
353
|
+
# @yield [ block ] the block to be executed and timed
|
|
354
|
+
def irb_time(n = 1, &block)
|
|
355
|
+
s = Time.now
|
|
356
|
+
n.times(&block)
|
|
357
|
+
d = Time.now - s
|
|
358
|
+
ensure
|
|
359
|
+
d ||= Time.now - s
|
|
360
|
+
if n == 1
|
|
361
|
+
warn "Took %.3fs seconds." % d
|
|
362
|
+
else
|
|
363
|
+
warn "Took %.3fs seconds, %.3fs per call (avg)." % [ d, d / n ]
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# The irb_time_result method executes a block n times while measuring
|
|
368
|
+
# execution time and returns the result of the last execution.
|
|
369
|
+
#
|
|
370
|
+
# @param n [ Integer ] the number of times to execute the block
|
|
371
|
+
#
|
|
372
|
+
# @yield [ i ]
|
|
373
|
+
#
|
|
374
|
+
# @return [ Object ] the result of the last block execution
|
|
375
|
+
def irb_time_result(n = 1)
|
|
376
|
+
r = nil
|
|
377
|
+
irb_time(n) { |i| r = yield(i) }
|
|
378
|
+
r
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# The irb_time_watch method monitors and reports performance metrics over
|
|
382
|
+
# time.
|
|
383
|
+
#
|
|
384
|
+
# This method continuously measures the output of a provided block,
|
|
385
|
+
# calculating differences and rates of change between successive
|
|
386
|
+
# measurements. It tracks these metrics and displays them with timing
|
|
387
|
+
# information, useful for observing how values evolve during execution.
|
|
388
|
+
#
|
|
389
|
+
# @param duration [ Integer ] the time interval in seconds between
|
|
390
|
+
# measurements
|
|
391
|
+
#
|
|
392
|
+
# @yield [ i ] the block to be measured, receiving the iteration count as an argument
|
|
393
|
+
def irb_time_watch(duration = 1)
|
|
394
|
+
normalize = -> value { value.ask_and_send(:to_f) || 0.0 }
|
|
395
|
+
start = Time.now
|
|
396
|
+
pre = nil
|
|
397
|
+
avg = Hash.new
|
|
398
|
+
i = 0
|
|
399
|
+
fetch_next = -> cur do
|
|
400
|
+
pre = cur.map(&normalize)
|
|
401
|
+
i += 1
|
|
402
|
+
sleep duration
|
|
403
|
+
end
|
|
404
|
+
loop do
|
|
405
|
+
cur = [ yield(i) ].flatten.map(&normalize)
|
|
406
|
+
unless pre
|
|
407
|
+
fetch_next.(cur)
|
|
408
|
+
redo
|
|
409
|
+
end
|
|
410
|
+
expired = Time.now - start
|
|
411
|
+
diffs = cur.zip(pre).map { |c, p| c - p }
|
|
412
|
+
rates = diffs.map { |d| d / duration }
|
|
413
|
+
durs = cur.zip(rates).each_with_index.map { |(c, r), i|
|
|
414
|
+
if r < 0
|
|
415
|
+
x = c.to_f / -r
|
|
416
|
+
a = avg[i].to_f
|
|
417
|
+
a -= a / 2
|
|
418
|
+
a += x / 2
|
|
419
|
+
d = Tins::Duration.new(a)
|
|
420
|
+
ds = d.to_s
|
|
421
|
+
ds.singleton_class { define_method(:to_f) { d.to_f } }
|
|
422
|
+
avg[i] = ds
|
|
423
|
+
end
|
|
424
|
+
avg[i]
|
|
425
|
+
}
|
|
426
|
+
warn "#{expired} #{cur.zip(diffs, rates, durs) * ' '} 𝝙 / per sec."
|
|
427
|
+
fetch_next.(cur)
|
|
428
|
+
sleep duration
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# The irb_write method writes text to a file or executes a block to
|
|
433
|
+
# generate content for writing.
|
|
434
|
+
#
|
|
435
|
+
# This method provides a convenient way to write content to a file,
|
|
436
|
+
# either by passing the text directly or by executing a block that
|
|
437
|
+
# generates the content. It uses secure file writing to ensure safety.
|
|
438
|
+
#
|
|
439
|
+
# @param filename [ String ] the path to the file where content will be
|
|
440
|
+
# written
|
|
441
|
+
# @param text [ String, nil ] the text content to write to the file, or
|
|
442
|
+
# nil if using a block
|
|
443
|
+
#
|
|
444
|
+
# @yield [ ] a block that generates content to be written to the file
|
|
445
|
+
def irb_write(filename, text = nil, &block)
|
|
446
|
+
if text.nil? && block
|
|
447
|
+
File.secure_write filename, nil, 'wb', &block
|
|
448
|
+
else
|
|
449
|
+
File.secure_write filename, text, 'wb'
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# The irb_read method reads the contents of a file either entirely or in
|
|
454
|
+
# chunks. When a block is provided, it reads the file in chunks of the
|
|
455
|
+
# specified size and yields each chunk to the block.
|
|
456
|
+
# If no block is given, it reads the entire file content at once and
|
|
457
|
+
# returns it as a string.
|
|
458
|
+
#
|
|
459
|
+
# @param filename [ String ] the path to the file to be read
|
|
460
|
+
# @param chunk_size [ Integer ] the size of each chunk to read when a
|
|
461
|
+
# block is provided
|
|
462
|
+
#
|
|
463
|
+
# @yield [ chunk ] yields each chunk of the file to the block
|
|
464
|
+
# @yieldparam chunk [ String ] a portion of the file content
|
|
465
|
+
#
|
|
466
|
+
# @return [ String, nil ] the entire file content if no block is given,
|
|
467
|
+
# otherwise nil
|
|
468
|
+
def irb_read(filename, chunk_size = 8_192)
|
|
469
|
+
if block_given?
|
|
470
|
+
File.open(filename) do |file|
|
|
471
|
+
until file.eof?
|
|
472
|
+
yield file.read(chunk_size)
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
nil
|
|
476
|
+
else
|
|
477
|
+
File.read filename
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# The irb_load! method loads Ruby files by their names into the current
|
|
482
|
+
# environment through an interactive selection interface.
|
|
483
|
+
#
|
|
484
|
+
# This method takes a glob pattern and finds matching Ruby files, then
|
|
485
|
+
# presents an interactive search interface for selecting which file to load.
|
|
486
|
+
# It ensures that each file is loaded only once by tracking loaded files
|
|
487
|
+
# using their paths. The method outputs messages to standard error
|
|
488
|
+
# indicating which file has been successfully loaded.
|
|
489
|
+
#
|
|
490
|
+
# @param glob [String] the glob pattern to search for Ruby files (defaults to
|
|
491
|
+
# ENV['UTILS_IRB_LOAD_GLOB'] or 'lib/**/*.rb')
|
|
492
|
+
#
|
|
493
|
+
# @return [Boolean] true if a file was successfully loaded, false if no file
|
|
494
|
+
# was selected or loaded
|
|
495
|
+
#
|
|
496
|
+
# @example
|
|
497
|
+
# # Load a file interactively with default glob pattern
|
|
498
|
+
# irb_load!
|
|
499
|
+
#
|
|
500
|
+
# # Load files matching a custom pattern
|
|
501
|
+
# irb_load!('app/models/**/*.rb')
|
|
502
|
+
#
|
|
503
|
+
# # Set environment variable for default pattern
|
|
504
|
+
# ENV['UTILS_IRB_LOAD_GLOB'] = 'lib/**/*.rb'
|
|
505
|
+
# irb_load!
|
|
506
|
+
#
|
|
507
|
+
# @note This method uses fuzzy matching to help find files when typing
|
|
508
|
+
# partial names. It respects the terminal height to limit the number of
|
|
509
|
+
# displayed results.
|
|
510
|
+
#
|
|
511
|
+
# @see SearchUI for the interactive search interface implementation
|
|
512
|
+
# @see Amatch::PairDistance for the fuzzy matching algorithm
|
|
513
|
+
def irb_load!(glob = ENV.fetch('UTILS_IRB_LOAD_GLOB', 'lib/**/*.rb'))
|
|
514
|
+
files = Dir.glob(glob)
|
|
515
|
+
found = Search.new(
|
|
516
|
+
match: -> answer {
|
|
517
|
+
matcher = Amatch::PairDistance.new(answer.downcase)
|
|
518
|
+
matches = files.map { |n| [ n, -matcher.similar(n.downcase) ] }.
|
|
519
|
+
sort.select { _2 < 0 }.sort_by(&:last).map(&:first)
|
|
520
|
+
matches.empty? and matches = files
|
|
521
|
+
matches.first(Tins::Terminal.lines - 1)
|
|
522
|
+
},
|
|
523
|
+
query: -> _answer, matches, selector {
|
|
524
|
+
matches.each_with_index.
|
|
525
|
+
map { |m, i| i == selector ? "→ " + Search.on_blue(m) : " " + m } * ?\n
|
|
526
|
+
},
|
|
527
|
+
found: -> _answer, matches, selector {
|
|
528
|
+
matches[selector]
|
|
529
|
+
},
|
|
530
|
+
output: STDOUT
|
|
531
|
+
).start
|
|
532
|
+
found or return false
|
|
533
|
+
load found
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
# The irb_server method provides access to an IRB server instance for
|
|
537
|
+
# interactive Ruby sessions.
|
|
538
|
+
#
|
|
539
|
+
# This method ensures that a single IRB server instance is created and
|
|
540
|
+
# started for the current process, loading the configuration from
|
|
541
|
+
# standard paths and using the configured server URL.
|
|
542
|
+
#
|
|
543
|
+
# @return [ Utils::IRB::IRBServer ] the IRB server instance, initialized
|
|
544
|
+
# and started if not already running
|
|
545
|
+
def irb_server
|
|
546
|
+
unless @irb_server
|
|
547
|
+
config = Utils::ConfigFile.new.tap(&:configure_from_paths)
|
|
548
|
+
@irb_server = Utils::IRB::IRBServer.new(url: config.irb_server_url).start
|
|
549
|
+
end
|
|
550
|
+
@irb_server
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# The irb_current_snippet method retrieves the current code snippet
|
|
554
|
+
# stored in the IRB server.
|
|
555
|
+
#
|
|
556
|
+
# This method accesses the IRB server instance and returns the snippet
|
|
557
|
+
# that has been stored for execution, or nil if no snippet is currently
|
|
558
|
+
# stored or if the server is not available.
|
|
559
|
+
#
|
|
560
|
+
# @return [ String, nil ] the current code snippet stored in the IRB
|
|
561
|
+
# server, or nil if not available
|
|
562
|
+
def irb_current_snippet
|
|
563
|
+
irb_server&.snippet
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# The irb_server_stop method sends a stop command to the IRB server
|
|
567
|
+
# client.
|
|
568
|
+
#
|
|
569
|
+
# This method accesses the IRB client instance and invokes the
|
|
570
|
+
# stop_server method on it, which gracefully shuts down the IRB server
|
|
571
|
+
# process.
|
|
572
|
+
#
|
|
573
|
+
# @return [ nil ] always returns nil after sending the stop command to
|
|
574
|
+
# the server
|
|
575
|
+
def irb_server_stop
|
|
576
|
+
irb_client.stop_server
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# The irb_client method provides access to an IRB server client instance.
|
|
580
|
+
#
|
|
581
|
+
# This method creates and returns a new IRB server client by first
|
|
582
|
+
# loading the configuration from standard paths and then using the
|
|
583
|
+
# configured server URL
|
|
584
|
+
# to initialize the client.
|
|
585
|
+
#
|
|
586
|
+
# @return [ Utils::IRB::IRBServer ] a new IRB server client instance configured
|
|
587
|
+
# with the URL from the application's configuration
|
|
588
|
+
def irb_client
|
|
589
|
+
config = Utils::ConfigFile.new.tap(&:configure_from_paths)
|
|
590
|
+
Utils::IRB::IRBServer.new(url: config.irb_server_url)
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# The ed method opens files for editing using the system editor.
|
|
594
|
+
#
|
|
595
|
+
# This method provides a convenient way to edit files by invoking the
|
|
596
|
+
# configured editor. When called without arguments, it edits the current
|
|
597
|
+
# object's representation. When called with file arguments, it edits those
|
|
598
|
+
# specific files.
|
|
599
|
+
#
|
|
600
|
+
# @param files [ Array ] an array of file paths to be edited
|
|
601
|
+
def ed(*files)
|
|
602
|
+
if files.empty?
|
|
603
|
+
$editor.full?(:edit, self)
|
|
604
|
+
else
|
|
605
|
+
$editor.full?(:edit, *files)
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
if defined?(ActiveRecord::Base)
|
|
610
|
+
$logger = Logger.new(STDERR)
|
|
611
|
+
# The irb_toggle_logging method toggles the logging configuration for
|
|
612
|
+
# ActiveRecord.
|
|
613
|
+
#
|
|
614
|
+
# This method manages the logger setting for ActiveRecord by switching
|
|
615
|
+
# between a custom logger and the previously configured logger. It
|
|
616
|
+
# returns true when switching to the custom logger, and false when
|
|
617
|
+
# reverting to the original logger.
|
|
618
|
+
#
|
|
619
|
+
# @return [ TrueClass, FalseClass ] true if the logger was switched to
|
|
620
|
+
# the custom logger, false if it was reverted to the original logger
|
|
621
|
+
def irb_toggle_logging
|
|
622
|
+
if ActiveRecord::Base.logger != $logger
|
|
623
|
+
$old_logger = ActiveRecord::Base.logger
|
|
624
|
+
ActiveRecord::Base.logger = $logger
|
|
625
|
+
true
|
|
626
|
+
else
|
|
627
|
+
ActiveRecord::Base.logger = $old_logger
|
|
628
|
+
false
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# A module that extends String with additional utility methods for shell
|
|
2
|
+
# command piping and file writing operations.
|
|
3
|
+
#
|
|
4
|
+
# Provides convenient methods for executing shell commands on string
|
|
5
|
+
# content and securely writing strings to files.
|
|
6
|
+
module Utils::IRB::String
|
|
7
|
+
# The | method executes a shell command and returns its output.
|
|
8
|
+
#
|
|
9
|
+
# This method takes a command string, pipes the current string to it via
|
|
10
|
+
# stdin, captures the command's stdout, and returns the resulting output
|
|
11
|
+
# as a string.
|
|
12
|
+
#
|
|
13
|
+
# @param cmd [ String ] the shell command to execute
|
|
14
|
+
#
|
|
15
|
+
# @return [ String ] the output of the executed command
|
|
16
|
+
def |(cmd)
|
|
17
|
+
IO.popen(cmd, 'w+') do |f|
|
|
18
|
+
f.write self
|
|
19
|
+
f.close_write
|
|
20
|
+
return f.read
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The >> method writes the string content to a file securely.
|
|
25
|
+
#
|
|
26
|
+
# This method takes a filename and uses File.secure_write to write the
|
|
27
|
+
# string's content to that file, ensuring secure file handling practices
|
|
28
|
+
# are followed.
|
|
29
|
+
#
|
|
30
|
+
# @param filename [ String ] the path to the file where the string content will be written
|
|
31
|
+
#
|
|
32
|
+
# @return [ Integer ] the number of bytes written to the file
|
|
33
|
+
def >>(filename)
|
|
34
|
+
File.secure_write(filename, self)
|
|
35
|
+
end
|
|
36
|
+
end
|