diff --git a/lib/rake.rb b/lib/rake.rb index 7e0a20953..961e64c5b 100755 --- a/lib/rake.rb +++ b/lib/rake.rb @@ -39,6 +39,11 @@ require 'ostruct' require 'rake/win32' +require 'rake/file_list' + + +# Alias FileList to be available at the top level. +FileList = Rake::FileList $trace = false @@ -283,29 +288,6 @@ def original_dir end #################################################################### - # Mixin for creating easily cloned objects. - # - module Cloneable - # Clone an object by making a new object and setting all the instance - # variables to the same values. - def dup - sibling = self.class.new - instance_variables.each do |ivar| - value = self.instance_variable_get(ivar) - new_value = value.clone rescue value - sibling.instance_variable_set(ivar, new_value) - end - sibling.taint if tainted? - sibling - end - - def clone - sibling = dup - sibling.freeze if frozen? - sibling - end - end - #################################################################### # Exit status class for times the system just gives us a nil. class PseudoStatus @@ -952,666 +934,7 @@ def import(*fns) end end -############################################################################# -# This a FileUtils extension that defines several additional commands to be -# added to the FileUtils utility functions. -# -module FileUtils - RUBY_EXT = ((Config::CONFIG['ruby_install_name'] =~ /\.(com|cmd|exe|bat|rb|sh)$/) ? - "" : - Config::CONFIG['EXEEXT']) - - RUBY = File.join( - Config::CONFIG['bindir'], - Config::CONFIG['ruby_install_name'] + RUBY_EXT). - sub(/.*\s.*/m, '"\&"') - - OPT_TABLE['sh'] = %w(noop verbose) - OPT_TABLE['ruby'] = %w(noop verbose) - - # Run the system command +cmd+. If multiple arguments are given the command - # is not run with the shell (same semantics as Kernel::exec and - # Kernel::system). - # - # Example: - # sh %{ls -ltr} - # - # sh 'ls', 'file with spaces' - # - # # check exit status after command runs - # sh %{grep pattern file} do |ok, res| - # if ! ok - # puts "pattern not found (status = #{res.exitstatus})" - # end - # end - # - def sh(*cmd, &block) - options = (Hash === cmd.last) ? cmd.pop : {} - unless block_given? - show_command = cmd.join(" ") - show_command = show_command[0,42] + "..." unless $trace - # TODO code application logic heref show_command.length > 45 - block = lambda { |ok, status| - ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]" - } - end - if RakeFileUtils.verbose_flag == :default - options[:verbose] = true - else - options[:verbose] ||= RakeFileUtils.verbose_flag - end - options[:noop] ||= RakeFileUtils.nowrite_flag - rake_check_options options, :noop, :verbose - rake_output_message cmd.join(" ") if options[:verbose] - unless options[:noop] - res = rake_system(*cmd) - status = $? - status = PseudoStatus.new(1) if !res && status.nil? - block.call(res, status) - end - end - - def rake_system(*cmd) - Rake::AltSystem.system(*cmd) - end - private :rake_system - - # Run a Ruby interpreter with the given arguments. - # - # Example: - # ruby %{-pe '$_.upcase!' 1 then - sh(*([RUBY] + args + [options]), &block) - else - sh("#{RUBY} #{args.first}", options, &block) - end - end - - LN_SUPPORTED = [true] - - # Attempt to do a normal file link, but fall back to a copy if the link - # fails. - def safe_ln(*args) - unless LN_SUPPORTED[0] - cp(*args) - else - begin - ln(*args) - rescue StandardError, NotImplementedError => ex - LN_SUPPORTED[0] = false - cp(*args) - end - end - end - - # Split a file path into individual directory names. - # - # Example: - # split_all("a/b/c") => ['a', 'b', 'c'] - # - def split_all(path) - head, tail = File.split(path) - return [tail] if head == '.' || tail == '/' - return [head, tail] if head == '/' - return split_all(head) + [tail] - end -end - -############################################################################# -# RakeFileUtils provides a custom version of the FileUtils methods that -# respond to the verbose and nowrite commands. -# -module RakeFileUtils - include FileUtils - - class << self - attr_accessor :verbose_flag, :nowrite_flag - end - RakeFileUtils.verbose_flag = :default - RakeFileUtils.nowrite_flag = false - - $fileutils_verbose = true - $fileutils_nowrite = false - - FileUtils::OPT_TABLE.each do |name, opts| - default_options = [] - if opts.include?(:verbose) || opts.include?("verbose") - default_options << ':verbose => RakeFileUtils.verbose_flag' - end - if opts.include?(:noop) || opts.include?("noop") - default_options << ':noop => RakeFileUtils.nowrite_flag' - end - - next if default_options.empty? - module_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{name}( *args, &block ) - super( - *rake_merge_option(args, - #{default_options.join(', ')} - ), &block) - end - EOS - end - - # Get/set the verbose flag controlling output from the FileUtils utilities. - # If verbose is true, then the utility method is echoed to standard output. - # - # Examples: - # verbose # return the current value of the verbose flag - # verbose(v) # set the verbose flag to _v_. - # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_. - # # Return to the original value when code is done. - def verbose(value=nil) - oldvalue = RakeFileUtils.verbose_flag - RakeFileUtils.verbose_flag = value unless value.nil? - if block_given? - begin - yield - ensure - RakeFileUtils.verbose_flag = oldvalue - end - end - RakeFileUtils.verbose_flag - end - - # Get/set the nowrite flag controlling output from the FileUtils utilities. - # If verbose is true, then the utility method is echoed to standard output. - # - # Examples: - # nowrite # return the current value of the nowrite flag - # nowrite(v) # set the nowrite flag to _v_. - # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_. - # # Return to the original value when code is done. - def nowrite(value=nil) - oldvalue = RakeFileUtils.nowrite_flag - RakeFileUtils.nowrite_flag = value unless value.nil? - if block_given? - begin - yield - ensure - RakeFileUtils.nowrite_flag = oldvalue - end - end - oldvalue - end - - # Use this function to prevent protentially destructive ruby code from - # running when the :nowrite flag is set. - # - # Example: - # - # when_writing("Building Project") do - # project.build - # end - # - # The following code will build the project under normal conditions. If the - # nowrite(true) flag is set, then the example will print: - # DRYRUN: Building Project - # instead of actually building the project. - # - def when_writing(msg=nil) - if RakeFileUtils.nowrite_flag - puts "DRYRUN: #{msg}" if msg - else - yield - end - end - - # Merge the given options with the default values. - def rake_merge_option(args, defaults) - if Hash === args.last - defaults.update(args.last) - args.pop - end - args.push defaults - args - end - private :rake_merge_option - - # Send the message to the default rake output (which is $stderr). - def rake_output_message(message) - $stderr.puts(message) - end - private :rake_output_message - - # Check that the options do not contain options not listed in +optdecl+. An - # ArgumentError exception is thrown if non-declared options are found. - def rake_check_options(options, *optdecl) - h = options.dup - optdecl.each do |name| - h.delete name - end - raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? - end - private :rake_check_options - - extend self -end - -############################################################################# -# Include the FileUtils file manipulation functions in the top level module, -# but mark them private so that they don't unintentionally define methods on -# other objects. - -include RakeFileUtils -private(*FileUtils.instance_methods(false)) -private(*RakeFileUtils.instance_methods(false)) - -###################################################################### -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 - @exclude_re = nil - @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 = [] - calculate_exclude_regexp if ! @pending - 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 calculate_exclude_regexp - ignores = [] - @exclude_patterns.each do |pat| - case pat - when Regexp - ignores << pat - when /[*?]/ - Dir[pat].each do |p| ignores << p end - else - ignores << Regexp.quote(pat) - end - end - if ignores.empty? - @exclude_re = /^$/ - else - re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|") - @exclude_re = Regexp.new(re_str) - end - end - - def resolve_add(fn) - case fn - when %r{[*?\[\{]} - add_matching(fn) - else - self << fn - end - end - private :resolve_add - - def resolve_exclude - calculate_exclude_regexp - reject! { |fn| exclude?(fn) } - self - end - private :resolve_exclude - - # Return a new FileList with the results of running +sub+ against each - # element of the oringal 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 oringal 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 String#ext 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 emac style file:linenumber:line message will be printed to - # standard out. - def egrep(pattern, *options) - each do |fn| - open(fn, "rb", *options) do |inf| - count = 0 - inf.each do |line| - count += 1 - if pattern.match(line) - if block_given? - yield fn, count, line - else - puts "#{fn}:#{count}:#{line}" - end - end - end - end - end - 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) - Dir[pattern].each do |fn| - self << fn unless exclude?(fn) - end - end - private :add_matching - - # Should the given file name be excluded? - def exclude?(fn) - calculate_exclude_regexp unless @exclude_re - fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) } - end - - DEFAULT_IGNORE_PATTERNS = [ - /(^|[\/\\])CVS([\/\\]|$)/, - /(^|[\/\\])\.svn([\/\\]|$)/, - /\.bak$/, - /~$/ - ] - DEFAULT_IGNORE_PROCS = [ - proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) } - ] -# @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup - - 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 - end - end # FileList -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 - -# Alias FileList to be available at the top level. -FileList = Rake::FileList - -############################################################################# +# ########################################################################### module Rake # Default Rakefile loader used by +import+. diff --git a/lib/rake/cloneable.rb b/lib/rake/cloneable.rb new file mode 100644 index 000000000..19c780bff --- /dev/null +++ b/lib/rake/cloneable.rb @@ -0,0 +1,25 @@ +module Rake + # ########################################################################## + # Mixin for creating easily cloned objects. + # + module Cloneable + # Clone an object by making a new object and setting all the instance + # variables to the same values. + def dup + sibling = self.class.new + instance_variables.each do |ivar| + value = self.instance_variable_get(ivar) + new_value = value.clone rescue value + sibling.instance_variable_set(ivar, new_value) + end + sibling.taint if tainted? + sibling + end + + def clone + sibling = dup + sibling.freeze if frozen? + sibling + end + end +end diff --git a/lib/rake/file_list.rb b/lib/rake/file_list.rb new file mode 100644 index 000000000..51dfc1c84 --- /dev/null +++ b/lib/rake/file_list.rb @@ -0,0 +1,410 @@ +require 'rake/cloneable' +require 'rake/rake_file_utils' + +###################################################################### +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 + @exclude_re = nil + @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 = [] + calculate_exclude_regexp if ! @pending + 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 calculate_exclude_regexp + ignores = [] + @exclude_patterns.each do |pat| + case pat + when Regexp + ignores << pat + when /[*?]/ + Dir[pat].each do |p| ignores << p end + else + ignores << Regexp.quote(pat) + end + end + if ignores.empty? + @exclude_re = /^$/ + else + re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|") + @exclude_re = Regexp.new(re_str) + end + end + + def resolve_add(fn) + case fn + when %r{[*?\[\{]} + add_matching(fn) + else + self << fn + end + end + private :resolve_add + + def resolve_exclude + calculate_exclude_regexp + reject! { |fn| exclude?(fn) } + self + end + private :resolve_exclude + + # Return a new FileList with the results of running +sub+ against each + # element of the oringal 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 oringal 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 String#ext 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 emac style file:linenumber:line message will be printed to + # standard out. + def egrep(pattern, *options) + each do |fn| + open(fn, "rb", *options) do |inf| + count = 0 + inf.each do |line| + count += 1 + if pattern.match(line) + if block_given? + yield fn, count, line + else + puts "#{fn}:#{count}:#{line}" + end + end + end + end + end + 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) + Dir[pattern].each do |fn| + self << fn unless exclude?(fn) + end + end + private :add_matching + + # Should the given file name be excluded? + def exclude?(fn) + calculate_exclude_regexp unless @exclude_re + fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) } + end + + DEFAULT_IGNORE_PATTERNS = [ + /(^|[\/\\])CVS([\/\\]|$)/, + /(^|[\/\\])\.svn([\/\\]|$)/, + /\.bak$/, + /~$/ + ] + DEFAULT_IGNORE_PROCS = [ + proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) } + ] +# @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup + + 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 + end + end # FileList +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 diff --git a/lib/rake/file_utils.rb b/lib/rake/file_utils.rb new file mode 100644 index 000000000..4454f7806 --- /dev/null +++ b/lib/rake/file_utils.rb @@ -0,0 +1,103 @@ +# ########################################################################### +# This a FileUtils extension that defines several additional commands to be +# added to the FileUtils utility functions. +# +module FileUtils + RUBY = File.join( + Config::CONFIG['bindir'], + Config::CONFIG['ruby_install_name'] + Config::CONFIG['EXEEXT']). + sub(/.*\s.*/m, '"\&"') + + OPT_TABLE['sh'] = %w(noop verbose) + OPT_TABLE['ruby'] = %w(noop verbose) + + # Run the system command +cmd+. If multiple arguments are given the command + # is not run with the shell (same semantics as Kernel::exec and + # Kernel::system). + # + # Example: + # sh %{ls -ltr} + # + # sh 'ls', 'file with spaces' + # + # # check exit status after command runs + # sh %{grep pattern file} do |ok, res| + # if ! ok + # puts "pattern not found (status = #{res.exitstatus})" + # end + # end + # + def sh(*cmd, &block) + options = (Hash === cmd.last) ? cmd.pop : {} + unless block_given? + show_command = cmd.join(" ") + show_command = show_command[0,42] + "..." unless $trace + # TODO code application logic heref show_command.length > 45 + block = lambda { |ok, status| + ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]" + } + end + if RakeFileUtils.verbose_flag == :default + options[:verbose] = true + else + options[:verbose] ||= RakeFileUtils.verbose_flag + end + options[:noop] ||= RakeFileUtils.nowrite_flag + rake_check_options options, :noop, :verbose + rake_output_message cmd.join(" ") if options[:verbose] + unless options[:noop] + res = rake_system(*cmd) + status = $? + status = PseudoStatus.new(1) if !res && status.nil? + block.call(res, status) + end + end + + def rake_system(*cmd) + Rake::AltSystem.system(*cmd) + end + private :rake_system + + # Run a Ruby interpreter with the given arguments. + # + # Example: + # ruby %{-pe '$_.upcase!' 1 then + sh(*([RUBY] + args + [options]), &block) + else + sh("#{RUBY} #{args.first}", options, &block) + end + end + + LN_SUPPORTED = [true] + + # Attempt to do a normal file link, but fall back to a copy if the link + # fails. + def safe_ln(*args) + unless LN_SUPPORTED[0] + cp(*args) + else + begin + ln(*args) + rescue StandardError, NotImplementedError => ex + LN_SUPPORTED[0] = false + cp(*args) + end + end + end + + # Split a file path into individual directory names. + # + # Example: + # split_all("a/b/c") => ['a', 'b', 'c'] + # + def split_all(path) + head, tail = File.split(path) + return [tail] if head == '.' || tail == '/' + return [head, tail] if head == '/' + return split_all(head) + [tail] + end +end diff --git a/lib/rake/rake_file_utils.rb b/lib/rake/rake_file_utils.rb new file mode 100644 index 000000000..7ddd5b33b --- /dev/null +++ b/lib/rake/rake_file_utils.rb @@ -0,0 +1,142 @@ +require 'rake/file_utils' + +# ########################################################################### +# RakeFileUtils provides a custom version of the FileUtils methods that +# respond to the verbose and nowrite commands. +# +module RakeFileUtils + include FileUtils + + class << self + attr_accessor :verbose_flag, :nowrite_flag + end + RakeFileUtils.verbose_flag = :default + RakeFileUtils.nowrite_flag = false + + $fileutils_verbose = true + $fileutils_nowrite = false + + FileUtils::OPT_TABLE.each do |name, opts| + default_options = [] + if opts.include?(:verbose) || opts.include?("verbose") + default_options << ':verbose => RakeFileUtils.verbose_flag' + end + if opts.include?(:noop) || opts.include?("noop") + default_options << ':noop => RakeFileUtils.nowrite_flag' + end + + next if default_options.empty? + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}( *args, &block ) + super( + *rake_merge_option(args, + #{default_options.join(', ')} + ), &block) + end + EOS + end + + # Get/set the verbose flag controlling output from the FileUtils utilities. + # If verbose is true, then the utility method is echoed to standard output. + # + # Examples: + # verbose # return the current value of the verbose flag + # verbose(v) # set the verbose flag to _v_. + # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_. + # # Return to the original value when code is done. + def verbose(value=nil) + oldvalue = RakeFileUtils.verbose_flag + RakeFileUtils.verbose_flag = value unless value.nil? + if block_given? + begin + yield + ensure + RakeFileUtils.verbose_flag = oldvalue + end + end + RakeFileUtils.verbose_flag + end + + # Get/set the nowrite flag controlling output from the FileUtils utilities. + # If verbose is true, then the utility method is echoed to standard output. + # + # Examples: + # nowrite # return the current value of the nowrite flag + # nowrite(v) # set the nowrite flag to _v_. + # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_. + # # Return to the original value when code is done. + def nowrite(value=nil) + oldvalue = RakeFileUtils.nowrite_flag + RakeFileUtils.nowrite_flag = value unless value.nil? + if block_given? + begin + yield + ensure + RakeFileUtils.nowrite_flag = oldvalue + end + end + oldvalue + end + + # Use this function to prevent protentially destructive ruby code from + # running when the :nowrite flag is set. + # + # Example: + # + # when_writing("Building Project") do + # project.build + # end + # + # The following code will build the project under normal conditions. If the + # nowrite(true) flag is set, then the example will print: + # DRYRUN: Building Project + # instead of actually building the project. + # + def when_writing(msg=nil) + if RakeFileUtils.nowrite_flag + puts "DRYRUN: #{msg}" if msg + else + yield + end + end + + # Merge the given options with the default values. + def rake_merge_option(args, defaults) + if Hash === args.last + defaults.update(args.last) + args.pop + end + args.push defaults + args + end + private :rake_merge_option + + # Send the message to the default rake output (which is $stderr). + def rake_output_message(message) + $stderr.puts(message) + end + private :rake_output_message + + # Check that the options do not contain options not listed in +optdecl+. An + # ArgumentError exception is thrown if non-declared options are found. + def rake_check_options(options, *optdecl) + h = options.dup + optdecl.each do |name| + h.delete name + end + raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? + end + private :rake_check_options + + extend self +end + +# ########################################################################### +# Include the FileUtils file manipulation functions in the top level module, +# but mark them private so that they don't unintentionally define methods on +# other objects. + +include RakeFileUtils +private(*FileUtils.instance_methods(false)) +private(*RakeFileUtils.instance_methods(false)) + diff --git a/test/test_fileutils.rb b/test/test_fileutils.rb index 20d965079..083b2cc5d 100644 --- a/test/test_fileutils.rb +++ b/test/test_fileutils.rb @@ -117,7 +117,7 @@ def test_file_utils_methods_are_available_at_top_level def test_fileutils_methods_dont_leak obj = Object.new assert_exception(NoMethodError) { obj.copy } # from FileUtils - assert_exception(NoMethodError) { obj.ruby } # from RubyFileUtils + assert_exception(NoMethodError) { obj.ruby "-v" } # from RubyFileUtils end def test_sh