test-unit-mock 0.30

Sign up to get free protection for your applications and to get access to all the features.
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