test-unit-mock 0.30

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 (10) hide show
  1. data/COPYING +58 -0
  2. data/ChangeLog +262 -0
  3. data/README +203 -0
  4. data/install.rb +85 -0
  5. data/misc/readmecode.rb +125 -0
  6. data/mock.rb +467 -0
  7. data/test-unit-mock.gemspec +20 -0
  8. data/test.rb +327 -0
  9. data/utils.rb +345 -0
  10. metadata +59 -0
@@ -0,0 +1,20 @@
1
+ require 'date'
2
+ Gem::Specification.new do |s|
3
+ s.name = %q{test-unit-mock}
4
+ s.version = "0.30"
5
+ s.date = Date.today.to_s
6
+ s.summary = %q{Test::Unit::Mock is a class for conveniently building mock objects in Test::Unit test cases.}
7
+ s.author = %q{Michael Granger}
8
+ s.email = %q{ged@FaerieMUD.org}
9
+ s.homepage = %q{http://www.deveiate.org/code/Test-Unit-Mock.html}
10
+ s.files = Dir.glob('**/*')
11
+ s.require_path = %q{.}
12
+ s.autorequire = %q{mock}
13
+ s.has_rdoc = true
14
+ s.rdoc_options = ["--main", "README"]
15
+ s.extra_rdoc_files = ["README"]
16
+ s.test_files = %w{test.rb}
17
+ s.require_path = %q{.}
18
+ s.add_dependency('algorithm-diff', '>= 0.0.0')
19
+ s.required_ruby_version = Gem::Version::Requirement.new(">= 1.6.8")
20
+ end
data/test.rb ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/ruby
2
+ # :nodoc: all
3
+ #
4
+ # Test suite for Test::Unit::MockObject
5
+ #
6
+ #
7
+
8
+ $LOAD_PATH.unshift '.'
9
+
10
+ require 'test/unit'
11
+ require 'mock'
12
+
13
+ require 'utils'
14
+ include UtilityFunctions
15
+
16
+ ### Set up a class to be mocked
17
+ class TestClass
18
+ def initialize( arg1 )
19
+ @arg1 = arg1
20
+ end
21
+
22
+ attr_accessor :arg1
23
+
24
+ def first
25
+ return "This is the first method"
26
+ end
27
+
28
+ def second( arg1 )
29
+ return "This is the second method (#{arg1})"
30
+ end
31
+
32
+ def third( arg1, arg2="default" )
33
+ return "This is the third method (#{arg1}, #{arg2})"
34
+ end
35
+
36
+ def fourth( arg1="default" )
37
+ return "This is the fourth method (#{arg1})"
38
+ end
39
+
40
+ def fifth( *args )
41
+ return "This is the fifth method (#{args.collect do |arg| arg.inspect end.join(',')})"
42
+ end
43
+
44
+ end
45
+
46
+
47
+ ### The test case
48
+ class TUMockTestCase < Test::Unit::TestCase
49
+
50
+ # Testing and setup methods that are exected to be available from MockObject
51
+ # instances. Does not include double-underscored and non-camelCased aliases,
52
+ # which are auto-generated by the test.
53
+ Interface = [
54
+ # Setup methods
55
+ :setReturnValues,
56
+ :setCallOrder,
57
+ :strictCallOrder=,
58
+ :strictCallOrder?,
59
+
60
+ # Testing methods
61
+ :callTrace,
62
+ :activate,
63
+ :verify,
64
+ :activate,
65
+ :clear,
66
+ :reset,
67
+ ]
68
+
69
+ #################################################################
70
+ ### C L A S S M E T H O D S
71
+ #################################################################
72
+
73
+ @@methodCounter = 0
74
+ @setupMethods = []
75
+ @teardownMethods = []
76
+
77
+ class << self
78
+ attr_accessor :setupMethods
79
+ attr_accessor :teardownMethods
80
+ end
81
+
82
+
83
+ ### Append a setup block for the current testcase
84
+ def self::addSetupBlock( &block )
85
+ @@methodCounter += 1
86
+ newMethodName = "setup_#{@@methodCounter}".intern
87
+ define_method( newMethodName, &block )
88
+ self.setupMethods.push newMethodName
89
+ end
90
+
91
+
92
+ ### Prepend a teardown block for the current testcase
93
+ def self::addTeardownBlock( &block )
94
+ @@methodCounter += 1
95
+ newMethodName = "teardown_#{@@methodCounter}".intern
96
+ define_method( newMethodName, &block )
97
+ self.teardownMethods.unshift newMethodName
98
+ end
99
+
100
+
101
+ #################################################################
102
+ ### I N S T A N C E M E T H O D S
103
+ #################################################################
104
+
105
+ ### Set up a test before running
106
+ def setup
107
+ self.class.setupMethods.each {|sblock|
108
+ self.send( sblock )
109
+ }
110
+ end
111
+ alias_method :set_up, :setup
112
+
113
+ ### Tear down a test after it runs
114
+ def teardown
115
+ self.class.teardownMethods.each {|tblock|
116
+ self.send( tblock )
117
+ }
118
+ end
119
+ alias_method :tear_down, :teardown
120
+
121
+
122
+ #################################################################
123
+ ### T E S T M E T H O D S
124
+ #################################################################
125
+
126
+ ### Test to be sure the mockup class and factory method are loaded.
127
+ def test_00_loaded
128
+ assert_instance_of Class, Test::Unit::Mockup
129
+ assert_respond_to Test::Unit, :MockObject
130
+ end
131
+
132
+
133
+ ### Test mock object creation/interface
134
+ def test_10_Constructor
135
+ mockClass = nil
136
+ mo = nil
137
+
138
+ assert_nothing_raised { mockClass = Test::Unit::MockObject(TestClass) }
139
+ assert_instance_of Class, mockClass
140
+
141
+ assert_nothing_raised { mo = mockClass::new }
142
+ assert_instance_of TestClass, mo
143
+ assert_kind_of TestClass, mo
144
+ assert mo.is_a?( TestClass ),
145
+ "Mock object doesn't fake the is_a? method"
146
+ assert mo.__class <= Test::Unit::Mockup,
147
+ "Mock object real type not a Test::Unit::Mockup derivative"
148
+
149
+ Interface.each {|meth|
150
+
151
+ # Test the regular method and the double-underscore one.
152
+ assert_respond_to mo, meth
153
+ assert_respond_to mo, "__#{meth.to_s}".intern
154
+
155
+ # Test for non-camelCased alias
156
+ if /[A-Z]/ =~ meth.to_s
157
+ # Oh, the irony.
158
+ nonCamelCaseMethod = meth.to_s.gsub( /([A-Z])/ ) {|match|
159
+ "_" + match.downcase
160
+ }.intern
161
+
162
+ assert_respond_to mo, nonCamelCaseMethod
163
+ end
164
+
165
+ }
166
+
167
+ self.class.addSetupBlock {
168
+ @mockObject = Test::Unit::MockObject(TestClass).new
169
+ }
170
+ self.class.addTeardownBlock {
171
+ @mockObject = nil
172
+ }
173
+ end
174
+
175
+
176
+ ### Test the mocked methods
177
+ def test_15_FakedMethods
178
+ fakedMethod = nil
179
+ rval = nil
180
+
181
+ TestClass.instance_methods(false).each {|methName|
182
+ realMethod = TestClass.instance_method( methName )
183
+
184
+ assert_nothing_raised { fakedMethod = @mockObject.method(methName) }
185
+ assert_instance_of Method, fakedMethod
186
+ assert_equal realMethod.arity, fakedMethod.arity
187
+ }
188
+
189
+ @mockObject.activate
190
+
191
+ # Now test calling with and without correct arity
192
+ assert_raises( ArgumentError ) { @mockObject.first("foo") }
193
+ assert_nothing_raised { @mockObject.first }
194
+
195
+ assert_raises( ArgumentError ) { @mockObject.second }
196
+ assert_nothing_raised { @mockObject.second("foo") }
197
+
198
+ assert_raises( ArgumentError ) { @mockObject.third }
199
+ assert_nothing_raised { @mockObject.third("foo") }
200
+
201
+ assert_nothing_raised { @mockObject.fourth("foo") }
202
+ assert_nothing_raised { @mockObject.fourth }
203
+
204
+ assert_nothing_raised { @mockObject.fifth }
205
+ assert_nothing_raised { @mockObject.fifth("foo") }
206
+ assert_nothing_raised { @mockObject.fifth("foo", "bar", 1, 11, $stderr, 1..18) }
207
+
208
+ # Test for call trace
209
+ assert_nothing_raised { rval = @mockObject.callTrace }
210
+ assert_instance_of Array, rval
211
+ assert_equal 8, rval.length
212
+
213
+ debugMsg "Call trace: "
214
+ rval.each {|trace| debugMsg trace}
215
+
216
+ end
217
+
218
+
219
+ ### Test the methods that aren't mocked (those that aren't already tested
220
+ ### above)
221
+ def test_17_UnmockedMethods
222
+ rval = nil
223
+
224
+ # inspect
225
+ assert_nothing_raised { rval = @mockObject.inspect }
226
+ assert_match( /#<Class:0x[a-f0-9]+>:0x[a-f0-9]+ /, rval )
227
+
228
+ # class
229
+ assert_nothing_raised { rval = @mockObject.class }
230
+ assert_equal TestClass, rval
231
+
232
+ # method, send, respond_to?
233
+ assert_nothing_raised { rval = @mockObject.send(:inspect) }
234
+ assert_not_nil rval
235
+ assert_nothing_raised { rval = @mockObject.method(:inspect) }
236
+ assert_instance_of Method, rval
237
+ assert_nothing_raised { rval = @mockObject.respond_to?(:fifth) }
238
+ assert_equal true, rval
239
+ end
240
+
241
+
242
+ ### Test the mock setup methods
243
+ def test_20_SetupMethods
244
+ assert_nothing_raised {
245
+ @mockObject.setReturnValues( :first => 1,
246
+ :second => 2,
247
+ :third => 3 )
248
+ }
249
+ assert_nothing_raised {
250
+ @mockObject.setReturnValues( :fourth => 4,
251
+ :second => 'II' )
252
+ }
253
+
254
+ assert_equal 1, @mockObject.first
255
+ assert_equal 'II', @mockObject.second(:foo)
256
+ assert_equal 3, @mockObject.third(:foo)
257
+ assert_equal 4, @mockObject.fourth
258
+ assert_equal true, @mockObject.fifth
259
+ end
260
+
261
+
262
+ ### Test the call-order verification with "strict" mode turned off
263
+ def test_30_TestCallOrder
264
+ assert_nothing_raised {
265
+ @mockObject.setCallOrder( :first, :second, :third, :fourth, :fifth )
266
+ }
267
+
268
+ # Test a successful run with an extra call
269
+ assert_raises( RuntimeError ) { @mockObject.verify }
270
+ assert_nothing_raised { @mockObject.activate }
271
+
272
+ @mockObject.first
273
+ @mockObject.second(:foo)
274
+ @mockObject.arg1
275
+ @mockObject.third(:foo)
276
+ @mockObject.fourth
277
+ @mockObject.fifth
278
+
279
+ assert_raises( RuntimeError ) { @mockObject.activate }
280
+ assert_nothing_raised { @mockObject.verify }
281
+
282
+ # Test a failure
283
+ @mockObject.clear
284
+ @mockObject.activate
285
+
286
+ @mockObject.first
287
+ @mockObject.second(:foo)
288
+ @mockObject.arg1
289
+ @mockObject.fourth
290
+ @mockObject.fifth
291
+
292
+ assert_raises( Test::Unit::AssertionFailedError ) { @mockObject.verify }
293
+ end
294
+
295
+
296
+ ### Test call-order verification with "strict" mode turned on
297
+ def test_35_TestStrictCallOrder
298
+ @mockObject.setCallOrder( :first, :second, :third, :fourth, :fifth )
299
+ assert_nothing_raised { @mockObject.strictCallOrder = true }
300
+
301
+ # Test a failure: an extra method
302
+ @mockObject.activate
303
+
304
+ @mockObject.first
305
+ @mockObject.second(:foo)
306
+ @mockObject.arg1
307
+ @mockObject.third(:foo)
308
+ @mockObject.fourth
309
+ @mockObject.fifth
310
+
311
+ assert_raises( Test::Unit::AssertionFailedError ) { @mockObject.verify }
312
+
313
+ # Test a failure for a missing method
314
+ @mockObject.clear
315
+ @mockObject.activate
316
+
317
+ @mockObject.first
318
+ @mockObject.second(:foo)
319
+ @mockObject.arg1
320
+ @mockObject.fourth
321
+ @mockObject.fifth
322
+
323
+ assert_raises( Test::Unit::AssertionFailedError ) { @mockObject.verify }
324
+ end
325
+
326
+ end
327
+
@@ -0,0 +1,345 @@
1
+ #
2
+ # Install/distribution utility functions
3
+ # $Id: utils.rb,v 1.2 2003/03/04 23:35:14 deveiant Exp $
4
+ #
5
+ # Copyright (c) 2001-2003, The FaerieMUD Consortium.
6
+ #
7
+ # This is free software. You may use, modify, and/or redistribute this
8
+ # software under the terms of the Perl Artistic License. (See
9
+ # http://language.perl.com/misc/Artistic.html)
10
+ #
11
+
12
+
13
+ BEGIN {
14
+ begin
15
+ require 'readline'
16
+ include Readline
17
+ rescue LoadError => e
18
+ $stderr.puts "Faking readline..."
19
+ def readline( prompt )
20
+ $stderr.print prompt.chomp
21
+ return $stdin.gets.chomp
22
+ end
23
+ end
24
+ }
25
+
26
+ module UtilityFunctions
27
+
28
+ # The list of regexen that eliminate files from the MANIFEST
29
+ ANTIMANIFEST = [
30
+ /makedist\.rb/,
31
+ /\bCVS\b/,
32
+ /~$/,
33
+ /^#/,
34
+ %r{docs/html},
35
+ %r{docs/man},
36
+ /^TEMPLATE/,
37
+ /\.cvsignore/,
38
+ /\.s?o$/
39
+ ]
40
+
41
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
42
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
43
+ AnsiAttributes = {
44
+ 'clear' => 0,
45
+ 'reset' => 0,
46
+ 'bold' => 1,
47
+ 'dark' => 2,
48
+ 'underline' => 4,
49
+ 'underscore' => 4,
50
+ 'blink' => 5,
51
+ 'reverse' => 7,
52
+ 'concealed' => 8,
53
+
54
+ 'black' => 30, 'on_black' => 40,
55
+ 'red' => 31, 'on_red' => 41,
56
+ 'green' => 32, 'on_green' => 42,
57
+ 'yellow' => 33, 'on_yellow' => 43,
58
+ 'blue' => 34, 'on_blue' => 44,
59
+ 'magenta' => 35, 'on_magenta' => 45,
60
+ 'cyan' => 36, 'on_cyan' => 46,
61
+ 'white' => 37, 'on_white' => 47
62
+ }
63
+
64
+ ErasePreviousLine = "\033[A\033[K"
65
+
66
+
67
+ ###############
68
+ module_function
69
+ ###############
70
+
71
+ # Create a string that contains the ANSI codes specified and return it
72
+ def ansiCode( *attributes )
73
+ attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
74
+ if attr.empty?
75
+ return ''
76
+ else
77
+ return "\e[%sm" % attr
78
+ end
79
+ end
80
+
81
+ # Test for the presence of the specified <tt>library</tt>, and output a
82
+ # message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
83
+ # is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
84
+ def testForLibrary( library, nicename=nil )
85
+ nicename ||= library
86
+ message( "Testing for the #{nicename} library..." )
87
+ if $:.detect {|dir| File.exists?(File.join(dir,"#{library}.rb")) || File.exists?(File.join(dir,"#{library}.so"))}
88
+ message( "found.\n" )
89
+ return true
90
+ else
91
+ message( "not found.\n" )
92
+ return false
93
+ end
94
+ end
95
+
96
+ # Test for the presence of the specified <tt>library</tt>, and output a
97
+ # message describing the problem using <tt>nicename</tt>. If
98
+ # <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
99
+ # to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
100
+ # specified, they are also use to build a message describing how to find the
101
+ # required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
102
+ # will cause the program to abort.
103
+ def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
104
+ nicename ||= library
105
+ unless testForLibrary( library, nicename )
106
+ msgs = [ "You are missing the required #{nicename} library.\n" ]
107
+ msgs << "RAA: #{raaUrl}\n" if raaUrl
108
+ msgs << "Download: #{downloadUrl}\n" if downloadUrl
109
+ if fatal
110
+ abort msgs.join('')
111
+ else
112
+ errorMessage msgs.join('')
113
+ end
114
+ end
115
+ return true
116
+ end
117
+
118
+ ### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
119
+ ### blue).
120
+ def header( msg )
121
+ msg.chomp!
122
+ $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
123
+ $stderr.flush
124
+ end
125
+
126
+ ### Output <tt>msg</tt> to STDERR and flush it.
127
+ def message( msg )
128
+ $stderr.print msg
129
+ $stderr.flush
130
+ end
131
+
132
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
133
+ ### (white on red).
134
+ def errorMessage( msg )
135
+ message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
136
+ end
137
+
138
+ ### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
139
+ ### (yellow on blue).
140
+ def debugMsg( msg )
141
+ return unless $DEBUG
142
+ msg.chomp!
143
+ $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
144
+ $stderr.flush
145
+ end
146
+
147
+ ### Erase the previous line (if supported by your terminal) and output the
148
+ ### specified <tt>msg</tt> instead.
149
+ def replaceMessage( msg )
150
+ print ErasePreviousLine
151
+ message( msg )
152
+ end
153
+
154
+ ### Output a divider made up of <tt>length</tt> hyphen characters.
155
+ def divider( length=75 )
156
+ puts "\r" + ("-" * length )
157
+ end
158
+ alias :writeLine :divider
159
+
160
+ ### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
161
+ ### status of 1.
162
+ def abort( msg )
163
+ print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
164
+ Kernel.exit!( 1 )
165
+ end
166
+
167
+ ### Output the specified <tt>promptString</tt> as a prompt (in green) and
168
+ ### return the user's input with leading and trailing spaces removed.
169
+ def prompt( promptString )
170
+ promptString.chomp!
171
+ return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip
172
+ end
173
+
174
+ ### Prompt the user with the given <tt>promptString</tt> via #prompt,
175
+ ### substituting the given <tt>default</tt> if the user doesn't input
176
+ ### anything.
177
+ def promptWithDefault( promptString, default )
178
+ response = prompt( "%s [%s]" % [ promptString, default ] )
179
+ if response.empty?
180
+ return default
181
+ else
182
+ return response
183
+ end
184
+ end
185
+
186
+ ### Search for the program specified by the given <tt>progname</tt> in the
187
+ ### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
188
+ ### no such program is in the path.
189
+ def findProgram( progname )
190
+ ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
191
+ file = File.join( d, progname )
192
+ return file if File.executable?( file )
193
+ }
194
+ return nil
195
+ end
196
+
197
+ ### Using the CVS log for the given <tt>file</tt> attempt to guess what the
198
+ ### next release version might be. This only works if releases are tagged
199
+ ### with tags like 'RELEASE_x_y'.
200
+ def extractNextVersionFromTags( file )
201
+ message "Attempting to extract next release version from CVS tags for #{file}...\n"
202
+ raise RuntimeError, "No such file '#{file}'" unless File.exists?( file )
203
+ cvsPath = findProgram( 'cvs' ) or
204
+ raise RuntimeError, "Cannot find the 'cvs' program. Aborting."
205
+
206
+ output = %x{#{cvsPath} log #{file}}
207
+ release = [ 0, 0 ]
208
+ output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match|
209
+ if $1.to_i > release[0] || $2.to_i > release[1]
210
+ release = [ $1.to_i, $2.to_i ]
211
+ replaceMessage( "Found %d.%02d...\n" % release )
212
+ end
213
+ }
214
+
215
+ if release[1] >= 99
216
+ release[0] += 1
217
+ release[1] = 1
218
+ else
219
+ release[1] += 1
220
+ end
221
+
222
+ return "%d.%02d" % release
223
+ end
224
+
225
+ ### Extract the project name (CVS Repository name) for the given directory.
226
+ def extractProjectName
227
+ File.open( "CVS/Repository", "r").readline.chomp
228
+ end
229
+
230
+ ### Read the specified <tt>manifestFile</tt>, which is a text file
231
+ ### describing which files to package up for a distribution. The manifest
232
+ ### should consist of one or more lines, each containing one filename or
233
+ ### shell glob pattern.
234
+ def readManifest( manifestFile="MANIFEST" )
235
+ message "Building manifest..."
236
+ raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
237
+
238
+ manifest = IO::readlines( manifestFile ).collect {|line|
239
+ line.chomp
240
+ }.select {|line|
241
+ line !~ /^(\s*(#.*)?)?$/
242
+ }
243
+
244
+ filelist = []
245
+ for pat in manifest
246
+ $stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE
247
+ filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
248
+ end
249
+
250
+ message "found #{filelist.length} files.\n"
251
+ return filelist
252
+ end
253
+
254
+ ### Given a <tt>filelist</tt> like that returned by #readManifest, remove
255
+ ### the entries therein which match the Regexp objects in the given
256
+ ### <tt>antimanifest</tt> and return the resultant Array.
257
+ def vetManifest( filelist, antimanifest=ANITMANIFEST )
258
+ origLength = filelist.length
259
+ message "Vetting manifest..."
260
+
261
+ for regex in antimanifest
262
+ if $VERBOSE
263
+ message "\n\tPattern /#{regex.source}/ removed: " +
264
+ filelist.find_all {|file| regex.match(file)}.join(', ')
265
+ end
266
+ filelist.delete_if {|file| regex.match(file)}
267
+ end
268
+
269
+ message "removed #{origLength - filelist.length} files from the list.\n"
270
+ return filelist
271
+ end
272
+
273
+ ### Combine a call to #readManifest with one to #vetManifest.
274
+ def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
275
+ vetManifest( readManifest(manifestFile), antimanifest )
276
+ end
277
+
278
+ ### Given a documentation <tt>catalogFile</tt>, which is in the same format
279
+ ### as that described by #readManifest, read and expand it, and then return
280
+ ### a list of those files which appear to have RDoc documentation in
281
+ ### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
282
+ ### file is used instead.
283
+ def findRdocableFiles( catalogFile="docs/CATALOG" )
284
+ startlist = []
285
+ if File.exists? catalogFile
286
+ message "Using CATALOG file (%s).\n" % catalogFile
287
+ startlist = getVettedManifest( catalogFile )
288
+ else
289
+ message "Using default MANIFEST\n"
290
+ startlist = getVettedManifest()
291
+ end
292
+
293
+ message "Looking for RDoc comments in:\n" if $VERBOSE
294
+ startlist.select {|fn|
295
+ message " #{fn}: " if $VERBOSE
296
+ found = false
297
+ File::open( fn, "r" ) {|fh|
298
+ fh.each {|line|
299
+ if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
300
+ found = true
301
+ break
302
+ end
303
+ }
304
+ }
305
+
306
+ message( (found ? "yes" : "no") + "\n" ) if $VERBOSE
307
+ found
308
+ }
309
+ end
310
+
311
+ ### Open a file and filter each of its lines through the given block a
312
+ ### <tt>line</tt> at a time. The return value of the block is used as the
313
+ ### new line, or omitted if the block returns <tt>nil</tt> or
314
+ ### <tt>false</tt>.
315
+ def editInPlace( file ) # :yields: line
316
+ raise "No block specified for editing operation" unless block_given?
317
+
318
+ tempName = "#{file}.#{$$}"
319
+ File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
320
+ File::unlink( tempName )
321
+ File::open( file, File::RDONLY ) {|fh|
322
+ fh.each {|line|
323
+ newline = yield( line ) or next
324
+ tempfile.print( newline )
325
+ }
326
+ }
327
+
328
+ tempfile.seek(0)
329
+
330
+ File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile|
331
+ newfile.print( tempfile.read )
332
+ }
333
+ }
334
+ end
335
+
336
+ ### Execute the specified shell <tt>command</tt>, read the results, and
337
+ ### return them. Like a %x{} that returns an Array instead of a String.
338
+ def shellCommand( *command )
339
+ raise "Empty command" if command.empty?
340
+
341
+ cmdpipe = IO::popen( command.join(' '), 'r' )
342
+ return cmdpipe.readlines
343
+ end
344
+
345
+ end