mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
d1d4490a57
* test/rake/*: ditto. * NEWS: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@38003 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
410 lines
12 KiB
Ruby
410 lines
12 KiB
Ruby
require 'rake/cloneable'
|
|
require 'rake/file_utils_ext'
|
|
require 'rake/pathmap'
|
|
|
|
######################################################################
|
|
module Rake
|
|
|
|
# #########################################################################
|
|
# A FileList is essentially an array with a few helper methods defined to
|
|
# make file manipulation a bit easier.
|
|
#
|
|
# FileLists are lazy. When given a list of glob patterns for possible files
|
|
# to be included in the file list, instead of searching the file structures
|
|
# to find the files, a FileList holds the pattern for latter use.
|
|
#
|
|
# This allows us to define a number of FileList to match any number of
|
|
# files, but only search out the actual files when then FileList itself is
|
|
# actually used. The key is that the first time an element of the
|
|
# FileList/Array is requested, the pending patterns are resolved into a real
|
|
# list of file names.
|
|
#
|
|
class FileList
|
|
|
|
include Cloneable
|
|
|
|
# == Method Delegation
|
|
#
|
|
# The lazy evaluation magic of FileLists happens by implementing all the
|
|
# array specific methods to call +resolve+ before delegating the heavy
|
|
# lifting to an embedded array object (@items).
|
|
#
|
|
# In addition, there are two kinds of delegation calls. The regular kind
|
|
# delegates to the @items array and returns the result directly. Well,
|
|
# almost directly. It checks if the returned value is the @items object
|
|
# itself, and if so will return the FileList object instead.
|
|
#
|
|
# The second kind of delegation call is used in methods that normally
|
|
# return a new Array object. We want to capture the return value of these
|
|
# methods and wrap them in a new FileList object. We enumerate these
|
|
# methods in the +SPECIAL_RETURN+ list below.
|
|
|
|
# List of array methods (that are not in +Object+) that need to be
|
|
# delegated.
|
|
ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
|
|
|
|
# List of additional methods that must be delegated.
|
|
MUST_DEFINE = %w[to_a inspect <=>]
|
|
|
|
# List of methods that should not be delegated here (we define special
|
|
# versions of them explicitly below).
|
|
MUST_NOT_DEFINE = %w[to_a to_ary partition *]
|
|
|
|
# List of delegated methods that return new array values which need
|
|
# wrapping.
|
|
SPECIAL_RETURN = %w[
|
|
map collect sort sort_by select find_all reject grep
|
|
compact flatten uniq values_at
|
|
+ - & |
|
|
]
|
|
|
|
DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq
|
|
|
|
# Now do the delegation.
|
|
DELEGATING_METHODS.each_with_index do |sym, i|
|
|
if SPECIAL_RETURN.include?(sym)
|
|
ln = __LINE__+1
|
|
class_eval %{
|
|
def #{sym}(*args, &block)
|
|
resolve
|
|
result = @items.send(:#{sym}, *args, &block)
|
|
FileList.new.import(result)
|
|
end
|
|
}, __FILE__, ln
|
|
else
|
|
ln = __LINE__+1
|
|
class_eval %{
|
|
def #{sym}(*args, &block)
|
|
resolve
|
|
result = @items.send(:#{sym}, *args, &block)
|
|
result.object_id == @items.object_id ? self : result
|
|
end
|
|
}, __FILE__, ln
|
|
end
|
|
end
|
|
|
|
# Create a file list from the globbable patterns given. If you wish to
|
|
# perform multiple includes or excludes at object build time, use the
|
|
# "yield self" pattern.
|
|
#
|
|
# Example:
|
|
# file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
|
|
#
|
|
# pkg_files = FileList.new('lib/**/*') do |fl|
|
|
# fl.exclude(/\bCVS\b/)
|
|
# end
|
|
#
|
|
def initialize(*patterns)
|
|
@pending_add = []
|
|
@pending = false
|
|
@exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
|
|
@exclude_procs = DEFAULT_IGNORE_PROCS.dup
|
|
@items = []
|
|
patterns.each { |pattern| include(pattern) }
|
|
yield self if block_given?
|
|
end
|
|
|
|
# Add file names defined by glob patterns to the file list. If an array
|
|
# is given, add each element of the array.
|
|
#
|
|
# Example:
|
|
# file_list.include("*.java", "*.cfg")
|
|
# file_list.include %w( math.c lib.h *.o )
|
|
#
|
|
def include(*filenames)
|
|
# TODO: check for pending
|
|
filenames.each do |fn|
|
|
if fn.respond_to? :to_ary
|
|
include(*fn.to_ary)
|
|
else
|
|
@pending_add << fn
|
|
end
|
|
end
|
|
@pending = true
|
|
self
|
|
end
|
|
alias :add :include
|
|
|
|
# Register a list of file name patterns that should be excluded from the
|
|
# list. Patterns may be regular expressions, glob patterns or regular
|
|
# strings. In addition, a block given to exclude will remove entries that
|
|
# return true when given to the block.
|
|
#
|
|
# Note that glob patterns are expanded against the file system. If a file
|
|
# is explicitly added to a file list, but does not exist in the file
|
|
# system, then an glob pattern in the exclude list will not exclude the
|
|
# file.
|
|
#
|
|
# Examples:
|
|
# FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
|
|
# FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
|
|
#
|
|
# If "a.c" is a file, then ...
|
|
# FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
|
|
#
|
|
# If "a.c" is not a file, then ...
|
|
# FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
|
|
#
|
|
def exclude(*patterns, &block)
|
|
patterns.each do |pat|
|
|
@exclude_patterns << pat
|
|
end
|
|
if block_given?
|
|
@exclude_procs << block
|
|
end
|
|
resolve_exclude if ! @pending
|
|
self
|
|
end
|
|
|
|
|
|
# Clear all the exclude patterns so that we exclude nothing.
|
|
def clear_exclude
|
|
@exclude_patterns = []
|
|
@exclude_procs = []
|
|
self
|
|
end
|
|
|
|
# Define equality.
|
|
def ==(array)
|
|
to_ary == array
|
|
end
|
|
|
|
# Return the internal array object.
|
|
def to_a
|
|
resolve
|
|
@items
|
|
end
|
|
|
|
# Return the internal array object.
|
|
def to_ary
|
|
to_a
|
|
end
|
|
|
|
# Lie about our class.
|
|
def is_a?(klass)
|
|
klass == Array || super(klass)
|
|
end
|
|
alias kind_of? is_a?
|
|
|
|
# Redefine * to return either a string or a new file list.
|
|
def *(other)
|
|
result = @items * other
|
|
case result
|
|
when Array
|
|
FileList.new.import(result)
|
|
else
|
|
result
|
|
end
|
|
end
|
|
|
|
# Resolve all the pending adds now.
|
|
def resolve
|
|
if @pending
|
|
@pending = false
|
|
@pending_add.each do |fn| resolve_add(fn) end
|
|
@pending_add = []
|
|
resolve_exclude
|
|
end
|
|
self
|
|
end
|
|
|
|
def resolve_add(fn)
|
|
case fn
|
|
when %r{[*?\[\{]}
|
|
add_matching(fn)
|
|
else
|
|
self << fn
|
|
end
|
|
end
|
|
private :resolve_add
|
|
|
|
def resolve_exclude
|
|
reject! { |fn| exclude?(fn) }
|
|
self
|
|
end
|
|
private :resolve_exclude
|
|
|
|
# Return a new FileList with the results of running +sub+ against each
|
|
# element of the original list.
|
|
#
|
|
# Example:
|
|
# FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
|
|
#
|
|
def sub(pat, rep)
|
|
inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
|
|
end
|
|
|
|
# Return a new FileList with the results of running +gsub+ against each
|
|
# element of the original list.
|
|
#
|
|
# Example:
|
|
# FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
|
|
# => ['lib\\test\\file', 'x\\y']
|
|
#
|
|
def gsub(pat, rep)
|
|
inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
|
|
end
|
|
|
|
# Same as +sub+ except that the original file list is modified.
|
|
def sub!(pat, rep)
|
|
each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
|
|
self
|
|
end
|
|
|
|
# Same as +gsub+ except that the original file list is modified.
|
|
def gsub!(pat, rep)
|
|
each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
|
|
self
|
|
end
|
|
|
|
# Apply the pathmap spec to each of the included file names, returning a
|
|
# new file list with the modified paths. (See String#pathmap for
|
|
# details.)
|
|
def pathmap(spec=nil)
|
|
collect { |fn| fn.pathmap(spec) }
|
|
end
|
|
|
|
# Return a new FileList with <tt>String#ext</tt> method applied to
|
|
# each member of the array.
|
|
#
|
|
# This method is a shortcut for:
|
|
#
|
|
# array.collect { |item| item.ext(newext) }
|
|
#
|
|
# +ext+ is a user added method for the Array class.
|
|
def ext(newext='')
|
|
collect { |fn| fn.ext(newext) }
|
|
end
|
|
|
|
|
|
# Grep each of the files in the filelist using the given pattern. If a
|
|
# block is given, call the block on each matching line, passing the file
|
|
# name, line number, and the matching line of text. If no block is given,
|
|
# a standard emacs style file:linenumber:line message will be printed to
|
|
# standard out. Returns the number of matched items.
|
|
def egrep(pattern, *options)
|
|
matched = 0
|
|
each do |fn|
|
|
begin
|
|
open(fn, "r", *options) do |inf|
|
|
count = 0
|
|
inf.each do |line|
|
|
count += 1
|
|
if pattern.match(line)
|
|
matched += 1
|
|
if block_given?
|
|
yield fn, count, line
|
|
else
|
|
puts "#{fn}:#{count}:#{line}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
rescue StandardError => ex
|
|
$stderr.puts "Error while processing '#{fn}': #{ex}"
|
|
end
|
|
end
|
|
matched
|
|
end
|
|
|
|
# Return a new file list that only contains file names from the current
|
|
# file list that exist on the file system.
|
|
def existing
|
|
select { |fn| File.exist?(fn) }
|
|
end
|
|
|
|
# Modify the current file list so that it contains only file name that
|
|
# exist on the file system.
|
|
def existing!
|
|
resolve
|
|
@items = @items.select { |fn| File.exist?(fn) }
|
|
self
|
|
end
|
|
|
|
# FileList version of partition. Needed because the nested arrays should
|
|
# be FileLists in this version.
|
|
def partition(&block) # :nodoc:
|
|
resolve
|
|
result = @items.partition(&block)
|
|
[
|
|
FileList.new.import(result[0]),
|
|
FileList.new.import(result[1]),
|
|
]
|
|
end
|
|
|
|
# Convert a FileList to a string by joining all elements with a space.
|
|
def to_s
|
|
resolve
|
|
self.join(' ')
|
|
end
|
|
|
|
# Add matching glob patterns.
|
|
def add_matching(pattern)
|
|
FileList.glob(pattern).each do |fn|
|
|
self << fn unless exclude?(fn)
|
|
end
|
|
end
|
|
private :add_matching
|
|
|
|
# Should the given file name be excluded?
|
|
def exclude?(fn)
|
|
return true if @exclude_patterns.any? do |pat|
|
|
case pat
|
|
when Regexp
|
|
fn =~ pat
|
|
when /[*?]/
|
|
File.fnmatch?(pat, fn, File::FNM_PATHNAME)
|
|
else
|
|
fn == pat
|
|
end
|
|
end
|
|
@exclude_procs.any? { |p| p.call(fn) }
|
|
end
|
|
|
|
DEFAULT_IGNORE_PATTERNS = [
|
|
/(^|[\/\\])CVS([\/\\]|$)/,
|
|
/(^|[\/\\])\.svn([\/\\]|$)/,
|
|
/\.bak$/,
|
|
/~$/
|
|
]
|
|
DEFAULT_IGNORE_PROCS = [
|
|
proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
|
|
]
|
|
|
|
def import(array)
|
|
@items = array
|
|
self
|
|
end
|
|
|
|
class << self
|
|
# Create a new file list including the files listed. Similar to:
|
|
#
|
|
# FileList.new(*args)
|
|
def [](*args)
|
|
new(*args)
|
|
end
|
|
|
|
# Get a sorted list of files matching the pattern. This method
|
|
# should be prefered to Dir[pattern] and Dir.glob(pattern) because
|
|
# the files returned are guaranteed to be sorted.
|
|
def glob(pattern, *args)
|
|
Dir.glob(pattern, *args).sort
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module Rake
|
|
class << self
|
|
|
|
# Yield each file or directory component.
|
|
def each_dir_parent(dir) # :nodoc:
|
|
old_length = nil
|
|
while dir != '.' && dir.length != old_length
|
|
yield(dir)
|
|
old_length = dir.length
|
|
dir = File.dirname(dir)
|
|
end
|
|
end
|
|
end
|
|
end # module Rake
|