PageRenderTime 276ms CodeModel.GetById 120ms app.highlight 81ms RepoModel.GetById 70ms app.codeStats 1ms

/vendor/gems/facets-2.4.5/lib/more/facets/filelist.rb

https://bitbucket.org/mediashelf/fedora-migrator
Ruby | 497 lines | 226 code | 45 blank | 226 comment | 15 complexity | 0a2104d81b44a8f581e066333da5277c MD5 | raw file
  1# = FileList
  2#
  3# A FileList is essentially an array with helper methods
  4# to make file manipulation easier.
  5#
  6# FileLists are lazy.  When given a list of glob patterns for
  7# possible files to be included in the file list, instead of
  8# searching the file structures to find the files, a FileList holds
  9# the pattern for latter use.
 10#
 11# This allows us to define a number of FileList to match any number of
 12# files, but only search out the actual files when then FileList
 13# itself is actually used.  The key is that the first time an
 14# element of the FileList/Array is requested, the pending patterns
 15# are resolved into a real list of file names.
 16#
 17#   fl = FileList.new
 18#   fl.include('./**/*')
 19#   fl.exclude('./*~')
 20#
 21# == History
 22#
 23#   FileList was ported from Jim Weirich's Rake.
 24#
 25# == Authors
 26#
 27# * Jim Weirich
 28#
 29# == Todo
 30#
 31# * Should the exclusions really be the default?
 32#   Maybe have #exclude_typical instead.
 33#
 34# == Copying
 35#
 36# Copyright (C) 2002 Jim Weirich
 37#
 38# General Public License (GPL)
 39#
 40# Permission is hereby granted, free of charge, to any person obtaining
 41# a copy of this software and associated documentation files (the
 42# "Software"), to deal in the Software without restriction, including
 43# without limitation the rights to use, copy, modify, merge, publish,
 44# distribute, sublicense, and/or sell copies of the Software, and to
 45# permit persons to whom the Software is furnished to do so, subject to
 46# the following conditions:
 47#
 48# The above copyright notice and this permission notice shall be
 49# included in all copies or substantial portions of the Software.
 50#
 51# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 52# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 53# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 54# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 55# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 56# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 57# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 58
 59# = FileList
 60#
 61# A FileList is essentially an array with helper methods
 62# to make file manipulation easier.
 63#
 64# FileLists are lazy.  When given a list of glob patterns for
 65# possible files to be included in the file list, instead of
 66# searching the file structures to find the files, a FileList holds
 67# the pattern for latter use.
 68#
 69# This allows us to define a number of FileList to match any number of
 70# files, but only search out the actual files when then FileList
 71# itself is actually used.  The key is that the first time an
 72# element of the FileList/Array is requested, the pending patterns
 73# are resolved into a real list of file names.
 74#
 75#   fl = FileList.new
 76#   fl.include('./**/*')
 77#   fl.exclude('./*~')
 78#
 79class FileList
 80
 81  # TODO: Add glob options.
 82  #attr :glob_options
 83
 84  #include Cloneable
 85  def clone
 86    sibling = self.class.new
 87    instance_variables.each do |ivar|
 88      value = self.instance_variable_get(ivar)
 89      sibling.instance_variable_set(ivar, value.rake_dup)
 90    end
 91    sibling
 92  end
 93  alias_method :dup, :clone
 94
 95  # == Method Delegation
 96  #
 97  # The lazy evaluation magic of FileLists happens by implementing
 98  # all the array specific methods to call +resolve+ before
 99  # delegating the heavy lifting to an embedded array object
100  # (@items).
101  #
102  # In addition, there are two kinds of delegation calls.  The
103  # regular kind delegates to the @items array and returns the
104  # result directly.  Well, almost directly.  It checks if the
105  # returned value is the @items object itself, and if so will
106  # return the FileList object instead.
107  #
108  # The second kind of delegation call is used in methods that
109  # normally return a new Array object.  We want to capture the
110  # return value of these methods and wrap them in a new FileList
111  # object.  We enumerate these methods in the +SPECIAL_RETURN+ list
112  # below.
113
114  # List of array methods (that are not in +Object+) that need to be
115  # delegated.
116  ARRAY_METHODS = Array.instance_methods - Object.instance_methods
117
118  # List of additional methods that must be delegated.
119  MUST_DEFINE = %w[to_a inspect]
120
121  # List of methods that should not be delegated here (we define
122  # special versions of them explicitly below).
123  MUST_NOT_DEFINE = %w[to_a to_ary partition *]
124
125  # List of delegated methods that return new array values which
126  # need wrapping.
127  SPECIAL_RETURN = %w[
128    map collect sort sort_by select find_all reject grep
129    compact flatten uniq values_at
130    + - & |
131  ]
132
133  DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).sort.uniq
134
135  # Now do the delegation.
136  DELEGATING_METHODS.each_with_index do |sym, i|
137    if SPECIAL_RETURN.include?(sym)
138      ln = __LINE__+1
139      class_eval %{
140        def #{sym}(*args, &block)
141          resolve if @pending
142          result = @items.send(:#{sym}, *args, &block)
143          FileList.new.import(result)
144        end
145      }, __FILE__, ln
146    else
147      ln = __LINE__+1
148      class_eval %{
149        def #{sym}(*args, &block)
150          resolve if @pending
151          result = @items.send(:#{sym}, *args, &block)
152          result.object_id == @items.object_id ? self : result
153        end
154      }, __FILE__, ln
155    end
156  end
157
158  # Create a file list from the globbable patterns given.  If you
159  # wish to perform multiple includes or excludes at object build
160  # time, use the "yield self" pattern.
161  #
162  # Example:
163  #   file_list = FileList.new['lib/**/*.rb', 'test/test*.rb']
164  #
165  #   pkg_files = FileList.new['lib/**/*'] do |fl|
166  #     fl.exclude(/\bCVS\b/)
167  #   end
168  #
169  def initialize(*patterns)
170    @pending_add = []
171    @pending = false
172    @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
173    @exclude_re = nil
174    @items = []
175    patterns.each { |pattern| include(pattern) }
176    yield self if block_given?
177  end
178
179  # Add file names defined by glob patterns to the file list.  If an
180  # array is given, add each element of the array.
181  #
182  # Example:
183  #   file_list.include("*.java", "*.cfg")
184  #   file_list.include %w( math.c lib.h *.o )
185  #
186  def include(*filenames)
187    # TODO: check for pending
188    filenames.each do |fn|
189      if fn.respond_to? :to_ary
190        include(*fn.to_ary)
191      else
192        @pending_add << fn
193      end
194    end
195    @pending = true
196    self
197  end
198  alias :add :include
199
200  # Register a list of file name patterns that should be excluded
201  # from the list.  Patterns may be regular expressions, glob
202  # patterns or regular strings.
203  #
204  # Note that glob patterns are expanded against the file system.
205  # If a file is explicitly added to a file list, but does not exist
206  # in the file system, then an glob pattern in the exclude list
207  # will not exclude the file.
208  #
209  # Examples:
210  #   FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
211  #   FileList['a.c', 'b.c'].exclude(/^a/)  => ['b.c']
212  #
213  # If "a.c" is a file, then ...
214  #   FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
215  #
216  # If "a.c" is not a file, then ...
217  #   FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
218  #
219  def exclude(*patterns)
220    patterns.each do |pat| @exclude_patterns << pat end
221    if ! @pending
222      calculate_exclude_regexp
223      reject! { |fn| fn =~ @exclude_re }
224    end
225    self
226  end
227
228  # Clear all the exclude patterns so that we exclude nothing.
229  def clear_exclude
230    @exclude_patterns = []
231    calculate_exclude_regexp if ! @pending
232  end
233
234  # Define equality.
235  def ==(array)
236    to_ary == array
237  end
238
239  # Return the internal array object.
240  def to_a
241    resolve
242    @items
243  end
244
245  # Return the internal array object.
246  def to_ary
247    resolve
248    @items
249  end
250
251  # Redefine * to return either a string or a new file list.
252  def *(other)
253    result = @items * other
254    case result
255    when Array
256      FileList.new.import(result)
257    else
258      result
259    end
260  end
261
262  # Resolve all the pending adds now.
263  def resolve
264    if @pending
265      @pending = false
266      @pending_add.each do |fn| resolve_add(fn) end
267      @pending_add = []
268      resolve_exclude
269    end
270    self
271  end
272
273  def calculate_exclude_regexp
274    ignores = []
275    @exclude_patterns.each do |pat|
276      case pat
277      when Regexp
278        ignores << pat
279      when /[*.]/
280        Dir[pat].each do |p| ignores << p end
281      else
282        ignores << Regexp.quote(pat)
283      end
284    end
285    if ignores.empty?
286      @exclude_re = /^$/
287    else
288      re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
289      @exclude_re = Regexp.new(re_str)
290    end
291  end
292
293  def resolve_add(fn)
294    case fn
295    when Array
296      fn.each { |f| self.resolve_add(f) }
297    when %r{[*?]}
298      add_matching(fn)
299    else
300      self << fn
301    end
302  end
303
304  def resolve_exclude
305    @exclude_patterns.each do |pat|
306      case pat
307      when Regexp
308        reject! { |fn| fn =~ pat }
309      when /[*.]/
310        reject_list = Dir[pat]
311        reject! { |fn| reject_list.include?(fn) }
312      else
313        reject! { |fn| fn == pat }
314      end
315    end
316    self
317  end
318
319  # Return a new FileList with the results of running +sub+ against
320  # each element of the oringal list.
321  #
322  # Example:
323  #   FileList['a.c', 'b.c'].sub(/\.c$/, '.o')  => ['a.o', 'b.o']
324  #
325  def sub(pat, rep)
326    inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
327  end
328
329  # Return a new FileList with the results of running +gsub+ against
330  # each element of the original list.
331  #
332  # Example:
333  #   FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
334  #      => ['lib\\test\\file', 'x\\y']
335  #
336  def gsub(pat, rep)
337    inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
338  end
339
340  # Same as +sub+ except that the oringal file list is modified.
341  def sub!(pat, rep)
342    each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
343    self
344  end
345
346  # Same as +gsub+ except that the original file list is modified.
347  def gsub!(pat, rep)
348    each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
349    self
350  end
351
352  # Return a new array with <tt>String#ext</tt> method applied to
353  # each member of the array.
354  #
355  # This method is a shortcut for:
356  #
357  #    array.collect { |item| item.ext(newext) }
358  #
359  # +ext+ is a user added method for the Array class.
360  def ext(newext='')
361    collect { |fn| fn.ext(newext) }
362  end
363
364  # Grep each of the files in the filelist using the given pattern.
365  # If a block is given, call the block on each matching line,
366  # passing the file name, line number, and the matching line of
367  # text.  If no block is given, a standard emac style
368  # file:linenumber:line message will be printed to standard out.
369  def egrep(pattern)
370    each do |fn|
371      open(fn) do |inf|
372        count = 0
373
374        inf.each do |line|
375          count += 1
376          if pattern.match(line)
377            if block_given?
378              yield fn, count, line
379            else
380              puts "#{fn}:#{count}:#{line}"
381            end
382          end
383        end
384
385      end
386    end
387  end
388
389  # FileList version of partition.  Needed because the nested arrays
390  # should be FileLists in this version.
391  def partition(&block) # :nodoc:
392    resolve
393    result = @items.partition(&block)
394    [
395      FileList.new.import(result[0]),
396      FileList.new.import(result[1]),
397    ]
398  end
399
400  # Convert a FileList to a string by joining all elements with a space.
401  def to_s
402    resolve if @pending
403    self.join(' ')
404  end
405
406  # Add matching glob patterns.
407  def add_matching(pattern)
408    Dir[pattern].each do |fn|
409      self << fn unless exclude?(fn)
410    end
411  end
412  private :add_matching
413
414  # Should the given file name be excluded?
415  def exclude?(fn)
416    calculate_exclude_regexp unless @exclude_re
417    fn =~ @exclude_re
418  end
419
420  DEFAULT_IGNORE_PATTERNS = [
421    /(^|[\/\\])CVS([\/\\]|$)/,
422    /(^|[\/\\])\.svn([\/\\]|$)/,
423    /(^|[\/\\])_darcs([\/\\]|$)/,
424    /\.bak$/,
425    /~$/,
426    /(^|[\/\\])core$/
427  ]
428  @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
429
430  def import(array)
431    @items = array
432    self
433  end
434
435  class << self
436    # Create a new file list including the files listed. Similar to:
437    #
438    #   FileList.new(*args)
439    def [](*args)
440      new(*args)
441    end
442
443    # Set the ignore patterns back to the default value.  The
444    # default patterns will ignore files
445    # * containing "CVS" in the file path
446    # * containing ".svn" in the file path
447    # * containing "_darcs" in the file path
448    # * ending with ".bak"
449    # * ending with "~"
450    # * named "core"
451    #
452    # Note that file names beginning with "." are automatically
453    # ignored by Ruby's glob patterns and are not specifically
454    # listed in the ignore patterns.
455    def select_default_ignore_patterns
456      @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
457    end
458
459    # Clear the ignore patterns.
460    def clear_ignore_patterns
461      @exclude_patterns = [ /^$/ ]
462    end
463  end
464
465end # FileList
466
467
468
469# = TEST
470#
471# TODO This test needs a mock File class.
472
473# FileList can't be tested without a FIXTURE setup.
474# So don't bother running it directly from here, but
475# transfer it over to the test directory.
476# Need a  better solution, is there a way to fake the
477# filesystem?
478
479=begin #no test
480
481  require 'test/unit'
482
483  class TC_FileList < Test::Unit::TestCase
484
485    def test_filelist
486      Dir.chdir File.join( $TESTDIR, 'filelist' ) do
487        fl = FileList.new
488        fl.include('*')
489        assert_equal( ['testfile.txt', 'testfile2.txt'], fl.to_a )
490        fl.exclude('*2.txt')
491        assert_equal( ['testfile.txt'], fl.to_a )
492      end
493    end
494
495  end
496
497=end