Snippet: ls_r (recursively list and process directories)

Here's a little Ruby function I came up with today. I thought it might be useful to other people, so here it is.

# To the extent possible under law, the author (John Croisant) has
# waived all copyright and related or neighboring rights to this work.
# See: https://creativecommons.org/publicdomain/zero/1.0/

# Return a hash of directory contents, recursively.
#
# Given a directory structure like this:
#
#     a/
#     |-- b.txt
#     `-- c/
#         |-- d.txt
#         `-- e/
#
# `ls_r("a")` will produce:
#
#     { <Pathname:a> => {
#         <Pathname:a/b.txt> => nil,
#         <Pathname:a/c> => {
#           <Pathname:a/c/d.txt> => nil,
#           <Pathname:a/c/e> => {}
#         }
#       }
#     }
#
# If a block is given, each Pathname will be passed to the block
# and the result used in the hash instead of the Pathname. This
# way, you can process the results as they are built, so you get a
# hash of things besides Pathnames.
#
# If the block returns nil, that entry and its children are discarded
# from the final result.
#
# Symlinks are not followed.
#
def ls_r( path, &block )
  require 'pathname'
  path = Pathname.new(path) unless path.is_a? Pathname

  new_path = path
  if block_given?
    new_path = yield path
    return {} if new_path.nil?
  end

  children = nil
  if path.directory?
    # Call ls_r recursively on each child. Makes an array of hashes.
    children = path.children.map { |child| ls_r( child, &block ) }

    # Combine the array of hashes into a single hash.
    # But ignore any children with a nil key.
    children = children.reduce({}){ |result, child|
      result.merge!( child.reject{ |k,v| k.nil? } )
    }
  end

  { new_path => children }
end
Previous post: Dreaming of Ambienome Next post: Nice-FFI 0.3 (and 0.2)