I’m looking at flay’s #expand_dirs_to_files
today. This method takes a list of files and directories and will recursively walk them to collect all of the Ruby source files that need to be analyzed.
The Code
1 2 3 4 5 6 7 8 9 10 11 |
def self.expand_dirs_to_files *dirs extensions = ['rb'] + Flay.load_plugins dirs.flatten.map { |p| if File.directory? p then Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")] else p end }.flatten end |
Review
#expand_dirs_to_files
has four steps:
- Collect all of the file extensions
- Iterative over each item in the
dirs
list - If the item is a directory, find all files inside. Otherwise just list the item
- Collect and flatten all of the files that were found
1. Collect all of the file extensions
1 |
extensions = ['rb'] + Flay.load_plugins |
Flay builds an array of extensions here. What’s interesting to me is that this gets sent to File#join
separated by commas:
1 2 3 4 5 |
# Source File.join(p, '**', "*.{#{extensions.join(',')}}") # Expanded with extensions = ['rb', 'liquid', 'xml'] File.join(p, '**', "*.{rb,liquid,xml}}") |
It doesn’t look like the {}
syntax is documented in File#join at all.
Update: Gregor Schmidt explained in the comments below that {}
is documented in Dir#glob. So File#join
assembles a string like ./**/*.{rb,liquid,xml}
which gets passed into Dir#[]
below.
Something else I didn’t know was that flay supports plugins. An ERB plugin is included with Flay but it looks like it might be easy to write plugins for other Ruby languages like HAML or Liquid.
2. Iterative over each item in the dirs
list
1 |
dirs.flatten.map { |p| ... } |
This is just a simple iterator and since it’s the last statement in #expand_dirs_to_files
it will return the array to the caller.
3. If the item is a directory, find all files inside. Otherwise just list the item
1 2 3 4 5 |
if File.directory? p then Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")] else p end |
Here is where the files and directories are recursively walked to find all files with matching extensions. I like how this code itself isn’t recursive, but it makes use of Dir#[]
‘s recursive globs to find all files in sub-directories (using ‘**/’).
4. Collect and flatten all of the files that were found
To finish up the search, #expand_dirs_to_files
flattens the results into a single array and then returns it to the caller.
It’s interesting how #expand_dirs_to_files
made use of File#join
and Dir#[]
to do all of the file searching, but still has a nice and simple public API. This pattern would be useful anytime I need to recursively search for files in Ruby.