utils 0.68.0 → 0.69.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +251 -18
  3. data/bin/ascii7 +28 -0
  4. data/bin/blameline +17 -0
  5. data/bin/changes +69 -5
  6. data/bin/classify +128 -7
  7. data/bin/code_comment +102 -104
  8. data/bin/commit_message +26 -2
  9. data/bin/create_cstags +18 -0
  10. data/bin/create_tags +10 -0
  11. data/bin/discover +38 -1
  12. data/bin/edit +14 -1
  13. data/bin/edit_wait +14 -0
  14. data/bin/enum +139 -15
  15. data/bin/git-empty +50 -0
  16. data/bin/git-versions +20 -0
  17. data/bin/json_check +15 -1
  18. data/bin/long_lines +11 -2
  19. data/bin/myex +38 -0
  20. data/bin/on_change +22 -0
  21. data/bin/path +21 -0
  22. data/bin/print_method +29 -1
  23. data/bin/probe +52 -4
  24. data/bin/rainbow +52 -0
  25. data/bin/rd2md +15 -0
  26. data/bin/search +83 -1
  27. data/bin/sedit +6 -0
  28. data/bin/serve +18 -3
  29. data/bin/ssh-tunnel +14 -2
  30. data/bin/strip_spaces +17 -9
  31. data/bin/sync_dir +48 -1
  32. data/bin/untest +19 -1
  33. data/bin/utils-utilsrc +42 -6
  34. data/bin/vcf2alias +33 -0
  35. data/bin/yaml_check +24 -2
  36. data/lib/utils/config_dir.rb +127 -0
  37. data/lib/utils/config_file.rb +445 -1
  38. data/lib/utils/editor.rb +215 -3
  39. data/lib/utils/finder.rb +127 -16
  40. data/lib/utils/grepper.rb +90 -1
  41. data/lib/utils/irb.rb +387 -39
  42. data/lib/utils/line_blamer.rb +28 -0
  43. data/lib/utils/line_formatter.rb +198 -0
  44. data/lib/utils/md5.rb +14 -0
  45. data/lib/utils/patterns.rb +77 -3
  46. data/lib/utils/probe_server.rb +302 -23
  47. data/lib/utils/ssh_tunnel_specification.rb +58 -0
  48. data/lib/utils/version.rb +1 -1
  49. data/lib/utils/xt/source_location_extension.rb +18 -6
  50. data/lib/utils.rb +3 -1
  51. data/tests/utils_test.rb +7 -1
  52. data/utils.gemspec +5 -5
  53. metadata +4 -6
  54. data/bin/number_files +0 -26
  55. data/lib/utils/xdg_config.rb +0 -10
  56. /data/{COPYING → LICENSE} +0 -0
data/lib/utils/editor.rb CHANGED
@@ -1,10 +1,19 @@
1
- require 'tins/xt/full'
2
1
  require 'fileutils'
3
2
  require 'rbconfig'
4
3
  require 'pstree'
5
4
 
6
5
  module Utils
7
6
  class Editor
7
+ # The initialize method sets up a new editor instance with default
8
+ # configuration.
9
+ #
10
+ # This method configures the editor by initializing default values for wait
11
+ # flag, pause duration, and server name. It also loads the configuration
12
+ # file and assigns the edit configuration section to the instance.
13
+ #
14
+ # @param block [ Proc ] optional block to be executed after initialization
15
+ #
16
+ # @return [ Utils::Editor ] a new editor instance configured with default settings
8
17
  def initialize
9
18
  self.wait = false
10
19
  self.pause_duration = 1
@@ -15,28 +24,79 @@ module Utils
15
24
  yield self if block_given?
16
25
  end
17
26
 
27
+ # The derive_server_name method constructs a server name based on
28
+ # environment configuration.
29
+ #
30
+ # This method determines an appropriate server name by checking for a
31
+ # VIM_SERVER environment variable, falling back to the current working
32
+ # directory if not set. On Windows-like systems, it prefixes the name with
33
+ # "G_" to ensure uniqueness. The resulting name is converted to uppercase
34
+ # for consistent formatting.
35
+ #
36
+ # @return [ String ] the constructed server name based on environment and
37
+ # system configuration
18
38
  private def derive_server_name
19
39
  name = ENV['VIM_SERVER'] || Dir.pwd
20
40
  RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ and name = "G_#{name}"
21
41
  name.upcase
22
42
  end
23
43
 
44
+ # The pause_duration method gets or sets the pause duration value.
45
+ #
46
+ # @return [ Integer ] the current pause duration value
47
+ # @param value [ Integer ] the new pause duration value to set
24
48
  attr_accessor :pause_duration
25
49
 
50
+ # The wait method gets the wait status.
51
+ #
52
+ # @return [ TrueClass, FalseClass, nil ] the wait status value
26
53
  attr_accessor :wait
27
54
 
55
+ alias wait? wait
56
+
57
+ # The servername method provides access to the server name attribute.
58
+ #
59
+ # This method returns the value of the server name instance variable,
60
+ # which represents the name of the server being used.
61
+ #
62
+ # @return [ String ] the server name value
28
63
  attr_accessor :servername
29
64
 
65
+ # The mkdir method provides access to the directory creation flag.
66
+ #
67
+ # This method returns the current value of the mkdir flag, which determines
68
+ # whether directory creation should be attempted when processing files.
69
+ #
70
+ # @return [ TrueClass, FalseClass ] the current state of the mkdir flag
30
71
  attr_accessor :mkdir
31
72
 
73
+ # The config method provides access to the configuration object.
74
+ #
75
+ # This method returns the configuration instance variable that holds the
76
+ # settings and options for the object's operation.
77
+ #
78
+ # @return [ Utils::ConfigFile ] the configuration object associated with this instance
32
79
  attr_accessor :config
33
80
 
34
- alias wait? wait
35
-
81
+ # The vim method constructs and returns the Vim command configuration.
82
+ #
83
+ # This method assembles the Vim command by combining the configured Vim
84
+ # path with any default arguments specified in the configuration.
85
+ #
86
+ # @return [ Array<String> ] an array containing the Vim executable path and
87
+ # its default arguments for command execution
36
88
  def vim
37
89
  ([ config.vim_path ] + Array(config.vim_default_args))
38
90
  end
39
91
 
92
+ # The cmd method constructs a command from parts and executes it.
93
+ #
94
+ # This method takes multiple arguments, processes them to build a command
95
+ # array, and then executes the command using the system call.
96
+ #
97
+ # @param parts [ Array ] the parts to be included in the command
98
+ #
99
+ # @return [ Boolean ] true if the command was successful, false otherwise
40
100
  def cmd(*parts)
41
101
  command = parts.compact.inject([]) do |a, p|
42
102
  case
@@ -52,6 +112,17 @@ module Utils
52
112
  system(*command.map(&:to_s))
53
113
  end
54
114
 
115
+ # The fullscreen= method sets the fullscreen state for the remote editor
116
+ # session.
117
+ #
118
+ # This method configures the fullscreen mode of the remote editor by
119
+ # sending appropriate commands through the edit_remote_send mechanism. It
120
+ # ensures the editor session is started and paused briefly before applying
121
+ # the fullscreen
122
+ # setting, then activates the session to apply the changes.
123
+ #
124
+ # @param enabled [ TrueClass, FalseClass ] determines whether to enable or
125
+ # disable fullscreen mode
55
126
  def fullscreen=(enabled)
56
127
  start
57
128
  sleep pause_duration
@@ -63,14 +134,48 @@ module Utils
63
134
  activate
64
135
  end
65
136
 
137
+ # The file_linenumber? method checks if a filename matches the file and
138
+ # line number pattern.
139
+ #
140
+ # This method determines whether the provided filename string conforms to
141
+ # the regular expression pattern used for identifying file paths
142
+ # accompanied by line numbers.
143
+ #
144
+ # @param filename [ String ] the filename string to be checked
145
+ #
146
+ # @return [ MatchData, nil ] a match data object if the filename matches the pattern,
147
+ # or nil if it does not match
66
148
  def file_linenumber?(filename)
67
149
  filename.match(Utils::Xt::SourceLocationExtension::FILE_LINENUMBER_REGEXP)
68
150
  end
69
151
 
152
+ # The expand_globs method processes an array of filename patterns by
153
+ # expanding glob expressions and returning a sorted array of unique
154
+ # filenames.
155
+ #
156
+ # @param filenames [ Array<String> ] an array of filename patterns that may
157
+ # include glob expressions
158
+ #
159
+ # @return [ Array<String> ] a sorted array of unique filenames with glob
160
+ # patterns expanded, or the original array if no glob patterns are present
70
161
  def expand_globs(filenames)
71
162
  filenames.map { |f| Dir[f] }.flatten.uniq.sort.full? || filenames
72
163
  end
73
164
 
165
+ # The edit method processes filenames to determine their source location
166
+ # and delegates to appropriate editing methods.
167
+ #
168
+ # If a single filename is provided and it has a source location, the method
169
+ # checks whether the location includes filename and linenumber attributes.
170
+ # If so, it calls edit_source_location with the source location; otherwise,
171
+ # it calls edit_file_linenumber with the source location components.
172
+ # If multiple filenames are provided and all have source locations, the
173
+ # method expands any glob patterns in the filenames, then calls edit_file
174
+ # with the expanded list of filenames.
175
+ # Finally, it ensures the editor is activated after processing.
176
+ #
177
+ # @param filenames [ Array<String, Integer> ] an array of filenames that
178
+ # may contain source location information
74
179
  def edit(*filenames)
75
180
  source_location = nil
76
181
  if filenames.size == 1 and
@@ -89,6 +194,15 @@ module Utils
89
194
  end
90
195
  end
91
196
 
197
+ # The make_dirs method creates directory structures for the provided
198
+ # filenames.
199
+ #
200
+ # This method checks if directory creation is enabled and, if so, ensures
201
+ # that the parent directories for each filename exist by creating them
202
+ # recursively.
203
+ #
204
+ # @param filenames [ Array<String> ] an array of filenames for which to
205
+ # create directory structures
92
206
  private def make_dirs(*filenames)
93
207
  if mkdir
94
208
  for filename in filenames
@@ -97,11 +211,23 @@ module Utils
97
211
  end
98
212
  end
99
213
 
214
+ # The edit_file method processes a list of filenames by ensuring their
215
+ # directories exist and then delegates to a remote file editing function.
216
+ #
217
+ # @param filenames [ Array<String> ] an array of filename strings to be processed
100
218
  def edit_file(*filenames)
101
219
  make_dirs(*filenames)
102
220
  edit_remote_file(*filenames)
103
221
  end
104
222
 
223
+ # The edit_file_linenumber method opens a file at a specific line number
224
+ # and optionally selects a range of lines in an editor.
225
+ #
226
+ # @param filename [ String ] the path to the file to be opened
227
+ # @param linenumber [ Integer ] the line number where the file should be
228
+ # opened
229
+ # @param rangeend [ Integer, nil ] the ending line number for selection, or
230
+ # nil if no range is specified
105
231
  def edit_file_linenumber(filename, linenumber, rangeend = nil)
106
232
  make_dirs filename
107
233
  if rangeend
@@ -113,12 +239,22 @@ module Utils
113
239
  end
114
240
  end
115
241
  if wait?
242
+ activate
116
243
  edit_remote_wait("+#{linenumber}", filename)
117
244
  else
118
245
  edit_remote("+#{linenumber}", filename)
119
246
  end
120
247
  end
121
248
 
249
+ # The edit_source_location method processes a source location object to
250
+ # open the corresponding file at the specified line number.
251
+ #
252
+ # This method takes a source location object and uses its filename, line
253
+ # number, and optional range end to invoke the edit_file_linenumber method
254
+ # for opening the file in an editor.
255
+ #
256
+ # @param source_location [ Array<String, Integer> ] the source location
257
+ # containing filename and line number information
122
258
  def edit_source_location(source_location)
123
259
  edit_file_linenumber(
124
260
  source_location.filename,
@@ -127,20 +263,45 @@ module Utils
127
263
  )
128
264
  end
129
265
 
266
+ # The rename_window method renames the current tmux window to match the
267
+ # base name of the current script.
268
+ #
269
+ # This method checks if the application is running within a tmux session
270
+ # and, if so, renames the current window to reflect the base name of the
271
+ # script being executed. It only performs the renaming operation if a tmux
272
+ # session is detected and the window has not already been started.
130
273
  private def rename_window
131
274
  return if started?
132
275
  ENV['TMUX'] and system "tmux rename-window #{File.basename($0)}"
133
276
  end
134
277
 
278
+ # The start method initializes the Vim server connection if it is not
279
+ # already running.
280
+ #
281
+ # This method first attempts to rename the terminal window to reflect the
282
+ # server name, then checks if the Vim server has already been started. If
283
+ # not, it executes the command to launch the Vim server with the specified
284
+ # server name.
135
285
  def start
136
286
  rename_window
137
287
  started? or cmd(*vim, '--servername', servername)
138
288
  end
139
289
 
290
+ # The stop method sends a quit command to the remote editor.
291
+ #
292
+ # This method checks if the editor is currently running and, if so, sends a
293
+ # quit command to close all windows and terminate the editor session.
140
294
  def stop
141
295
  started? and edit_remote_send('<ESC>:qa<CR>')
142
296
  end
143
297
 
298
+ # The activate method switches to the Vim editor window or opens a new one.
299
+ #
300
+ # This method checks if the Vim default arguments include the '-g' flag to determine
301
+ # whether to open a new buffer in the current window or switch to an
302
+ # existing Vim pane. When the '-g' flag is present, it creates a temporary
303
+ # file and then closes it. Otherwise, it identifies the appropriate tmux
304
+ # pane running an editor process and switches to it.
144
305
  def activate
145
306
  if Array(config.vim_default_args).include?('-g')
146
307
  edit_remote("stupid_trick#{rand}")
@@ -160,29 +321,80 @@ module Utils
160
321
  end
161
322
  end
162
323
 
324
+ # The serverlist method retrieves a list of available Vim server names.
325
+ #
326
+ # This method executes the Vim command to list all active servers and
327
+ # returns the results as an array of server names.
328
+ #
329
+ # @return [ Array<String> ] an array of Vim server names currently available
163
330
  def serverlist
164
331
  `#{vim.map(&:inspect) * ' '} --serverlist`.split
165
332
  end
166
333
 
334
+ # The started? method checks whether a server with the given name is
335
+ # currently running.
336
+ #
337
+ # This method verifies the presence of a server in the list of active
338
+ # servers by checking if the server name exists within the serverlist.
339
+ #
340
+ # @param name [ String ] the name of the server to check for
341
+ #
342
+ # @return [ TrueClass, FalseClass ] true if the server is running, false otherwise
167
343
  def started?(name = servername)
168
344
  serverlist.member?(name)
169
345
  end
170
346
 
347
+ # The edit_remote method executes a remote Vim command with the specified
348
+ # arguments.
349
+ #
350
+ # This method prepares a command to communicate with a running Vim server
351
+ # instance, allowing for remote execution of Vim commands without directly
352
+ # interacting with the terminal. It ensures the window is renamed before
353
+ # sending the command and constructs the appropriate command line arguments
354
+ # for the Vim server interface.
355
+ #
356
+ # @param args [ Array ] the arguments to be passed to the remote Vim command
171
357
  def edit_remote(*args)
172
358
  rename_window
173
359
  cmd(*vim, '--servername', servername, '--remote', *args)
174
360
  end
175
361
 
362
+ # The edit_remote_wait method executes a command remotely and waits for its
363
+ # completion.
364
+ #
365
+ # This method sends a command to a remote server using the specified vim
366
+ # server connection, and blocks until the remote operation finishes
367
+ # executing.
368
+ #
369
+ # @param args [ Array ] the arguments to be passed to the remote command
176
370
  def edit_remote_wait(*args)
177
371
  rename_window
178
372
  cmd(*vim, '--servername', servername, '--remote-wait', *args)
179
373
  end
180
374
 
375
+ # The edit_remote_send method transmits a sequence of arguments to a remote
376
+ # Vim server for execution.
377
+ #
378
+ # This method prepares and sends commands to an already running Vim
379
+ # instance identified by its server name, allowing for remote control of
380
+ # the editor session. It ensures the window is properly named before
381
+ # sending the command, and uses the configured Vim executable along with
382
+ # its remote communication flags.
383
+ #
384
+ # @param args [ Array<String> ] the arguments to be sent to the remote Vim server
181
385
  def edit_remote_send(*args)
182
386
  rename_window
183
387
  cmd(*vim, '--servername', servername, '--remote-send', *args)
184
388
  end
185
389
 
390
+ # The edit_remote_file method delegates to either edit_remote_wait or
391
+ # edit_remote based on the wait? condition.
392
+ #
393
+ # This method determines whether to execute file editing operations with
394
+ # waiting for completion or without waiting, depending on the result of the
395
+ # wait? check.
396
+ #
397
+ # @param filenames [ Array<String> ] an array of filenames to be processed
186
398
  def edit_remote_file(*filenames)
187
399
  if wait?
188
400
  edit_remote_wait(*filenames)
data/lib/utils/finder.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'term/ansicolor'
2
- require 'tins/xt'
3
2
  require 'tempfile'
4
3
  require 'digest/md5'
5
4
  require 'fileutils'
@@ -10,6 +9,17 @@ class Utils::Finder
10
9
  include Utils::Patterns
11
10
  include Term::ANSIColor
12
11
 
12
+ # The initialize method sets up the finder instance with the provided options.
13
+ #
14
+ # This method configures the finder by processing the input options,
15
+ # including arguments, root directories, and pattern settings. It initializes
16
+ # the pattern matcher based on the specified options and prepares the index
17
+ # for searching.
18
+ #
19
+ # @param opts [ Hash ] the options hash containing configuration settings
20
+ # @option opts [ Hash ] :args the argument options for the finder
21
+ # @option opts [ Array ] :roots the root directories to search in
22
+ # @option opts [ Utils::ConfigFile ] :config the configuration file object
13
23
  def initialize(opts = {})
14
24
  @args = opts[:args] || {}
15
25
  @roots = discover_roots(opts[:roots])
@@ -27,19 +37,36 @@ class Utils::Finder
27
37
  reset_index
28
38
  end
29
39
 
40
+ # The paths reader method provides access to the array of file paths that
41
+ # have been processed or collected.
42
+ #
43
+ # This method returns the internal array containing the file paths, allowing
44
+ # external code to read the current set of paths without modifying the
45
+ # original collection.
46
+ #
47
+ # @return [ Array<String> ] an array of file path strings that have been processed or collected
30
48
  attr_reader :paths
31
49
 
50
+ # The output reader method provides access to the output value.
51
+ #
52
+ # @return [ Object ] the output value that was set previously
32
53
  attr_reader :output
33
54
 
34
- def search_index
35
- paths = load_paths
36
- search_paths(paths)
37
- end
38
-
39
- alias search search_index
40
-
55
+ # The build_paths method constructs a list of file system paths by traversing
56
+ # the configured root directories.
57
+ #
58
+ # This method iterates through the specified root directories and collects
59
+ # all file system entries, applying filtering logic to exclude certain
60
+ # directories and files based on configuration settings.
61
+ # It handles both regular files and directories, ensuring that directory
62
+ # entries are properly marked with a trailing slash for distinction. The
63
+ # resulting paths are deduplicated before being returned.
64
+ #
65
+ # @return [ Array ] an array of file system path strings, with directories
66
+ # marked by a trailing slash
41
67
  def build_paths
42
68
  paths = []
69
+
43
70
  visit = -> filename {
44
71
  s = filename.stat
45
72
  bn = filename.pathname.basename
@@ -60,6 +87,15 @@ class Utils::Finder
60
87
  paths
61
88
  end
62
89
 
90
+ # The index_path method generates a unique file path for storing finder
91
+ # results.
92
+ #
93
+ # This method creates a standardized location in the temporary directory for
94
+ # caching finder path data based on the root directories being processed.
95
+ # It ensures uniqueness by hashing the sorted root paths and uses the current
96
+ # script name as part of the directory structure.
97
+ #
98
+ # @return [ String ] the full file path where finder results should be stored
63
99
  def index_path
64
100
  roots = @roots.map { |r| File.expand_path(r) }.uniq.sort
65
101
  filename = "finder-paths-" +
@@ -69,6 +105,12 @@ class Utils::Finder
69
105
  File.join(dirname, filename)
70
106
  end
71
107
 
108
+ # The create_paths method generates and stores path information by building a
109
+ # list of paths, writing them to a secure file, and then returning the list
110
+ # of paths.
111
+ #
112
+ # @return [ Array ] an array containing the paths that were built and written
113
+ # to the index file
72
114
  def create_paths
73
115
  paths = build_paths
74
116
  File.secure_write(index_path) do |output|
@@ -77,6 +119,14 @@ class Utils::Finder
77
119
  paths
78
120
  end
79
121
 
122
+ # The load_paths method reads and processes indexed file paths from disk.
123
+ #
124
+ # This method loads lines from the index file path, removes trailing
125
+ # whitespace, and filters out directory entries if the debug flag is not set.
126
+ # It returns create_paths if the index file is empty or missing,
127
+ # otherwise it returns the processed list of file paths.
128
+ #
129
+ # @return [ Array<String> ] an array of file paths loaded from the index
80
130
  memoize method:
81
131
  def load_paths
82
132
  lines = File.readlines(index_path)
@@ -88,6 +138,16 @@ class Utils::Finder
88
138
  return create_paths
89
139
  end
90
140
 
141
+ # The reset_index method resets the index file by removing it if the reset
142
+ # flag is set or if the index has expired.
143
+ #
144
+ # This method checks whether the reset argument flag is set or if the index
145
+ # file has expired based on its modification time.
146
+ # If either condition is true, it removes the index file from the filesystem
147
+ # and clears the mize cache. The method then returns the instance itself to
148
+ # allow for method chaining.
149
+ #
150
+ # @return [ Utils::Finder ] returns self to allow for method chaining
91
151
  def reset_index
92
152
  path = index_path
93
153
  if @args[?r] || index_expired?(path)
@@ -98,14 +158,17 @@ class Utils::Finder
98
158
  self
99
159
  end
100
160
 
101
- def search_index
102
- search_paths load_paths
103
- end
104
-
105
- def search_directly
106
- search_paths build_paths
107
- end
108
-
161
+ # The search_paths method processes and filters a collection of file paths
162
+ # based on specified criteria.
163
+ #
164
+ # This method takes an array of paths and applies filtering based on file
165
+ # extensions and patterns. It handles both fuzzy and regular expression
166
+ # pattern matching, and returns formatted results with optional sorting and
167
+ # limiting of results.
168
+ #
169
+ # @param paths [ Array<String> ] the collection of file paths to be processed
170
+ #
171
+ # @return [ Utils::Finder ] returns self to allow for method chaining
109
172
  def search_paths(paths)
110
173
  suffixes = Array(@args[?I])
111
174
  suffixes.full? do |s|
@@ -148,8 +211,44 @@ class Utils::Finder
148
211
  self
149
212
  end
150
213
 
214
+ # The search_directly method performs a direct search by building paths and
215
+ # then searching through them.
216
+ #
217
+ # This method first constructs the list of paths to be searched and then
218
+ # executes the search operation on those paths, returning the results of the
219
+ # search.
220
+ #
221
+ # @return [ Object ] the result of the search operation performed on the built paths
222
+ def search_directly
223
+ search_paths build_paths
224
+ end
225
+
226
+ # The search_index method performs a pattern search across previously loaded
227
+ # paths.
228
+ #
229
+ # This method utilizes the loaded paths from the internal storage to execute
230
+ # a search operation, applying the configured pattern matching criteria to
231
+ # filter and return relevant results based on the current search
232
+ # configuration.
233
+ def search_index
234
+ search_paths load_paths
235
+ end
236
+
237
+ alias search search_index
238
+
151
239
  private
152
240
 
241
+ # The index_expired? method checks whether the index file has exceeded its
242
+ # expiration duration.
243
+ #
244
+ # This method determines if the specified index file is considered expired
245
+ # based on the configured discovery index expiration time. It compares the
246
+ # current time with the modification time of the file to make this
247
+ # determination.
248
+ #
249
+ # @param path [ String ] the filesystem path to the index file being checked
250
+ #
251
+ # @return [ TrueClass, FalseClass ] true if the index file has expired, false otherwise
153
252
  def index_expired?(path)
154
253
  if duration = @config.discover.index_expire_after
155
254
  Time.now - duration >= File.mtime(path)
@@ -160,6 +259,18 @@ class Utils::Finder
160
259
  false
161
260
  end
162
261
 
262
+ # The discover_roots method processes an array of root patterns and expands
263
+ # them into actual directory paths.
264
+ #
265
+ # This method takes an array of root patterns, which may include glob patterns,
266
+ # and uses Dir[r] to expand each pattern into matching directory paths.
267
+ # It handles the case where the input roots array is nil by defaulting to an
268
+ # empty array.
269
+ #
270
+ # @param roots [ Array<String>, nil ] an array of root patterns or nil
271
+ #
272
+ # @return [ Array<String> ] an array of expanded directory paths that match
273
+ # the input patterns
163
274
  def discover_roots(roots)
164
275
  roots ||= []
165
276
  roots.inject([]) { |rs, r| rs.concat Dir[r] }