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.
- data/.ruby +99 -0
- data/COPYING +203 -21
- data/History.rdoc +35 -0
- data/License.txt +204 -0
- data/README.rdoc +113 -0
- data/Version +1 -0
- data/bin/ludo +16 -0
- data/bin/ratch +1 -8
- data/lib/ratch.rb +28 -0
- data/lib/ratch.yml +99 -0
- data/lib/ratch/batch.rb +500 -0
- data/lib/ratch/console.rb +199 -0
- data/lib/ratch/core_ext.rb +1 -4
- data/lib/ratch/core_ext/facets.rb +15 -1
- data/lib/ratch/core_ext/filetest.rb +29 -0
- data/lib/ratch/core_ext/{string.rb → to_actual_filename.rb} +0 -23
- data/lib/ratch/core_ext/to_console.rb +30 -12
- data/lib/ratch/core_ext/{object.rb → to_yamlfrag.rb} +1 -0
- data/lib/ratch/core_ext/unfold_paragraphs.rb +27 -0
- data/lib/ratch/file_list.rb +411 -0
- data/lib/ratch/script.rb +99 -5
- data/lib/ratch/script/help.rb +84 -0
- data/lib/ratch/shell.rb +783 -0
- data/lib/ratch/system.rb +15 -0
- data/lib/ratch/utils/cli.rb +49 -0
- data/lib/ratch/utils/config.rb +52 -0
- data/lib/ratch/{emailer.rb → utils/email.rb} +43 -6
- data/lib/ratch/utils/ftp.rb +134 -0
- data/lib/ratch/utils/pom.rb +22 -0
- data/lib/ratch/utils/rdoc.rb +48 -0
- data/lib/ratch/utils/tar.rb +88 -0
- data/lib/ratch/utils/xdg.rb +39 -0
- data/lib/ratch/utils/zlib.rb +54 -0
- data/spec/01_shell.rdoc +198 -0
- data/spec/02_script.rdoc +34 -0
- data/spec/03_batch.rdoc +71 -0
- data/spec/04_system.rdoc +3 -0
- data/spec/applique/array.rb +8 -0
- data/spec/applique/setup.rb +20 -0
- data/test/case_batch.rb +63 -0
- data/test/case_shell.rb +46 -0
- data/test/core_ext/case_pathname.rb +361 -0
- data/test/helper.rb +4 -0
- data/test/utils/case_cli.rb +6 -0
- data/test/utils/case_config.rb +12 -0
- data/test/utils/case_email.rb +10 -0
- data/test/utils/case_ftp.rb +6 -0
- data/test/utils/case_pom.rb +17 -0
- data/test/utils/case_rdoc.rb +23 -0
- data/test/utils/case_tar.rb +6 -0
- data/test/utils/case_zlib.rb +11 -0
- data/test/utils/fixtures/pom_sample/Profile +4 -0
- data/test/utils/fixtures/rdoc_sample/README.rdoc +4 -0
- data/test/utils/fixtures/rdoc_sample/lib/rdoc_sample/rdoc_sample.rb +9 -0
- metadata +139 -82
- data/HISTORY +0 -6
- data/MANIFEST +0 -53
- data/NEWS +0 -12
- data/README +0 -87
- data/VERSION +0 -1
- data/demo/tryme-task.ratch +0 -12
- data/demo/tryme1.ratch +0 -6
- data/doc/log/basic_stats/index.html +0 -39
- data/doc/log/notes.xml +0 -18
- data/doc/log/stats.log +0 -14
- data/doc/log/syntax.log +0 -0
- data/doc/log/testunit.log +0 -156
- data/lib/ratch/commandline.rb +0 -16
- data/lib/ratch/core_ext/pathname.rb +0 -38
- data/lib/ratch/dsl.rb +0 -420
- data/lib/ratch/index.rb +0 -4
- data/lib/ratch/io.rb +0 -116
- data/lib/ratch/plugin.rb +0 -65
- data/lib/ratch/service.rb +0 -33
- data/lib/ratch/task.rb +0 -249
- data/lib/ratch/task2.rb +0 -298
- data/meta/abstract +0 -4
- data/meta/author +0 -1
- data/meta/contact +0 -1
- data/meta/homepage +0 -1
- data/meta/name +0 -1
- data/meta/requires +0 -4
- data/meta/summary +0 -1
- data/test/README +0 -1
- data/test/test_helper.rb +0 -4
- 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
|
data/lib/ratch/core_ext.rb
CHANGED
@@ -1 +1,15 @@
|
|
1
|
-
|
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
|
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
|
-
|
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.
|
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
|
74
|
+
flags
|
57
75
|
end
|
58
76
|
|
59
77
|
# Turn a hash into arguments.
|
@@ -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
|
+
|