ratch 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+