1require 'rake/cloneable' 2require 'rake/file_utils_ext' 3require 'rake/pathmap' 4 5###################################################################### 6module Rake 7 8 # ######################################################################### 9 # A FileList is essentially an array with a few helper methods defined to 10 # make file manipulation a bit easier. 11 # 12 # FileLists are lazy. When given a list of glob patterns for possible files 13 # to be included in the file list, instead of searching the file structures 14 # to find the files, a FileList holds the pattern for latter use. 15 # 16 # This allows us to define a number of FileList to match any number of 17 # files, but only search out the actual files when then FileList itself is 18 # actually used. The key is that the first time an element of the 19 # FileList/Array is requested, the pending patterns are resolved into a real 20 # list of file names. 21 # 22 class FileList 23 24 include Cloneable 25 26 # == Method Delegation 27 # 28 # The lazy evaluation magic of FileLists happens by implementing all the 29 # array specific methods to call +resolve+ before delegating the heavy 30 # lifting to an embedded array object (@items). 31 # 32 # In addition, there are two kinds of delegation calls. The regular kind 33 # delegates to the @items array and returns the result directly. Well, 34 # almost directly. It checks if the returned value is the @items object 35 # itself, and if so will return the FileList object instead. 36 # 37 # The second kind of delegation call is used in methods that normally 38 # return a new Array object. We want to capture the return value of these 39 # methods and wrap them in a new FileList object. We enumerate these 40 # methods in the +SPECIAL_RETURN+ list below. 41 42 # List of array methods (that are not in +Object+) that need to be 43 # delegated. 44 ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s } 45 46 # List of additional methods that must be delegated. 47 MUST_DEFINE = %w[to_a inspect <=>] 48 49 # List of methods that should not be delegated here (we define special 50 # versions of them explicitly below). 51 MUST_NOT_DEFINE = %w[to_a to_ary partition *] 52 53 # List of delegated methods that return new array values which need 54 # wrapping. 55 SPECIAL_RETURN = %w[ 56 map collect sort sort_by select find_all reject grep 57 compact flatten uniq values_at 58 + - & | 59 ] 60 61 DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq 62 63 # Now do the delegation. 64 DELEGATING_METHODS.each_with_index do |sym, i| 65 if SPECIAL_RETURN.include?(sym) 66 ln = __LINE__+1 67 class_eval %{ 68 def #{sym}(*args, &block) 69 resolve 70 result = @items.send(:#{sym}, *args, &block) 71 FileList.new.import(result) 72 end 73 }, __FILE__, ln 74 else 75 ln = __LINE__+1 76 class_eval %{ 77 def #{sym}(*args, &block) 78 resolve 79 result = @items.send(:#{sym}, *args, &block) 80 result.object_id == @items.object_id ? self : result 81 end 82 }, __FILE__, ln 83 end 84 end 85 86 # Create a file list from the globbable patterns given. If you wish to 87 # perform multiple includes or excludes at object build time, use the 88 # "yield self" pattern. 89 # 90 # Example: 91 # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb') 92 # 93 # pkg_files = FileList.new('lib/**/*') do |fl| 94 # fl.exclude(/\bCVS\b/) 95 # end 96 # 97 def initialize(*patterns) 98 @pending_add = [] 99 @pending = false 100 @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup 101 @exclude_procs = DEFAULT_IGNORE_PROCS.dup 102 @items = [] 103 patterns.each { |pattern| include(pattern) } 104 yield self if block_given? 105 end 106 107 # Add file names defined by glob patterns to the file list. If an array 108 # is given, add each element of the array. 109 # 110 # Example: 111 # file_list.include("*.java", "*.cfg") 112 # file_list.include %w( math.c lib.h *.o ) 113 # 114 def include(*filenames) 115 # TODO: check for pending 116 filenames.each do |fn| 117 if fn.respond_to? :to_ary 118 include(*fn.to_ary) 119 else 120 @pending_add << fn 121 end 122 end 123 @pending = true 124 self 125 end 126 alias :add :include 127 128 # Register a list of file name patterns that should be excluded from the 129 # list. Patterns may be regular expressions, glob patterns or regular 130 # strings. In addition, a block given to exclude will remove entries that 131 # return true when given to the block. 132 # 133 # Note that glob patterns are expanded against the file system. If a file 134 # is explicitly added to a file list, but does not exist in the file 135 # system, then an glob pattern in the exclude list will not exclude the 136 # file. 137 # 138 # Examples: 139 # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c'] 140 # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c'] 141 # 142 # If "a.c" is a file, then ... 143 # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c'] 144 # 145 # If "a.c" is not a file, then ... 146 # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c'] 147 # 148 def exclude(*patterns, &block) 149 patterns.each do |pat| 150 @exclude_patterns << pat 151 end 152 if block_given? 153 @exclude_procs << block 154 end 155 resolve_exclude if ! @pending 156 self 157 end 158 159 160 # Clear all the exclude patterns so that we exclude nothing. 161 def clear_exclude 162 @exclude_patterns = [] 163 @exclude_procs = [] 164 self 165 end 166 167 # Define equality. 168 def ==(array) 169 to_ary == array 170 end 171 172 # Return the internal array object. 173 def to_a 174 resolve 175 @items 176 end 177 178 # Return the internal array object. 179 def to_ary 180 to_a 181 end 182 183 # Lie about our class. 184 def is_a?(klass) 185 klass == Array || super(klass) 186 end 187 alias kind_of? is_a? 188 189 # Redefine * to return either a string or a new file list. 190 def *(other) 191 result = @items * other 192 case result 193 when Array 194 FileList.new.import(result) 195 else 196 result 197 end 198 end 199 200 # Resolve all the pending adds now. 201 def resolve 202 if @pending 203 @pending = false 204 @pending_add.each do |fn| resolve_add(fn) end 205 @pending_add = [] 206 resolve_exclude 207 end 208 self 209 end 210 211 def resolve_add(fn) 212 case fn 213 when %r{[*?\[\{]} 214 add_matching(fn) 215 else 216 self << fn 217 end 218 end 219 private :resolve_add 220 221 def resolve_exclude 222 reject! { |fn| exclude?(fn) } 223 self 224 end 225 private :resolve_exclude 226 227 # Return a new FileList with the results of running +sub+ against each 228 # element of the original list. 229 # 230 # Example: 231 # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] 232 # 233 def sub(pat, rep) 234 inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) } 235 end 236 237 # Return a new FileList with the results of running +gsub+ against each 238 # element of the original list. 239 # 240 # Example: 241 # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\") 242 # => ['lib\\test\\file', 'x\\y'] 243 # 244 def gsub(pat, rep) 245 inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) } 246 end 247 248 # Same as +sub+ except that the original file list is modified. 249 def sub!(pat, rep) 250 each_with_index { |fn, i| self[i] = fn.sub(pat,rep) } 251 self 252 end 253 254 # Same as +gsub+ except that the original file list is modified. 255 def gsub!(pat, rep) 256 each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) } 257 self 258 end 259 260 # Apply the pathmap spec to each of the included file names, returning a 261 # new file list with the modified paths. (See String#pathmap for 262 # details.) 263 def pathmap(spec=nil) 264 collect { |fn| fn.pathmap(spec) } 265 end 266 267 # Return a new FileList with <tt>String#ext</tt> method applied to 268 # each member of the array. 269 # 270 # This method is a shortcut for: 271 # 272 # array.collect { |item| item.ext(newext) } 273 # 274 # +ext+ is a user added method for the Array class. 275 def ext(newext='') 276 collect { |fn| fn.ext(newext) } 277 end 278 279 280 # Grep each of the files in the filelist using the given pattern. If a 281 # block is given, call the block on each matching line, passing the file 282 # name, line number, and the matching line of text. If no block is given, 283 # a standard emacs style file:linenumber:line message will be printed to 284 # standard out. Returns the number of matched items. 285 def egrep(pattern, *options) 286 matched = 0 287 each do |fn| 288 begin 289 open(fn, "r", *options) do |inf| 290 count = 0 291 inf.each do |line| 292 count += 1 293 if pattern.match(line) 294 matched += 1 295 if block_given? 296 yield fn, count, line 297 else 298 puts "#{fn}:#{count}:#{line}" 299 end 300 end 301 end 302 end 303 rescue StandardError => ex 304 $stderr.puts "Error while processing '#{fn}': #{ex}" 305 end 306 end 307 matched 308 end 309 310 # Return a new file list that only contains file names from the current 311 # file list that exist on the file system. 312 def existing 313 select { |fn| File.exist?(fn) } 314 end 315 316 # Modify the current file list so that it contains only file name that 317 # exist on the file system. 318 def existing! 319 resolve 320 @items = @items.select { |fn| File.exist?(fn) } 321 self 322 end 323 324 # FileList version of partition. Needed because the nested arrays should 325 # be FileLists in this version. 326 def partition(&block) # :nodoc: 327 resolve 328 result = @items.partition(&block) 329 [ 330 FileList.new.import(result[0]), 331 FileList.new.import(result[1]), 332 ] 333 end 334 335 # Convert a FileList to a string by joining all elements with a space. 336 def to_s 337 resolve 338 self.join(' ') 339 end 340 341 # Add matching glob patterns. 342 def add_matching(pattern) 343 FileList.glob(pattern).each do |fn| 344 self << fn unless exclude?(fn) 345 end 346 end 347 private :add_matching 348 349 # Should the given file name be excluded? 350 def exclude?(fn) 351 return true if @exclude_patterns.any? do |pat| 352 case pat 353 when Regexp 354 fn =~ pat 355 when /[*?]/ 356 File.fnmatch?(pat, fn, File::FNM_PATHNAME) 357 else 358 fn == pat 359 end 360 end 361 @exclude_procs.any? { |p| p.call(fn) } 362 end 363 364 DEFAULT_IGNORE_PATTERNS = [ 365 /(^|[\/\\])CVS([\/\\]|$)/, 366 /(^|[\/\\])\.svn([\/\\]|$)/, 367 /\.bak$/, 368 /~$/ 369 ] 370 DEFAULT_IGNORE_PROCS = [ 371 proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) } 372 ] 373 374 def import(array) 375 @items = array 376 self 377 end 378 379 class << self 380 # Create a new file list including the files listed. Similar to: 381 # 382 # FileList.new(*args) 383 def [](*args) 384 new(*args) 385 end 386 387 # Get a sorted list of files matching the pattern. This method 388 # should be prefered to Dir[pattern] and Dir.glob(pattern) because 389 # the files returned are guaranteed to be sorted. 390 def glob(pattern, *args) 391 Dir.glob(pattern, *args).sort 392 end 393 end 394 end 395end 396 397module Rake 398 class << self 399 400 # Yield each file or directory component. 401 def each_dir_parent(dir) # :nodoc: 402 old_length = nil 403 while dir != '.' && dir.length != old_length 404 yield(dir) 405 old_length = dir.length 406 dir = File.dirname(dir) 407 end 408 end 409 end 410end # module Rake 411