# DirTravel is a library for getting information about files and # directories recursively to a tree structure. This library extends # the RubyTree classes to include directory and files info. Please # refer to RubyTree documentation for RubyTree related features. # # A proxy object "DirTravel::Travel" is used to gather the directory # content. The "filetree" class method provides interface for getting # the content with few options (see: DirTravel::Travel.filetree for # details). # # See the DirTravel::Entry methods doc for possibilities in examining # the directory hierachy. # # # Examples: # require 'dirtravel' # # # Collect all entries under '.' # d = DirTravel::Travel.filetree( '.' ) # # # Display names of the entries in the hierarhcy. # d.children.each do |i| # puts i.name # end # # # Display absolute path for file (leaf level) entries. # d.each_leaf do |i| # puts i.abspath # end # # # Get MP3 files and create a list of all album directories # # (assuming "...//" hierarhcy). # d = DirTravel::Travel.filetree( '.', { :suffix => '.mp3' } ) # albums = d.select_level( d.node_height - 1 ) # module DirTravel require 'rubytree' class DirTravelError < RuntimeError; end # Extend RubyTree base class with file and directory features. class Entry < Tree::TreeNode attr_accessor :name def initialize( name ) super( name, nil ) @abspath = nil end # Return path components as Array. # # @param basedir [Entry] Starting level for the hierarchy. # @return [Array] Array of names in hierarchy. def parts( basedir = self ) parents = [] while basedir parents.push basedir.tip basedir = basedir.parent end parents.reverse end alias pathArray parts # Relative path. def path parts.join( '/' ) end # Relative path under root. # # @param level [Integer] Level down from root. def subpath( level = 1 ) pa = parts if level < 0 || level > pa.length raise DirTravelError, "Invalid index for subpath level!" end pa[ level .. -1 ].join( '/' ) end # Absolute path. def abspath if @abspath @abspath else root.abspath + '/' + subpath end end # Top directory name (usually same as name). def tip @name.split( "/" )[-1] end # Relative path of parenting directory. # # @param basedir [Entry] Starting level for the hierarchy. # @return [String] Containing directory. def dir( basedir = self ) parts( basedir.parent ).join( '/' ) end # Select all siblings from given node depth. # # @param level [Integer] Selected level in hierachy. Level is # number of steps down in hierarhcy. # @return [Array] Array of siblings in selected hierarchy. def select_level( level ) select do |i| i.node_depth == level; end end # Return all file entries in hierarchy. def files select do |i| i.kind_of?( FileEntry ) end end # File.stat data for the Entry. def stat File.stat( path ) end # Relative path Entry. def relative? @name[0] != '/' end # Rename node. # # @param name [String] If name is abspath then Entry becomes # abspath. def rename( name ) @name = name # Absolute or relative path? if name[0] == "/" @abspath = name else @abspath = nil end end end # Directory type entry. class DirEntry < Entry # Instantiate. # # @param name [String] Directory name. # @param root [Boolean] If root, abspath is set now. def initialize( name, root = false ) super( name ) if root if name[0] == '/' @abspath = name else @abspath = Dir.pwd + '/' + name end end end end # File type entry. class FileEntry < Entry attr_reader :suffix, :basename def initialize( name ) super( name ) @suffix = File.extname( name ) @basename = File.basename( name, @suffix ) end end # Create directory recursion tree (with # Travel.filetree). Optionally filter with suffix and modify # tree building with options Hash (see below). # # == Parameters: # sort:: # Sort directory entries (default: false). # suffix:: # Limit search to files with "suffix" (default: nil). # files:: # Include files to search (default: true). # inclusive:: # Basedirs parent becomes the basedir (default: false). # # Example: # d1 = DirTravel::Travel.filetree( dir1, { :sort => true, :suffix => '.mp3' } ) class Travel # Root DirEntry of Travel. attr_accessor :root # Default options for Travel. attr_accessor :defaults # Starting directory for Travel attr_accessor :basedir # Create directory recursion tree. # @param basedir [String] Starting directory (top). # @param options [Hash] Hash optionally including keys: :sort, :suffix, :files. # @return [DirEntry] Root item of the file system hierarchy. def Travel.filetree( basedir = '.', options = {} ) r = Travel.new( basedir, options ) r.travel if r.defaults[ :inclusive ] path = File.dirname( r.root.abspath ) if r.root.relative? path = File.basename( path ) end newRoot = DirEntry.new( path, true ) r.root.rename( r.root.tip ) newRoot.add( r.root ) r.root = newRoot end r.root end def initialize( basedir = '.', options = {} ) @basedir = basedir @defaults = { :suffix => nil, :sort => false, :files => true, :inclusive => false, } @defaults.merge!( options ) @root = DirEntry.new( clean( @basedir ), true ) end # Recursively get all files with suffix. Ignore suffix if # suffix is nil. def travel( suffix = @defaults[ :suffix ] ) entriesIn( @basedir, @root, suffix ) end private # Clean relative path head (remove dot etc). def clean( dir ) if dir[0] == '/' dir else File.basename( File.absolute_path( dir ) ) end end # Recursively get all files with suffix. Ignore suffix if # suffix is nil. def entriesIn( dir, node, suffix = nil ) list = entries( dir ) list.each do |i| if File.file?( dir + '/' + i ) # File entry. if ( !suffix || suffix == File.extname( i ) ) && @defaults[ :files ] node.add( FileEntry.new( i ) ) end else # Dir entry. newNode = DirEntry.new( i ) node.add( newNode ) entriesIn( dir + '/' + i, newNode, suffix ) end end self end # Directory entries with dot entries filtered out. def entries( dir ) entries = Dir.entries( dir ).select do |i| nonDotEntry( i ); end entries.sort! if @defaults[ :sort ] entries end # Test for non-dot-entry. def nonDotEntry( name ) ( name != '.' && name != '..' ) end end end