ratch 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/.ruby +99 -0
  2. data/COPYING +203 -21
  3. data/History.rdoc +35 -0
  4. data/License.txt +204 -0
  5. data/README.rdoc +113 -0
  6. data/Version +1 -0
  7. data/bin/ludo +16 -0
  8. data/bin/ratch +1 -8
  9. data/lib/ratch.rb +28 -0
  10. data/lib/ratch.yml +99 -0
  11. data/lib/ratch/batch.rb +500 -0
  12. data/lib/ratch/console.rb +199 -0
  13. data/lib/ratch/core_ext.rb +1 -4
  14. data/lib/ratch/core_ext/facets.rb +15 -1
  15. data/lib/ratch/core_ext/filetest.rb +29 -0
  16. data/lib/ratch/core_ext/{string.rb → to_actual_filename.rb} +0 -23
  17. data/lib/ratch/core_ext/to_console.rb +30 -12
  18. data/lib/ratch/core_ext/{object.rb → to_yamlfrag.rb} +1 -0
  19. data/lib/ratch/core_ext/unfold_paragraphs.rb +27 -0
  20. data/lib/ratch/file_list.rb +411 -0
  21. data/lib/ratch/script.rb +99 -5
  22. data/lib/ratch/script/help.rb +84 -0
  23. data/lib/ratch/shell.rb +783 -0
  24. data/lib/ratch/system.rb +15 -0
  25. data/lib/ratch/utils/cli.rb +49 -0
  26. data/lib/ratch/utils/config.rb +52 -0
  27. data/lib/ratch/{emailer.rb → utils/email.rb} +43 -6
  28. data/lib/ratch/utils/ftp.rb +134 -0
  29. data/lib/ratch/utils/pom.rb +22 -0
  30. data/lib/ratch/utils/rdoc.rb +48 -0
  31. data/lib/ratch/utils/tar.rb +88 -0
  32. data/lib/ratch/utils/xdg.rb +39 -0
  33. data/lib/ratch/utils/zlib.rb +54 -0
  34. data/spec/01_shell.rdoc +198 -0
  35. data/spec/02_script.rdoc +34 -0
  36. data/spec/03_batch.rdoc +71 -0
  37. data/spec/04_system.rdoc +3 -0
  38. data/spec/applique/array.rb +8 -0
  39. data/spec/applique/setup.rb +20 -0
  40. data/test/case_batch.rb +63 -0
  41. data/test/case_shell.rb +46 -0
  42. data/test/core_ext/case_pathname.rb +361 -0
  43. data/test/helper.rb +4 -0
  44. data/test/utils/case_cli.rb +6 -0
  45. data/test/utils/case_config.rb +12 -0
  46. data/test/utils/case_email.rb +10 -0
  47. data/test/utils/case_ftp.rb +6 -0
  48. data/test/utils/case_pom.rb +17 -0
  49. data/test/utils/case_rdoc.rb +23 -0
  50. data/test/utils/case_tar.rb +6 -0
  51. data/test/utils/case_zlib.rb +11 -0
  52. data/test/utils/fixtures/pom_sample/Profile +4 -0
  53. data/test/utils/fixtures/rdoc_sample/README.rdoc +4 -0
  54. data/test/utils/fixtures/rdoc_sample/lib/rdoc_sample/rdoc_sample.rb +9 -0
  55. metadata +139 -82
  56. data/HISTORY +0 -6
  57. data/MANIFEST +0 -53
  58. data/NEWS +0 -12
  59. data/README +0 -87
  60. data/VERSION +0 -1
  61. data/demo/tryme-task.ratch +0 -12
  62. data/demo/tryme1.ratch +0 -6
  63. data/doc/log/basic_stats/index.html +0 -39
  64. data/doc/log/notes.xml +0 -18
  65. data/doc/log/stats.log +0 -14
  66. data/doc/log/syntax.log +0 -0
  67. data/doc/log/testunit.log +0 -156
  68. data/lib/ratch/commandline.rb +0 -16
  69. data/lib/ratch/core_ext/pathname.rb +0 -38
  70. data/lib/ratch/dsl.rb +0 -420
  71. data/lib/ratch/index.rb +0 -4
  72. data/lib/ratch/io.rb +0 -116
  73. data/lib/ratch/plugin.rb +0 -65
  74. data/lib/ratch/service.rb +0 -33
  75. data/lib/ratch/task.rb +0 -249
  76. data/lib/ratch/task2.rb +0 -298
  77. data/meta/abstract +0 -4
  78. data/meta/author +0 -1
  79. data/meta/contact +0 -1
  80. data/meta/homepage +0 -1
  81. data/meta/name +0 -1
  82. data/meta/requires +0 -4
  83. data/meta/summary +0 -1
  84. data/test/README +0 -1
  85. data/test/test_helper.rb +0 -4
  86. data/test/test_task.rb +0 -46
@@ -0,0 +1,199 @@
1
+ module Ratch
2
+
3
+ # Ratch Shell Console
4
+ #
5
+ # NOTE: Console class is still a work in progress.
6
+ class Console
7
+
8
+ attr_accessor :suppress_output
9
+
10
+ # Set up the user's environment, including a pure binding into which
11
+ # env.rb and commands.rb are mixed.
12
+ def initialize
13
+ require 'readline'
14
+ #root = Rush::Dir.new('/')
15
+ #home = Rush::Dir.new(ENV['HOME']) if ENV['HOME']
16
+ #pwd = Rush::Dir.new(ENV['PWD']) if ENV['PWD']
17
+
18
+ #@config = Rush::Config.new
19
+
20
+ @config.load_history.each do |item|
21
+ Readline::HISTORY.push(item)
22
+ end
23
+
24
+ Readline.basic_word_break_characters = ""
25
+ Readline.completion_append_character = nil
26
+ Readline.completion_proc = completion_proc
27
+
28
+ @shell = Ratch::Shell.new
29
+
30
+ @pure_binding = @shell.instance_eval("binding")
31
+
32
+ $last_res = nil
33
+
34
+ #eval @config.load_env, @pure_binding
35
+
36
+ #commands = @config.load_commands
37
+ #Rush::Dir.class_eval commands
38
+ #Array.class_eval commands
39
+ end
40
+
41
+ # Run the interactive shell using readline.
42
+ def run
43
+ loop do
44
+ cmd = Readline.readline('ratch> ')
45
+
46
+ finish if cmd.nil? or cmd == 'exit'
47
+
48
+ next if cmd == ""
49
+
50
+ Readline::HISTORY.push(cmd)
51
+
52
+ execute(cmd)
53
+ end
54
+ end
55
+
56
+ # Run a single command.
57
+ def execute(cmd)
58
+ res = eval(cmd, @pure_binding)
59
+ $last_res = res
60
+ eval("_ = $last_res", @pure_binding)
61
+ print_result(res)
62
+ #rescue Rush::Exception => e
63
+ # puts "Exception #{e.class} -> #{e.message}"
64
+ rescue ::Exception => e
65
+ puts "Exception #{e.class} -> #{e.message}"
66
+ e.backtrace.each do |t|
67
+ puts " #{::File.expand_path(t)}"
68
+ end
69
+ end
70
+
71
+ # Save history to ~/.config/ratch/history when the shell exists.
72
+ def finish
73
+ @config.save_history(Readline::HISTORY.to_a)
74
+ puts
75
+ exit
76
+ end
77
+
78
+ # TODO: FIX ALL BELOW
79
+
80
+ # Nice printing of different return types, particularly Rush::SearchResults.
81
+ def print_result(res)
82
+ return if self.suppress_output
83
+ if res.kind_of? String
84
+ puts res
85
+ elsif res.kind_of? Rush::SearchResults
86
+ widest = res.entries.map { |k| k.full_path.length }.max
87
+ res.entries_with_lines.each do |entry, lines|
88
+ print entry.full_path
89
+ print ' ' * (widest - entry.full_path.length + 2)
90
+ print "=> "
91
+ print res.colorize(lines.first.strip.head(30))
92
+ print "..." if lines.first.strip.length > 30
93
+ if lines.size > 1
94
+ print " (plus #{lines.size - 1} more matches)"
95
+ end
96
+ print "\n"
97
+ end
98
+ puts "#{res.entries.size} matching files with #{res.lines.size} matching lines"
99
+ elsif res.respond_to? :each
100
+ counts = {}
101
+ res.each do |item|
102
+ puts item
103
+ counts[item.class] ||= 0
104
+ counts[item.class] += 1
105
+ end
106
+ if counts == {}
107
+ puts "=> (empty set)"
108
+ else
109
+ count_s = counts.map do |klass, count|
110
+ "#{count} x #{klass}"
111
+ end.join(', ')
112
+ puts "=> #{count_s}"
113
+ end
114
+ else
115
+ puts "=> #{res.inspect}"
116
+ end
117
+ end
118
+
119
+ def path_parts(input) # :nodoc:
120
+ case input
121
+ when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)([\[\/])(['"])([^\3]*)$/
122
+ $~.to_a.slice(1, 4).push($~.pre_match)
123
+ when /((?:@{1,2}|\$|)\w+(?:\[[^\]]+\])*)(\.)(\w*)$/
124
+ $~.to_a.slice(1, 3).push($~.pre_match)
125
+ when /((?:@{1,2}|\$|)\w+)$/
126
+ $~.to_a.slice(1, 1).push(nil).push($~.pre_match)
127
+ else
128
+ [ nil, nil, nil ]
129
+ end
130
+ end
131
+
132
+ def complete_method(receiver, dot, partial_name, pre)
133
+ path = eval("#{receiver}.full_path", @pure_binding) rescue nil
134
+ box = eval("#{receiver}.box", @pure_binding) rescue nil
135
+ if path and box
136
+ (box[path].methods - Object.methods).select do |e|
137
+ e.match(/^#{Regexp.escape(partial_name)}/)
138
+ end.map do |e|
139
+ (pre || '') + receiver + dot + e
140
+ end
141
+ end
142
+ end
143
+
144
+ def complete_path(possible_var, accessor, quote, partial_path, pre) # :nodoc:
145
+ original_var, fixed_path = possible_var, ''
146
+ if /^(.+\/)([^\/]*)$/ === partial_path
147
+ fixed_path, partial_path = $~.captures
148
+ possible_var += "['#{fixed_path}']"
149
+ end
150
+ full_path = eval("#{possible_var}.full_path", @pure_binding) rescue nil
151
+ box = eval("#{possible_var}.box", @pure_binding) rescue nil
152
+ if full_path and box
153
+ Rush::Dir.new(full_path, box).entries.select do |e|
154
+ e.name.match(/^#{Regexp.escape(partial_path)}/)
155
+ end.map do |e|
156
+ (pre || '') + original_var + accessor + quote + fixed_path + e.name + (e.dir? ? "/" : "")
157
+ end
158
+ end
159
+ end
160
+
161
+ def complete_variable(partial_name, pre)
162
+ lvars = eval('local_variables', @pure_binding)
163
+ gvars = eval('global_variables', @pure_binding)
164
+ ivars = eval('instance_variables', @pure_binding)
165
+ (lvars + gvars + ivars).select do |e|
166
+ e.match(/^#{Regexp.escape(partial_name)}/)
167
+ end.map do |e|
168
+ (pre || '') + e
169
+ end
170
+ end
171
+
172
+ # Try to do tab completion on dir square brackets and slash accessors.
173
+ #
174
+ # Example:
175
+ #
176
+ # dir['subd # presing tab here will produce dir['subdir/ if subdir exists
177
+ # dir/'subd # presing tab here will produce dir/'subdir/ if subdir exists
178
+ #
179
+ # This isn't that cool yet, because it can't do multiple levels of subdirs.
180
+ # It does work remotely, though, which is pretty sweet.
181
+ def completion_proc
182
+ proc do |input|
183
+ receiver, accessor, *rest = path_parts(input)
184
+ if receiver
185
+ case accessor
186
+ when /^[\[\/]$/
187
+ complete_path(receiver, accessor, *rest)
188
+ when /^\.$/
189
+ complete_method(receiver, accessor, *rest)
190
+ when nil
191
+ complete_variable(receiver, *rest)
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -1,6 +1,3 @@
1
- __DIR__ = File.dirname(__FILE__)
2
-
3
- Dir[File.join(__DIR__, 'core_ext', '*.rb')].each do |file|
1
+ Dir[File.dirname(__FILE__) + '/core_ext/**/*.rb'].each do |file|
4
2
  require file
5
3
  end
6
-
@@ -1 +1,15 @@
1
- require 'facets'
1
+ # I know some people will be deterred by the dependency on Facets b/c they
2
+ # see it as a "heavy" dependency. But really that is far from true, consider
3
+ # the following libs are all that it used.
4
+
5
+ require 'facets/array/not_empty'
6
+ require 'facets/dir/multiglob' # DEPRECATE: when new #glob is working.
7
+ require 'facets/module/basename'
8
+ require 'facets/module/alias_accessor'
9
+ require 'facets/kernel/yes' # pulls in #ask and #no? too.
10
+ require 'facets/kernel/silence' # FIXME ???
11
+ require 'facets/kernel/disable_warnings'
12
+
13
+ require 'facets/pathname'
14
+ require 'facets/filetest'
15
+ require 'facets/fileutils'
@@ -0,0 +1,29 @@
1
+ module FileTest
2
+
3
+ # Return a cached list of the PATH environment variable.
4
+ # This is a support method used by #bin?
5
+ def command_paths
6
+ @command_paths ||= ENV['PATH'].split(/[:;]/)
7
+ end
8
+
9
+ # Is a file a bin/ executable?
10
+ #
11
+ # TODO: Make more robust. Probably needs to be fixed for Windows.
12
+ def bin?(fname)
13
+ is_bin = command_paths.any? do |f|
14
+ FileTest.exist?(File.join(f, fname))
15
+ end
16
+ #is_bin ? File.basename(fname) : false
17
+ is_bin ? fname : false
18
+ end
19
+
20
+ ## Is a file a task?
21
+ #
22
+ #def task?(path)
23
+ # task = File.dirname($0) + "/#{path}"
24
+ # task.chomp!('!')
25
+ # task if FileTest.file?(task) && FileTest.executable?(task)
26
+ #end
27
+
28
+ end
29
+
@@ -1,7 +1,5 @@
1
1
  class String
2
2
 
3
- attr_accessor :color
4
-
5
3
  # Find actual filename (casefolding) and returns it.
6
4
  # Returns nil if no file is found.
7
5
 
@@ -17,26 +15,5 @@ class String
17
15
  replace(filename) if filename
18
16
  end
19
17
 
20
- #
21
- def unfold_paragraphs
22
- blank = false
23
- text = ''
24
- split(/\n/).each do |line|
25
- if /\S/ !~ line
26
- text << "\n\n"
27
- blank = true
28
- else
29
- if /^(\s+|[*])/ =~ line
30
- text << (line.rstrip + "\n")
31
- else
32
- text << (line.rstrip + " ")
33
- end
34
- blank = false
35
- end
36
- end
37
- text = text.gsub("\n\n\n","\n\n")
38
- return text
39
- end
40
-
41
18
  end
42
19
 
@@ -1,18 +1,29 @@
1
+ # TODO: Improve the naming scheme of these methods.
2
+
1
3
  #
2
- class Array
4
+ class Array #:nodoc:
3
5
 
4
6
  # Convert an array into commandline parameters.
5
7
  # The array is accepted in the format of Ruby
6
8
  # method arguments --ie. [arg1, arg2, ..., hash]
7
9
 
8
10
  def to_console
9
- flags = (Hash===last ? pop : {})
10
- flags = flags.to_console
11
- flags + ' ' + join(" ")
11
+ #flags = (Hash===last ? pop : {})
12
+ #flags = flags.to_console
13
+ #flags + ' ' + join(" ")
14
+ to_argv.join(' ')
12
15
  end
13
16
 
17
+ # TODO: DEPRECATE
14
18
  alias_method :to_params, :to_console
15
19
 
20
+ #
21
+ def to_argv
22
+ flags = (Hash===last ? pop : {})
23
+ flags = flags.to_argv
24
+ flags + self
25
+ end
26
+
16
27
  # def to_console
17
28
  # flags = (Hash===last ? pop : {})
18
29
  # flags = flags.collect do |f,v|
@@ -35,25 +46,32 @@ end
35
46
 
36
47
  class Hash
37
48
 
38
- # Convert an array into command line parameters.
49
+ # Convert a Hash into command line arguments.
39
50
  # The array is accepted in the format of Ruby
40
51
  # method arguments --ie. [arg1, arg2, ..., hash]
41
-
42
52
  def to_console
43
- flags = collect do |f,v|
53
+ to_argv.join(' ')
54
+ end
55
+
56
+ # Convert a Hash into command line parameters.
57
+ # The array is accepted in the format of Ruby
58
+ # method arguments --ie. [arg1, arg2, ..., hash]
59
+ def to_argv
60
+ flags = []
61
+ each do |f,v|
44
62
  m = f.to_s.size == 1 ? '-' : '--'
45
63
  case v
46
64
  when Array
47
- v.collect{ |e| "#{m}#{f}='#{e}'" }.join(' ')
65
+ v.each{ |e| flags << "#{m}#{f}='#{e}'" }
48
66
  when true
49
- "#{m}#{f}"
67
+ flags << "#{m}#{f}"
50
68
  when false, nil
51
- ''
69
+ # nothing
52
70
  else
53
- "#{m}#{f}='#{v}'"
71
+ flags << "#{m}#{f}='#{v}'"
54
72
  end
55
73
  end
56
- flags.join(" ")
74
+ flags
57
75
  end
58
76
 
59
77
  # Turn a hash into arguments.
@@ -1,5 +1,6 @@
1
1
  class Object
2
2
 
3
+ #
3
4
  def to_yamlfrag
4
5
  to_yaml.sub("---",'').rstrip
5
6
  end
@@ -0,0 +1,27 @@
1
+ # TODO: Replace with facets/string/unfold
2
+
3
+ class String
4
+
5
+ #
6
+ def unfold_paragraphs
7
+ blank = false
8
+ text = ''
9
+ split(/\n/).each do |line|
10
+ if /\S/ !~ line
11
+ text << "\n\n"
12
+ blank = true
13
+ else
14
+ if /^(\s+|[*])/ =~ line
15
+ text << (line.rstrip + "\n")
16
+ else
17
+ text << (line.rstrip + " ")
18
+ end
19
+ blank = false
20
+ end
21
+ end
22
+ text = text.gsub("\n\n\n","\n\n")
23
+ return text
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,411 @@
1
+ #require 'rake/file_utils_ext'
2
+
3
+ module Ratch
4
+
5
+ # A FileList is essentially an array with a few helper methods defined to
6
+ # make file manipulation a bit easier.
7
+ #
8
+ # FileLists are lazy. When given a list of glob patterns for possible files
9
+ # to be included in the file list, instead of searching the file structures
10
+ # to find the files, a FileList holds the pattern for latter use.
11
+ #
12
+ # This allows us to define a number of FileList to match any number of
13
+ # files, but only search out the actual files when then FileList itself is
14
+ # actually used. The key is that the first time an element of the
15
+ # FileList/Array is requested, the pending patterns are resolved into a real
16
+ # list of file names.
17
+ #
18
+ class FileList
19
+
20
+ # == Method Delegation
21
+ #
22
+ # The lazy evaluation magic of FileLists happens by implementing all the
23
+ # array specific methods to call +resolve+ before delegating the heavy
24
+ # lifting to an embedded array object (@items).
25
+ #
26
+ # In addition, there are two kinds of delegation calls. The regular kind
27
+ # delegates to the @items array and returns the result directly. Well,
28
+ # almost directly. It checks if the returned value is the @items object
29
+ # itself, and if so will return the FileList object instead.
30
+ #
31
+ # The second kind of delegation call is used in methods that normally
32
+ # return a new Array object. We want to capture the return value of these
33
+ # methods and wrap them in a new FileList object. We enumerate these
34
+ # methods in the +SPECIAL_RETURN+ list below.
35
+
36
+ # List of array methods (that are not in +Object+) that need to be
37
+ # delegated.
38
+ ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
39
+
40
+ # List of additional methods that must be delegated.
41
+ MUST_DEFINE = %w[to_a inspect <=>]
42
+
43
+ # List of methods that should not be delegated here (we define special
44
+ # versions of them explicitly below).
45
+ MUST_NOT_DEFINE = %w[to_a to_ary partition *]
46
+
47
+ # List of delegated methods that return new array values which need
48
+ # wrapping.
49
+ SPECIAL_RETURN = %w[
50
+ map collect sort sort_by select find_all reject grep
51
+ compact flatten uniq values_at
52
+ + - & |
53
+ ]
54
+
55
+ DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq
56
+
57
+ # Now do the delegation.
58
+ DELEGATING_METHODS.each_with_index do |sym, i|
59
+ if SPECIAL_RETURN.include?(sym)
60
+ ln = __LINE__+1
61
+ class_eval %{
62
+ def #{sym}(*args, &block)
63
+ resolve
64
+ result = @items.send(:#{sym}, *args, &block)
65
+ FileList.new.import(result)
66
+ end
67
+ }, __FILE__, ln
68
+ else
69
+ ln = __LINE__+1
70
+ class_eval %{
71
+ def #{sym}(*args, &block)
72
+ resolve
73
+ result = @items.send(:#{sym}, *args, &block)
74
+ result.object_id == @items.object_id ? self : result
75
+ end
76
+ }, __FILE__, ln
77
+ end
78
+ end
79
+
80
+ # Create a new file list including the files listed. Similar to:
81
+ #
82
+ # FileList.new(*args)
83
+ def self.[](*args)
84
+ new(*args)
85
+ end
86
+
87
+ # Same a #new but removes the default exclusions.
88
+ def self.all(*args)
89
+ obj = new(*args)
90
+ obj.clear_exclude
91
+ obj
92
+ end
93
+
94
+ # Create a file list from the globbable patterns given. If you wish to
95
+ # perform multiple includes or excludes at object build time, use the
96
+ # "yield self" pattern.
97
+ #
98
+ # Example:
99
+ # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
100
+ #
101
+ # pkg_files = FileList.new('lib/**/*') do |fl|
102
+ # fl.exclude(/\bCVS\b/)
103
+ # end
104
+ #
105
+ def initialize(*patterns)
106
+ @pending_add = []
107
+ @pending = false
108
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
109
+ @exclude_procs = DEFAULT_IGNORE_PROCS.dup
110
+ @items = []
111
+ patterns.each { |pattern| include(pattern) }
112
+ yield self if block_given?
113
+ end
114
+
115
+ # Add file names defined by glob patterns to the file list. If an array
116
+ # is given, add each element of the array.
117
+ #
118
+ # Example:
119
+ # file_list.include("*.java", "*.cfg")
120
+ # file_list.include %w( math.c lib.h *.o )
121
+ #
122
+ def include(*filenames)
123
+ # TODO: check for pending
124
+ filenames.each do |fn|
125
+ if fn.respond_to? :to_ary
126
+ include(*fn.to_ary)
127
+ else
128
+ @pending_add << fn
129
+ end
130
+ end
131
+ @pending = true
132
+ self
133
+ end
134
+ alias :add :include
135
+
136
+ # Register a list of file name patterns that should be excluded from the
137
+ # list. Patterns may be regular expressions, glob patterns or regular
138
+ # strings. In addition, a block given to exclude will remove entries that
139
+ # return true when given to the block.
140
+ #
141
+ # Note that glob patterns are expanded against the file system. If a file
142
+ # is explicitly added to a file list, but does not exist in the file
143
+ # system, then an glob pattern in the exclude list will not exclude the
144
+ # file.
145
+ #
146
+ # Examples:
147
+ # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
148
+ # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
149
+ #
150
+ # If "a.c" is a file, then ...
151
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
152
+ #
153
+ # If "a.c" is not a file, then ...
154
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
155
+ #
156
+ def exclude(*patterns, &block)
157
+ patterns.each do |pat|
158
+ @exclude_patterns << pat
159
+ end
160
+ if block_given?
161
+ @exclude_procs << block
162
+ end
163
+ resolve_exclude if ! @pending
164
+ self
165
+ end
166
+
167
+
168
+ # Clear all the exclude patterns so that we exclude nothing.
169
+ def clear_exclude
170
+ @exclude_patterns = []
171
+ @exclude_procs = []
172
+ self
173
+ end
174
+
175
+ # Define equality.
176
+ def ==(array)
177
+ to_ary == array
178
+ end
179
+
180
+ # Return the internal array object.
181
+ def to_a
182
+ resolve
183
+ @items
184
+ end
185
+
186
+ # Return the internal array object.
187
+ def to_ary
188
+ to_a
189
+ end
190
+
191
+ # Lie about our class.
192
+ def is_a?(klass)
193
+ klass == Array || super(klass)
194
+ end
195
+ alias kind_of? is_a?
196
+
197
+ # Redefine * to return either a string or a new file list.
198
+ def *(other)
199
+ result = @items * other
200
+ case result
201
+ when Array
202
+ FileList.new.import(result)
203
+ else
204
+ result
205
+ end
206
+ end
207
+
208
+ # Resolve all the pending adds now.
209
+ def resolve
210
+ if @pending
211
+ @pending = false
212
+ @pending_add.each do |fn| resolve_add(fn) end
213
+ @pending_add = []
214
+ resolve_exclude
215
+ end
216
+ self
217
+ end
218
+
219
+ def resolve_add(fn)
220
+ case fn
221
+ when %r{[*?\[\{]}
222
+ add_matching(fn)
223
+ else
224
+ self << fn
225
+ end
226
+ end
227
+ private :resolve_add
228
+
229
+ def resolve_exclude
230
+ reject! { |fn| exclude?(fn) }
231
+ self
232
+ end
233
+ private :resolve_exclude
234
+
235
+ # Return a new FileList with the results of running +sub+ against each
236
+ # element of the orignal list.
237
+ #
238
+ # Example:
239
+ # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
240
+ #
241
+ def sub(pat, rep)
242
+ inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
243
+ end
244
+
245
+ # Return a new FileList with the results of running +gsub+ against each
246
+ # element of the original list.
247
+ #
248
+ # Example:
249
+ # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
250
+ # => ['lib\\test\\file', 'x\\y']
251
+ #
252
+ def gsub(pat, rep)
253
+ inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
254
+ end
255
+
256
+ # Same as +sub+ except that the oringal file list is modified.
257
+ def sub!(pat, rep)
258
+ each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
259
+ self
260
+ end
261
+
262
+ # Same as +gsub+ except that the original file list is modified.
263
+ def gsub!(pat, rep)
264
+ each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
265
+ self
266
+ end
267
+
268
+ # Apply the pathmap spec to each of the included file names, returning a
269
+ # new file list with the modified paths. (See String#pathmap for
270
+ # details.)
271
+ def pathmap(spec=nil)
272
+ collect { |fn| fn.pathmap(spec) }
273
+ end
274
+
275
+ # Return a new FileList with <tt>String#ext</tt> method applied to
276
+ # each member of the array.
277
+ #
278
+ # This method is a shortcut for:
279
+ #
280
+ # array.collect { |item| item.ext(newext) }
281
+ #
282
+ # +ext+ is a user added method for the Array class.
283
+ def ext(newext='')
284
+ collect { |fn| fn.ext(newext) }
285
+ end
286
+
287
+
288
+ # Grep each of the files in the filelist using the given pattern. If a
289
+ # block is given, call the block on each matching line, passing the file
290
+ # name, line number, and the matching line of text. If no block is given,
291
+ # a standard emac style file:linenumber:line message will be printed to
292
+ # standard out. Returns the number of matched items.
293
+ def egrep(pattern, *options)
294
+ matched = 0
295
+ each do |fn|
296
+ begin
297
+ open(fn, "rb", *options) do |inf|
298
+ count = 0
299
+ inf.each do |line|
300
+ count += 1
301
+ if pattern.match(line)
302
+ matched += 1
303
+ if block_given?
304
+ yield fn, count, line
305
+ else
306
+ puts "#{fn}:#{count}:#{line}"
307
+ end
308
+ end
309
+ end
310
+ end
311
+ rescue StandardError => ex
312
+ puts "Error while processing '#{fn}': #{ex}"
313
+ end
314
+ end
315
+ matched
316
+ end
317
+
318
+ # Return a new file list that only contains file names from the current
319
+ # file list that exist on the file system.
320
+ def existing
321
+ select { |fn| File.exist?(fn) }
322
+ end
323
+
324
+ # Modify the current file list so that it contains only file name that
325
+ # exist on the file system.
326
+ def existing!
327
+ resolve
328
+ @items = @items.select { |fn| File.exist?(fn) }
329
+ self
330
+ end
331
+
332
+ # FileList version of partition. Needed because the nested arrays should
333
+ # be FileLists in this version.
334
+ def partition(&block) # :nodoc:
335
+ resolve
336
+ result = @items.partition(&block)
337
+ [
338
+ FileList.new.import(result[0]),
339
+ FileList.new.import(result[1]),
340
+ ]
341
+ end
342
+
343
+ # Convert a FileList to a string by joining all elements with a space.
344
+ def to_s
345
+ resolve
346
+ self.join(' ')
347
+ end
348
+
349
+ # Add matching glob patterns.
350
+ def add_matching(pattern)
351
+ Dir[pattern].each do |fn|
352
+ self << fn unless exclude?(fn)
353
+ end
354
+ end
355
+ private :add_matching
356
+
357
+ # Should the given file name be excluded?
358
+ def exclude?(fn)
359
+ return true if @exclude_patterns.any? do |pat|
360
+ case pat
361
+ when Regexp
362
+ fn =~ pat
363
+ when /[*?]/
364
+ File.fnmatch?(pat, fn, File::FNM_PATHNAME)
365
+ else
366
+ fn == pat
367
+ end
368
+ end
369
+ @exclude_procs.any? { |p| p.call(fn) }
370
+ end
371
+
372
+ def import(array)
373
+ @items = array
374
+ self
375
+ end
376
+
377
+ # Clone an object by making a new object and setting all the instance
378
+ # variables to the same values.
379
+ def dup
380
+ sibling = self.class.new
381
+ instance_variables.each do |ivar|
382
+ value = self.instance_variable_get(ivar)
383
+ new_value = value.clone rescue value
384
+ sibling.instance_variable_set(ivar, new_value)
385
+ end
386
+ sibling.taint if tainted?
387
+ sibling
388
+ end
389
+
390
+ def clone
391
+ sibling = dup
392
+ sibling.freeze if frozen?
393
+ sibling
394
+ end
395
+
396
+ #
397
+ DEFAULT_IGNORE_PATTERNS = [
398
+ /(^|[\/\\])CVS([\/\\]|$)/,
399
+ /(^|[\/\\])\.svn([\/\\]|$)/,
400
+ /\.bak$/,
401
+ /~$/
402
+ ]
403
+
404
+ #
405
+ DEFAULT_IGNORE_PROCS = [
406
+ proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
407
+ ]
408
+
409
+ end
410
+ end
411
+