1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
##
# PathExpander helps pre-process command-line arguments expanding
# directories into their constituent files. It further helps by
# providing additional mechanisms to make specifying subsets easier
# with path subtraction and allowing for command-line arguments to be
# saved in a file.
#
# NOTE: this is NOT an options processor. It is a path processor
# (basically everything else besides options). It does provide a
# mechanism for pre-filtering cmdline options, but not with the intent
# of actually processing them in PathExpander. Use OptionParser to
# deal with options either before or after passing ARGV through
# PathExpander.
class PathExpander
VERSION = "1.1.0" # :nodoc:
##
# The args array to process.
attr_accessor :args
##
# The glob used to expand dirs to files.
attr_accessor :glob
##
# The path to scan if no paths are found in the initial scan.
attr_accessor :path
##
# Create a new path expander that operates on args and expands via
# glob as necessary. Takes an optional +path+ arg to fall back on if
# no paths are found on the initial scan (see #process_args).
def initialize args, glob, path = "."
self.args = args
self.glob = glob
self.path = path
end
##
# Takes an array of paths and returns an array of paths where all
# directories are expanded to all files found via the glob provided
# to PathExpander.
def expand_dirs_to_files *dirs
dirs.flatten.map { |p|
if File.directory? p then
Dir[File.join(p, glob)].find_all { |f| File.file? f }
else
p
end
}.flatten.sort
end
##
# Process a file into more arguments. Override this to add
# additional capabilities.
def process_file path
File.readlines(path).map(&:chomp)
end
##
# Enumerate over args passed to PathExpander and return a list of
# files and flags to process. Arguments are processed as:
#
# @file_of_args :: Read the file and append to args.
# -file_path :: Subtract path from file to be processed.
# -dir_path :: Expand and subtract paths from files to be processed.
# -not_a_path :: Add to flags to be processed.
# dir_path :: Expand and add to files to be processed.
# file_path :: Add to files to be processed.
#
# See expand_dirs_to_files for details on how expansion occurs.
#
# Subtraction happens last, regardless of argument ordering.
#
# If no files are found (which is not the same as having an empty
# file list after subtraction), then fall back to expanding on the
# default #path given to initialize.
def process_args
pos_files = []
neg_files = []
flags = []
clean = true
args.each do |arg|
case arg
when /^@(.*)/ then # push back on, so they can have dirs/-/@ as well
clean = false
args.concat process_file $1
when /^-(.*)/ then
if File.exist? $1 then
clean = false
neg_files += expand_dirs_to_files($1)
else
flags << arg
end
else
root_path = File.expand_path(arg) == "/" # eg: -n /./
if File.exist? arg and not root_path then
clean = false
pos_files += expand_dirs_to_files(arg)
else
flags << arg
end
end
end
files = pos_files - neg_files
files += expand_dirs_to_files(self.path) if files.empty? && clean
[files, flags]
end
##
# Process over flags and treat any special ones here. Returns an
# array of the flags you haven't processed.
#
# This version does nothing. Subclass and override for
# customization.
def process_flags flags
flags
end
##
# Top-level method processes args. It replaces args' contents with a
# new array of flags to process and returns a list of files to
# process. Eg
#
# PathExpander.new(ARGV).process.each do |f|
# puts "./#{f}"
# end
def process
files, flags = process_args
args.replace process_flags flags
files.uniq
end
##
# A file filter mechanism similar to, but not as extensive as,
# .gitignore files:
#
# + If a pattern does not contain a slash, it is treated as a shell glob.
# + If a pattern ends in a slash, it matches on directories (and contents).
# + Otherwise, it matches on relative paths.
#
# File.fnmatch is used throughout, so glob patterns work for all 3 types.
#
# Takes a list of +files+ and either an io or path of +ignore+ data
# and returns a list of files left after filtering.
def filter_files files, ignore
ignore_paths = if ignore.respond_to? :read then
ignore.read
elsif File.exist? ignore then
File.read ignore
end
if ignore_paths then
nonglobs, globs = ignore_paths.split("\n").partition { |p| p.include? "/" }
dirs, ifiles = nonglobs.partition { |p| p.end_with? "/" }
dirs = dirs.map { |s| s.chomp "/" }
dirs.map! { |i| File.expand_path i }
globs.map! { |i| File.expand_path i }
ifiles.map! { |i| File.expand_path i }
only_paths = File::FNM_PATHNAME
files = files.reject { |f|
f = File.expand_path(f)
dirs.any? { |i| File.fnmatch?(i, File.dirname(f), only_paths) } ||
globs.any? { |i| File.fnmatch?(i, f) } ||
ifiles.any? { |i| File.fnmatch?(i, f, only_paths) }
}
end
files
end
end
|