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.
data/lib/utils/irb.rb CHANGED
@@ -7,6 +7,7 @@ require 'utils'
7
7
  require 'fileutils'
8
8
  require 'amatch'
9
9
  require 'search_ui'
10
+ require 'logger'
10
11
  require_maybe 'ap'
11
12
 
12
13
  $editor = Utils::Editor.new
@@ -21,871 +22,6 @@ module Utils
21
22
  # pattern matching, shell command integration, file I/O operations,
22
23
  # performance measurement tools, and developer productivity enhancements.
23
24
  module IRB
24
- # A module that extends Regexp functionality with additional pattern
25
- # matching and display capabilities.
26
- #
27
- # Provides enhanced regexp operations including match highlighting and
28
- # shell command integration.
29
- module Shell
30
- include SearchUI
31
- include FileUtils
32
- include Tins::Find
33
-
34
- # The receiver_unless_main method retrieves the receiver name of a method
35
- # unless it is the main object, optionally executing a block with the
36
- # receiver name.
37
- #
38
- # @param method [ Method ] the method object to inspect
39
- # @param block [ Proc ] an optional block to execute with the receiver name
40
- #
41
- # @return [ String, nil ] the receiver name if it is not 'main', otherwise nil
42
- def receiver_unless_main(method, &block)
43
- receiver_name = method.receiver.to_s
44
- if receiver_name != 'main'
45
- if block
46
- block.(receiver_name)
47
- else
48
- receiver_name
49
- end
50
- end
51
- end
52
- private :receiver_unless_main
53
-
54
- # The ri method invokes the ri documentation tool to display help
55
- # information for the specified patterns. It automatically determines the
56
- # pattern to search for when none are provided.
57
- # The method handles different types of patterns including modules,
58
- # objects that respond to to_str, and other objects. Documentation is
59
- # displayed through the system's ri command with output piped to the
60
- # pager.
61
- #
62
- # @param patterns [ Array ] the patterns to search for in the documentation
63
- # @param doc [ String ] the documentation command to execute (defaults to 'ri')
64
- def ri(*patterns, doc: 'ri')
65
- patterns.empty? and
66
- receiver_unless_main(method(__method__)) do |pattern|
67
- return ri(pattern, doc: doc)
68
- end
69
- patterns.map! { |p|
70
- case
71
- when Module === p
72
- p.name
73
- when p.respond_to?(:to_str)
74
- p.to_str
75
- else
76
- p.class.name
77
- end
78
- }
79
- system "#{doc} #{patterns.map { |p| "'#{p}'" } * ' ' } | #$pager"
80
- end
81
-
82
- # The yri method invokes the ri documentation tool with yri as the
83
- # documenter to display help information for the specified patterns.
84
- #
85
- # @param patterns [ Array<String> ] the patterns to look up documentation for
86
- def yri(*patterns)
87
- ri(*patterns, doc: 'yri')
88
- end
89
-
90
- # The ai method interacts with an Ollama chat service to process queries
91
- # and optionally return responses.
92
- #
93
- # This method constructs command-line arguments for the ollama_chat_send
94
- # utility based on the provided options, executes the command with the
95
- # query as input, and returns the response if requested.
96
- #
97
- # @param query [ String ] the input query to send to the Ollama chat
98
- # service
99
- # @param command [ TrueClass, FalseClass ] whether to treat the query as
100
- # a command
101
- # @param respond [ TrueClass, FalseClass ] whether to capture and return
102
- # the response from the service
103
- # @param parse [ TrueClass, FalseClass ] whether to parse the response
104
- # @param dir [ String ] the directory to use for the operation
105
- #
106
- # @return [ String, nil ] the response from the Ollama chat service if
107
- # respond is true, otherwise nil
108
- def ai(query, command: false, respond: false, parse: false, dir: ?.)
109
- dir = File.expand_path(dir)
110
- args = {
111
- ?r => respond,
112
- ?t => command,
113
- ?p => parse,
114
- ?d => dir,
115
- }
116
- args = args.map { |k, v|
117
- v == false and next
118
- v == true ? "-#{k}" : [ "-#{k}", v.to_s ]
119
- }.flatten.compact
120
- args.unshift 'ollama_chat_send'
121
- response = nil
122
- IO.popen(Shellwords.join(args), 'r+') do |io|
123
- io.write query
124
- io.close_write
125
- if respond
126
- response = io.read
127
- end
128
- end
129
- response
130
- end
131
-
132
- # The irb_open method opens a URL or executes a block to capture output
133
- # and open it.
134
- #
135
- # This method provides a way to open URLs or capture the output of a
136
- # block and open it in the default application. If a URL is provided, it
137
- # directly opens the URL. If a block is given, it captures the output of
138
- # the block, writes it to a temporary file, and opens that file. If
139
- # neither is provided, it raises an error.
140
- #
141
- # @param url [ String, nil ] the URL to open
142
- # @param block [ Proc, nil ] the block to capture output from
143
- def irb_open(url = nil, &block)
144
- case
145
- when url
146
- system 'open', url
147
- when block
148
- Tempfile.open('wb') do |t|
149
- t.write capture_output(&block)
150
- t.rewind
151
- system 'open', t.path
152
- end
153
- when url = receiver_unless_main(method(__method__))
154
- irb_open url
155
- else
156
- raise ArgumentError, 'need an url or block'
157
- end
158
- end
159
-
160
- # This method obtains the complete list of instance methods available for
161
- # the specified object's class, then processes them through the
162
- # irb_wrap_methods helper to prepare them for interactive use in IRB.
163
- #
164
- # @param obj [ Object ] the object whose class instance methods are to be retrieved
165
- #
166
- # @return [ Array ] an array of wrapped method objects suitable for IRB interaction
167
- def irb_all_class_instance_methods(obj = self)
168
- methods = obj.class.instance_methods
169
- irb_wrap_methods obj, methods
170
- end
171
-
172
- # The irb_class_instance_methods method retrieves instance methods
173
- # defined directly in the class of the given object, excluding inherited
174
- # methods, and wraps them for enhanced interactive exploration in IRB
175
- # environment.
176
- #
177
- # @param obj [ Object ] the object whose class instance methods are to be retrieved
178
- #
179
- # @return [ Array ] an array of wrapped method objects suitable for IRB interaction
180
- def irb_class_instance_methods(obj = self)
181
- methods = obj.class.instance_methods(false)
182
- irb_wrap_methods obj, methods
183
- end
184
-
185
- # The irb_all_instance_methods method retrieves all instance methods
186
- # defined on a module.
187
- #
188
- # This method collects the instance methods from the specified module and
189
- # wraps them for enhanced interactive exploration in IRB. It is designed
190
- # to provide a more user-friendly interface for examining module methods
191
- # within the interactive Ruby environment.
192
- #
193
- # @param modul [ Object ] the module from which to retrieve instance methods
194
- #
195
- # @return [ Array ] an array of wrapped method objects suitable for IRB interaction
196
- def irb_all_instance_methods(modul = self)
197
- methods = modul.instance_methods
198
- irb_wrap_methods modul, methods, true
199
- end
200
-
201
- # Return instance methods defined in module modul without the inherited/mixed
202
- # in methods.
203
- # The irb_instance_methods method retrieves instance methods defined directly in a module.
204
- #
205
- # This method fetches all instance methods that are explicitly defined within the specified module,
206
- # excluding inherited methods. It then wraps these methods for enhanced interactive exploration
207
- # within the IRB environment.
208
- #
209
- # @param modul [ Object ] the module from which to retrieve instance methods
210
- #
211
- # @return [ Array ] an array of wrapped method objects suitable for IRB interaction
212
- def irb_instance_methods(modul = self)
213
- methods = modul.instance_methods(false)
214
- irb_wrap_methods modul, methods, true
215
- end
216
-
217
- # The irb_all_methods method retrieves all methods available on an
218
- # object.
219
- #
220
- # This method collects all methods associated with the given object
221
- # (including its singleton methods) and wraps them for enhanced
222
- # interactive exploration in IRB. It provides a comprehensive list
223
- # of methods that can be used to understand the object's capabilities and
224
- # interface.
225
- #
226
- # @param obj [ Object ] the object whose methods are to be retrieved
227
- #
228
- # @return [ Array ] an array of wrapped method objects for interactive use
229
- def irb_all_methods(obj = self)
230
- methods = obj.methods
231
- irb_wrap_methods obj, methods
232
- end
233
-
234
- # The irb_methods method retrieves instance methods defined in the class
235
- # hierarchy excluding those inherited from ancestor classes.
236
- #
237
- # This method computes a list of instance methods that are directly
238
- # defined in the class of the given object, excluding any methods that
239
- # are inherited from its superclass or modules. It then wraps these
240
- # methods for enhanced display in IRB.
241
- #
242
- # @param obj [ Object ] the object whose class methods are to be examined
243
- #
244
- # @return [ Array ] an array of wrapped method objects for display in IRB
245
- def irb_methods(obj = self)
246
- methods = obj.class.ancestors[1..-1].inject(obj.methods) do |all, a|
247
- all -= a.instance_methods
248
- end
249
- irb_wrap_methods obj, methods
250
- end
251
-
252
- # The irb_singleton_methods method retrieves singleton methods associated
253
- # with an object.
254
- #
255
- # This method collects all singleton methods defined on the specified object,
256
- # excluding inherited methods, and prepares them for display in an interactive
257
- # Ruby environment.
258
- #
259
- # @param obj [ Object ] the object whose singleton methods are to be retrieved
260
- #
261
- # @return [ Array ] an array of singleton method names associated with the object
262
- def irb_singleton_methods(obj = self)
263
- irb_wrap_methods obj, obj.methods(false)
264
- end
265
-
266
- # The irb_wrap_methods method creates wrapped method objects for introspection.
267
- #
268
- # This method takes a set of method names and wraps them in a way that allows
269
- # for easier inspection and display within an IRB session. It handles
270
- # potential errors during the wrapping process by rescuing exceptions and
271
- # filtering out invalid entries.
272
- #
273
- # @param obj [ Object ] the object whose methods are being wrapped
274
- # @param methods [ Array ] the array of method names to wrap
275
- # @param modul [ TrueClass, FalseClass ] flag indicating if the methods are module methods
276
- #
277
- # @return [ Array ] an array of wrapped method objects sorted in ascending order
278
- def irb_wrap_methods(obj = self, methods = methods(), modul = false)
279
- methods.map do |name|
280
- MethodWrapper.new(obj, name, modul) rescue nil
281
- end.compact.sort!
282
- end
283
-
284
- # Base class for wrapping objects with descriptive metadata.
285
- #
286
- # This class provides a foundation for creating wrapper objects that
287
- # associate descriptive information with underlying objects. It handles
288
- # name conversion and provides common methods for accessing and comparing
289
- # wrapped objects.
290
- class WrapperBase
291
- include Comparable
292
-
293
- # The initialize method sets up the instance name by converting the
294
- # input to a string representation.
295
- #
296
- # This method handles different input types by converting them to a
297
- # string, prioritizing to_str over to_sym and falling back to to_s if
298
- # neither is available.
299
- #
300
- # @param name [ Object ] the input name to be converted to a string
301
- def initialize(name)
302
- @name =
303
- case
304
- when name.respond_to?(:to_str)
305
- name.to_str
306
- when name.respond_to?(:to_sym)
307
- name.to_sym.to_s
308
- else
309
- name.to_s
310
- end
311
- end
312
-
313
- # The name reader method returns the value of the name instance
314
- # variable.
315
- #
316
- # @return [ String] the value stored in the name instance variable
317
- attr_reader :name
318
-
319
- # The description reader method provides access to the description
320
- # attribute.
321
- #
322
- # @return [ String, nil ] the description value or nil if not set
323
- attr_reader :description
324
-
325
- alias to_str description
326
-
327
- alias inspect description
328
-
329
- alias to_s description
330
-
331
- # The == method assigns a new name value to the instance variable.
332
- #
333
- # @param name [ Object ] the name value to be assigned
334
- #
335
- # @return [ Object ] returns the assigned name value
336
- def ==(name)
337
- @name = name
338
- end
339
-
340
- alias eql? ==
341
-
342
- # The hash method returns the hash value of the name attribute.
343
- #
344
- # @return [ Integer ] the hash value used for object identification
345
- def hash
346
- @name.hash
347
- end
348
-
349
- # The <=> method compares the names of two objects for sorting purposes.
350
- #
351
- # @param other [ Object ] the other object to compare against
352
- #
353
- # @return [ Integer ] -1 if this object's name is less than the other's,
354
- # 0 if they are equal, or 1 if this object's name is greater than the other's
355
- def <=>(other)
356
- @name <=> other.name
357
- end
358
- end
359
-
360
- # A wrapper class for Ruby method objects that provides enhanced
361
- # introspection and display capabilities.
362
- #
363
- # This class extends WrapperBase to create specialized wrappers for Ruby
364
- # method objects, offering detailed information about methods including
365
- # their source location, arity, and owner. It facilitates interactive
366
- # exploration of Ruby methods in environments like IRB by providing
367
- # structured access to method metadata and enabling sorting and
368
- # comparison operations based on method descriptions.
369
- class MethodWrapper < WrapperBase
370
- # The initialize method sets up a new instance with the specified
371
- # object, method name, and module flag.
372
- #
373
- # This method creates and configures a new instance by storing the
374
- # method object and its description, handling both instance methods and
375
- # regular methods based on the module flag parameter.
376
- #
377
- # @param obj [ Object ] the object from which to retrieve the method
378
- # @param name [ String ] the name of the method to retrieve
379
- # @param modul [ TrueClass, FalseClass ] flag indicating whether to retrieve an instance method
380
- def initialize(obj, name, modul)
381
- super(name)
382
- @wrapped_method = modul ? obj.instance_method(name) : obj.method(name)
383
- @description = @wrapped_method.description(style: :namespace)
384
- end
385
-
386
- # The method reader returns the method object associated with the
387
- # instance.
388
- attr_reader :wrapped_method
389
-
390
- # The owner method retrieves the owner of the method object.
391
- #
392
- # This method checks if the wrapped method object responds to the owner
393
- # message and returns the owner if available, otherwise it returns nil.
394
- #
395
- # @return [ Object, nil ] the owner of the method or nil if not applicable
396
- def owner
397
- @wrapped_method.respond_to?(:owner) ? @wrapped_method.owner : nil
398
- end
399
-
400
- # The arity method returns the number of parameters expected by the method.
401
- #
402
- # @return [ Integer ] the number of required parameters for the method
403
- def arity
404
- @wrapped_method.arity
405
- end
406
-
407
- # The source_location method retrieves the file path and line number
408
- # where the method is defined.
409
- #
410
- # This method accesses the underlying source location information for
411
- # the method object, returning an array that contains the filename and
412
- # line number of the method's definition.
413
- #
414
- # @return [ Array<String, Integer> ] an array containing the filename and line number
415
- # where the method is defined, or nil if the location cannot be determined
416
- def source_location
417
- @wrapped_method.source_location
418
- end
419
-
420
- # The <=> method compares the descriptions of two objects for ordering
421
- # purposes.
422
- #
423
- # @param other [ Object ] the other object to compare against
424
- #
425
- # @return [ Integer ] -1 if this object's description is less than the other's,
426
- # 0 if they are equal, or 1 if this object's description is greater than the other's
427
- def <=>(other)
428
- @description <=> other.description
429
- end
430
- end
431
-
432
- # A wrapper class for Ruby constant objects that provides enhanced
433
- # introspection and display capabilities.
434
- #
435
- # This class extends WrapperBase to create specialized wrappers for Ruby
436
- # constant objects, offering detailed information about constants
437
- # including their names and associated classes. It facilitates
438
- # interactive exploration of Ruby constants in environments like IRB by
439
- # providing structured access to constant metadata and enabling sorting
440
- # and comparison operations based on constant descriptions.
441
- class ConstantWrapper < WrapperBase
442
- # The initialize method sets up a new instance with the provided object
443
- # and name.
444
- #
445
- # This method configures the instance by storing a reference to the
446
- # object's class and creating a description string that combines the
447
- # name with the class name.
448
- #
449
- # @param obj [ Object ] the object whose class will be referenced
450
- # @param name [ String ] the name to be used in the description
451
- #
452
- # @return [ Utils::Patterns::Pattern ] a new pattern instance configured with the provided arguments
453
- def initialize(obj, name)
454
- super(name)
455
- @klass = obj.class
456
- @description = "#@name:#@klass"
457
- end
458
-
459
- # The klass reader method provides access to the class value stored in the instance.
460
- #
461
- # @return [ Object ] the class value
462
- attr_reader :klass
463
- end
464
-
465
- # The irb_constants method retrieves and wraps all constants from a given
466
- # module.
467
- #
468
- # This method collects all constants defined in the specified module,
469
- # creates ConstantWrapper instances for each constant, and returns them
470
- # sorted in ascending order.
471
- #
472
- # @param modul [ Object ] the module from which to retrieve constants
473
- #
474
- # @return [ Array<ConstantWrapper> ] an array of ConstantWrapper objects
475
- # representing the constants in the module, sorted alphabetically
476
- def irb_constants(modul = self)
477
- modul.constants.map { |c| ConstantWrapper.new(modul.const_get(c), c) }.sort
478
- end
479
-
480
- # The irb_subclasses method retrieves and wraps subclass information for
481
- # a given class.
482
- #
483
- # This method fetches the subclasses of the specified class and creates
484
- # ConstantWrapper instances for each subclass, allowing them to be sorted
485
- # and displayed in a structured format.
486
- #
487
- # @param klass [ Object ] the class object to retrieve subclasses from
488
- #
489
- # @return [ Array<ConstantWrapper> ] an array of ConstantWrapper objects
490
- # representing the subclasses
491
- def irb_subclasses(klass = self)
492
- klass.subclasses.map { |c| ConstantWrapper.new(eval(c), c) }.sort
493
- end
494
-
495
- unless Object.const_defined?(:Infinity)
496
- Infinity = 1.0 / 0 # I like to define the infinite.
497
- end
498
-
499
- # The capture_output method captures stdout and optionally stderr output
500
- # during code execution.
501
- #
502
- # This method temporarily redirects standard output (and optionally
503
- # standard error) to a temporary file, executes the provided block, and
504
- # then returns the captured output as a string.
505
- #
506
- # @param with_stderr [ TrueClass, FalseClass ] whether to also capture standard error output
507
- #
508
- # @yield [ void ] the block of code to execute while capturing output
509
- #
510
- # @return [ String ] the captured output as a string
511
- def capture_output(with_stderr = false)
512
- require 'tempfile'
513
- begin
514
- old_stdout, $stdout = $stdout, Tempfile.new('irb')
515
- if with_stderr
516
- old_stderr, $stderr = $stderr, $stdout
517
- end
518
- yield
519
- ensure
520
- $stdout, temp = old_stdout, $stdout
521
- with_stderr and $stderr = old_stderr
522
- end
523
- temp.rewind
524
- temp.read
525
- end
526
-
527
- # Use pager on the output of the commands given in the block. The less
528
- # method executes a block and outputs its result through the pager.
529
- #
530
- # This method runs the provided block in a controlled environment,
531
- # captures its output, and streams that output through the system's
532
- # configured pager for display.
533
- #
534
- # @param with_stderr [ TrueClass, FalseClass ] whether to include standard error in the capture
535
- #
536
- # @yield [ void ]
537
- def less(with_stderr = false, &block)
538
- IO.popen($pager, 'w') do |f|
539
- f.write capture_output(with_stderr, &block)
540
- f.close_write
541
- end
542
- nil
543
- end
544
-
545
- # The irb_time method measures the execution time of a block and outputs
546
- # the duration to standard error.
547
- #
548
- # @param n [ Integer ] the number of times to execute the block, defaults
549
- # to 1
550
- #
551
- # @yield [ block ] the block to be executed and timed
552
- def irb_time(n = 1, &block)
553
- s = Time.now
554
- n.times(&block)
555
- d = Time.now - s
556
- ensure
557
- d ||= Time.now - s
558
- if n == 1
559
- warn "Took %.3fs seconds." % d
560
- else
561
- warn "Took %.3fs seconds, %.3fs per call (avg)." % [ d, d / n ]
562
- end
563
- end
564
-
565
- # The irb_time_result method executes a block n times while measuring
566
- # execution time and returns the result of the last execution.
567
- #
568
- # @param n [ Integer ] the number of times to execute the block
569
- #
570
- # @yield [ i ]
571
- #
572
- # @return [ Object ] the result of the last block execution
573
- def irb_time_result(n = 1)
574
- r = nil
575
- irb_time(n) { |i| r = yield(i) }
576
- r
577
- end
578
-
579
- # The irb_time_watch method monitors and reports performance metrics over
580
- # time.
581
- #
582
- # This method continuously measures the output of a provided block,
583
- # calculating differences and rates of change between successive
584
- # measurements. It tracks these metrics and displays them with timing
585
- # information, useful for observing how values evolve during execution.
586
- #
587
- # @param duration [ Integer ] the time interval in seconds between
588
- # measurements
589
- #
590
- # @yield [ i ] the block to be measured, receiving the iteration count as an argument
591
- def irb_time_watch(duration = 1)
592
- start = Time.now
593
- pre = nil
594
- avg = Hash.new
595
- i = 0
596
- fetch_next = -> cur do
597
- pre = cur.map(&:to_f)
598
- i += 1
599
- sleep duration
600
- end
601
- loop do
602
- cur = [ yield(i) ].flatten
603
- unless pre
604
- fetch_next.(cur)
605
- redo
606
- end
607
- expired = Time.now - start
608
- diffs = cur.zip(pre).map { |c, p| c - p }
609
- rates = diffs.map { |d| d / duration }
610
- durs = cur.zip(rates).each_with_index.map { |(c, r), i|
611
- if r < 0
612
- x = c.to_f / -r
613
- a = avg[i].to_f
614
- a -= a / 2
615
- a += x / 2
616
- d = Tins::Duration.new(a)
617
- ds = d.to_s
618
- ds.singleton_class { define_method(:to_f) { d.to_f } }
619
- avg[i] = ds
620
- end
621
- avg[i]
622
- }
623
- warn "#{expired} #{cur.zip(diffs, rates, durs) * ' '} 𝝙 / per sec."
624
- fetch_next.(cur)
625
- sleep duration
626
- end
627
- end
628
-
629
- # The irb_write method writes text to a file or executes a block to
630
- # generate content for writing.
631
- #
632
- # This method provides a convenient way to write content to a file,
633
- # either by passing the text directly or by executing a block that
634
- # generates the content. It uses secure file writing to ensure safety.
635
- #
636
- # @param filename [ String ] the path to the file where content will be
637
- # written
638
- # @param text [ String, nil ] the text content to write to the file, or
639
- # nil if using a block
640
- #
641
- # @yield [ ] a block that generates content to be written to the file
642
- def irb_write(filename, text = nil, &block)
643
- if text.nil? && block
644
- File.secure_write filename, nil, 'wb', &block
645
- else
646
- File.secure_write filename, text, 'wb'
647
- end
648
- end
649
-
650
- # The irb_read method reads the contents of a file either entirely or in
651
- # chunks. When a block is provided, it reads the file in chunks of the
652
- # specified size and yields each chunk to the block.
653
- # If no block is given, it reads the entire file content at once and
654
- # returns it as a string.
655
- #
656
- # @param filename [ String ] the path to the file to be read
657
- # @param chunk_size [ Integer ] the size of each chunk to read when a
658
- # block is provided
659
- #
660
- # @yield [ chunk ] yields each chunk of the file to the block
661
- # @yieldparam chunk [ String ] a portion of the file content
662
- #
663
- # @return [ String, nil ] the entire file content if no block is given,
664
- # otherwise nil
665
- def irb_read(filename, chunk_size = 8_192)
666
- if block_given?
667
- File.open(filename) do |file|
668
- until file.eof?
669
- yield file.read(chunk_size)
670
- end
671
- end
672
- nil
673
- else
674
- File.read filename
675
- end
676
- end
677
-
678
- # The irb_load! method loads Ruby files by their names into the current
679
- # environment through an interactive selection interface.
680
- #
681
- # This method takes a glob pattern and finds matching Ruby files, then
682
- # presents an interactive search interface for selecting which file to load.
683
- # It ensures that each file is loaded only once by tracking loaded files
684
- # using their paths. The method outputs messages to standard error
685
- # indicating which file has been successfully loaded.
686
- #
687
- # @param glob [String] the glob pattern to search for Ruby files (defaults to
688
- # ENV['UTILS_IRB_LOAD_GLOB'] or 'lib/**/*.rb')
689
- #
690
- # @return [Boolean] true if a file was successfully loaded, false if no file
691
- # was selected or loaded
692
- #
693
- # @example
694
- # # Load a file interactively with default glob pattern
695
- # irb_load!
696
- #
697
- # # Load files matching a custom pattern
698
- # irb_load!('app/models/**/*.rb')
699
- #
700
- # # Set environment variable for default pattern
701
- # ENV['UTILS_IRB_LOAD_GLOB'] = 'lib/**/*.rb'
702
- # irb_load!
703
- #
704
- # @note This method uses fuzzy matching to help find files when typing
705
- # partial names. It respects the terminal height to limit the number of
706
- # displayed results.
707
- #
708
- # @see SearchUI for the interactive search interface implementation
709
- # @see Amatch::PairDistance for the fuzzy matching algorithm
710
- def irb_load!(glob = ENV.fetch('UTILS_IRB_LOAD_GLOB', 'lib/**/*.rb'))
711
- files = Dir.glob(glob)
712
- found = Search.new(
713
- match: -> answer {
714
- matcher = Amatch::PairDistance.new(answer.downcase)
715
- matches = files.map { |n| [ n, -matcher.similar(n.downcase) ] }.
716
- sort.select { _2 < 0 }.sort_by(&:last).map(&:first)
717
- matches.empty? and matches = files
718
- matches.first(Tins::Terminal.lines - 1)
719
- },
720
- query: -> _answer, matches, selector {
721
- matches.each_with_index.
722
- map { |m, i| i == selector ? "→ " + Search.on_blue(m) : " " + m } * ?\n
723
- },
724
- found: -> _answer, matches, selector {
725
- matches[selector]
726
- },
727
- output: STDOUT
728
- ).start
729
- found or return false
730
- load found
731
- end
732
-
733
- # The irb_server method provides access to an IRB server instance for
734
- # interactive Ruby sessions.
735
- #
736
- # This method ensures that a single IRB server instance is created and
737
- # started for the current process, loading the configuration from
738
- # standard paths and using the configured server URL.
739
- #
740
- # @return [ Utils::IRB::IRBServer ] the IRB server instance, initialized
741
- # and started if not already running
742
- def irb_server
743
- unless @irb_server
744
- config = Utils::ConfigFile.new.tap(&:configure_from_paths)
745
- @irb_server = Utils::IRB::IRBServer.new(url: config.irb_server_url).start
746
- end
747
- @irb_server
748
- end
749
-
750
- # The irb_server_stop method sends a stop command to the IRB server
751
- # client.
752
- #
753
- # This method accesses the IRB client instance and invokes the
754
- # stop_server method on it, which gracefully shuts down the IRB server
755
- # process.
756
- #
757
- # @return [ nil ] always returns nil after sending the stop command to
758
- # the server
759
- def irb_server_stop
760
- irb_client.stop_server
761
- end
762
-
763
- # The irb_client method provides access to an IRB server client instance.
764
- #
765
- # This method creates and returns a new IRB server client by first
766
- # loading the configuration from standard paths and then using the
767
- # configured server URL
768
- # to initialize the client.
769
- #
770
- # @return [ Utils::IRB::IRBServer ] a new IRB server client instance configured
771
- # with the URL from the application's configuration
772
- def irb_client
773
- config = Utils::ConfigFile.new.tap(&:configure_from_paths)
774
- Utils::IRB::IRBServer.new(url: config.irb_server_url)
775
- end
776
-
777
- # The ed method opens files for editing using the system editor.
778
- #
779
- # This method provides a convenient way to edit files by invoking the
780
- # configured editor. When called without arguments, it edits the current
781
- # object's representation. When called with file arguments, it edits those
782
- # specific files.
783
- #
784
- # @param files [ Array ] an array of file paths to be edited
785
- def ed(*files)
786
- if files.empty?
787
- $editor.full?(:edit, self)
788
- else
789
- $editor.full?(:edit, *files)
790
- end
791
- end
792
-
793
- if defined?(ActiveRecord::Base)
794
- $logger = Logger.new(STDERR)
795
- # The irb_toggle_logging method toggles the logging configuration for
796
- # ActiveRecord.
797
- #
798
- # This method manages the logger setting for ActiveRecord by switching
799
- # between a custom logger and the previously configured logger. It
800
- # returns true when switching to the custom logger, and false when
801
- # reverting to the original logger.
802
- #
803
- # @return [ TrueClass, FalseClass ] true if the logger was switched to
804
- # the custom logger, false if it was reverted to the original logger
805
- def irb_toggle_logging
806
- require 'logger'
807
- if ActiveRecord::Base.logger != $logger
808
- $old_logger = ActiveRecord::Base.logger
809
- ActiveRecord::Base.logger = $logger
810
- true
811
- else
812
- ActiveRecord::Base.logger = $old_logger
813
- false
814
- end
815
- end
816
- end
817
- end
818
-
819
- # A module that extends Regexp functionality with additional pattern
820
- # matching and display capabilities.
821
- #
822
- # Provides enhanced regexp operations including match highlighting and
823
- # shell command integration.
824
- #
825
- # @example
826
- # /pattern/ # => regular expression object
827
- # /pattern/.show_match("text") # => highlighted text match
828
- module Regexp
829
- # The show_match method evaluates a string against the receiver pattern
830
- # and highlights matching portions.
831
- #
832
- # This method tests whether the provided string matches the pattern
833
- # represented by the receiver. When a match is found, it applies the
834
- # success proc to highlight the matched portion of the string. If no
835
- # match is found, it applies the failure proc to indicate that no match
836
- # was found.
837
- #
838
- # @param string [ String ] the string to be tested against the pattern
839
- # @param success [ Proc ] a proc that processes the matched portion of the string
840
- # @param failure [ Proc ] a proc that processes the "no match" indication
841
- #
842
- # @return [ String ] the formatted string with matched portions highlighted or a no match message
843
- def show_match(
844
- string,
845
- success: -> s { Term::ANSIColor.green { s } },
846
- failure: -> s { Term::ANSIColor.red { s } }
847
- )
848
- string =~ self ? "#{$`}#{success.($&)}#{$'}" : failure.("no match")
849
- end
850
- end
851
-
852
- # A module that extends String with additional utility methods for shell
853
- # command piping and file writing operations.
854
- #
855
- # Provides convenient methods for executing shell commands on string
856
- # content and securely writing strings to files.
857
- module String
858
- # The | method executes a shell command and returns its output.
859
- #
860
- # This method takes a command string, pipes the current string to it via
861
- # stdin, captures the command's stdout, and returns the resulting output
862
- # as a string.
863
- #
864
- # @param cmd [ String ] the shell command to execute
865
- #
866
- # @return [ String ] the output of the executed command
867
- def |(cmd)
868
- IO.popen(cmd, 'w+') do |f|
869
- f.write self
870
- f.close_write
871
- return f.read
872
- end
873
- end
874
-
875
- # The >> method writes the string content to a file securely.
876
- #
877
- # This method takes a filename and uses File.secure_write to write the
878
- # string's content to that file, ensuring secure file handling practices
879
- # are followed.
880
- #
881
- # @param filename [ String ] the path to the file where the string content will be written
882
- #
883
- # @return [ Integer ] the number of bytes written to the file
884
- def >>(filename)
885
- File.secure_write(filename, self)
886
- end
887
- end
888
-
889
25
  # The configure method sets up IRB configuration options.
890
26
  #
891
27
  # This method configures the IRB environment by setting the history save
@@ -907,6 +43,9 @@ module Utils
907
43
  end
908
44
  end
909
45
 
46
+ require 'utils/irb/shell'
47
+ require 'utils/irb/regexp'
48
+ require 'utils/irb/string'
910
49
  require 'utils/irb/irb_server'
911
50
 
912
51
  Utils::IRB.configure