Ruby-MemCache 0.0.1

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.
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # Unit test for the MemCache class
4
+ # $Id: require.tests.rb 12 2004-10-03 21:04:34Z ged $
5
+ #
6
+ # Copyright (c) 2004 RubyCrafters, LLC. Most rights reserved.
7
+ #
8
+ # This work is licensed under the Creative Commons Attribution-ShareAlike
9
+ # License. To view a copy of this license, visit
10
+ # http://creativecommons.org/licenses/by-sa/1.0/ or send a letter to Creative
11
+ # Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
12
+ #
13
+ #
14
+
15
+ unless defined? Arrow::TestCase
16
+ testsdir = File::dirname( File::expand_path(__FILE__) )
17
+ basedir = File::dirname( testsdir )
18
+ $LOAD_PATH.unshift "#{basedir}/lib" unless
19
+ $LOAD_PATH.include?( "#{basedir}/lib" )
20
+ $LOAD_PATH.unshift "#{basedir}/tests" unless
21
+ $LOAD_PATH.include?( "#{basedir}/tests" )
22
+
23
+ require 'mctestcase'
24
+ end
25
+
26
+
27
+ ### Collection of tests for the MemCache class.
28
+ class RequireTestCase < MemCache::TestCase
29
+
30
+ ### Instance test
31
+ def test_00_Require
32
+ assert_nothing_raised { require 'memcache' }
33
+ assert_kind_of Class, MemCache
34
+ end
35
+
36
+ end
37
+
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # Unit test for Memcache stats functions
4
+ # $Id: stats.tests.rb 22 2004-10-05 05:44:36Z ged $
5
+ #
6
+ # Copyright (c) 2004 RubyCrafters, LLC. Most rights reserved.
7
+ #
8
+ # This work is licensed under the Creative Commons Attribution-ShareAlike
9
+ # License. To view a copy of this license, visit
10
+ # http://creativecommons.org/licenses/by-sa/1.0/ or send a letter to Creative
11
+ # Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
12
+ #
13
+ #
14
+
15
+ unless defined? MemCache::TestCase
16
+ testsdir = File::dirname( File::expand_path(__FILE__) )
17
+ basedir = File::dirname( testsdir )
18
+ $LOAD_PATH.unshift "#{basedir}/lib" unless
19
+ $LOAD_PATH.include?( "#{basedir}/lib" )
20
+ $LOAD_PATH.unshift "#{basedir}/tests" unless
21
+ $LOAD_PATH.include?( "#{basedir}/tests" )
22
+
23
+ require 'mctestcase'
24
+ end
25
+
26
+
27
+ ### Collection of tests for the Memcache class.
28
+ class MemCache::StatsTestCase < MemCache::TestCase
29
+
30
+ ### Set up memcache instance or skip
31
+ def setup
32
+ if @config
33
+ server = [ @config[:server], @config[:port] ].join(":")
34
+ @memcache = MemCache::new( server )
35
+ @memcache.clear
36
+ @memcache.debug = $DEBUG
37
+ else
38
+ skip( "No memcached to test against." )
39
+ end
40
+
41
+ super
42
+ end
43
+
44
+
45
+ #################################################################
46
+ ### T E S T S
47
+ #################################################################
48
+
49
+ def test_00_client_stats
50
+ printTestHeader "Stats: Client stats method"
51
+ rval = nil
52
+
53
+ # Generate some stats to test
54
+ @memcache[ :foo ] = 1
55
+ @memcache[ :foo ]
56
+ @memcache[ :foo, :bar ] = 47, "Mr Roboto"
57
+
58
+ assert_respond_to @memcache, :stats
59
+ assert_nothing_raised { rval = @memcache.stats }
60
+ assert_instance_of Hash, rval
61
+ end
62
+
63
+
64
+ # :TODO: Most of these methods should really be tested with multiple
65
+ # servers, but that's kind of a pain in the arse for the user to set
66
+ # up. Once I have a good enough mock object perhaps I'll add some more
67
+ # tests.
68
+
69
+
70
+ def test_50_server_stats
71
+ printTestHeader "Stats: Server stats method"
72
+ rval = nil
73
+
74
+ assert_respond_to @memcache, :server_stats
75
+
76
+ # Without servers specified
77
+ assert_nothing_raised {
78
+ rval = @memcache.server_stats
79
+ }
80
+ assert_instance_of Hash, rval
81
+
82
+ # With servers specified
83
+ assert_nothing_raised {
84
+ rval = @memcache.server_stats( @memcache.servers )
85
+ }
86
+ assert_instance_of Hash, rval
87
+ end
88
+
89
+ def test_55_server_map_stats
90
+ printTestHeader "Stats: Server stats maps method"
91
+ rval = nil
92
+
93
+ assert_respond_to @memcache, :server_map_stats
94
+
95
+ # 'stat maps' doesn't work on MacOS X (no /proc/self/maps) and hangs on
96
+ # the Linux boxes I've tested it on, so this is commented out until it
97
+ # works somewhere.
98
+
99
+ ## Without servers specified
100
+ #assert_nothing_raised {
101
+ # rval = @memcache.server_map_stats
102
+ #}
103
+ #assert_instance_of Hash, rval
104
+ #
105
+ ## With servers specified
106
+ #assert_nothing_raised {
107
+ # rval = @memcache.server_map_stats( @memcache.servers )
108
+ #}
109
+ #assert_instance_of Hash, rval
110
+ end
111
+
112
+ def test_60_server_malloc_stats
113
+ printTestHeader "Stats: Server malloc stats method"
114
+ rval = nil
115
+
116
+ assert_respond_to @memcache, :server_malloc_stats
117
+
118
+ # Without servers specified
119
+ assert_nothing_raised {
120
+ rval = @memcache.server_malloc_stats
121
+ }
122
+ assert_instance_of Hash, rval
123
+
124
+ # With servers specified
125
+ assert_nothing_raised {
126
+ rval = @memcache.server_malloc_stats( @memcache.servers )
127
+ }
128
+ assert_instance_of Hash, rval
129
+ end
130
+
131
+ def test_65_server_slab_stats
132
+ printTestHeader "Stats: Server slab stats method"
133
+ rval = nil
134
+
135
+ assert_respond_to @memcache, :server_slab_stats
136
+
137
+ # Without servers specified
138
+ assert_nothing_raised {
139
+ rval = @memcache.server_slab_stats
140
+ }
141
+ assert_instance_of Hash, rval
142
+
143
+ # With servers specified
144
+ assert_nothing_raised {
145
+ rval = @memcache.server_slab_stats( @memcache.servers )
146
+ }
147
+ assert_instance_of Hash, rval
148
+ end
149
+
150
+ def test_70_server_item_stats
151
+ printTestHeader "Stats: Server item stats method"
152
+ rval = nil
153
+
154
+ assert_respond_to @memcache, :server_item_stats
155
+
156
+ # Without servers specified
157
+ assert_nothing_raised {
158
+ rval = @memcache.server_item_stats
159
+ }
160
+ assert_instance_of Hash, rval
161
+
162
+ # With servers specified
163
+ assert_nothing_raised {
164
+ rval = @memcache.server_item_stats( @memcache.servers )
165
+ }
166
+ assert_instance_of Hash, rval
167
+ end
168
+
169
+ def test_65_server_size_stats
170
+ printTestHeader "Stats: Server size stats method"
171
+ rval = nil
172
+
173
+ assert_respond_to @memcache, :server_size_stats
174
+
175
+ # Without servers specified
176
+ assert_nothing_raised {
177
+ rval = @memcache.server_size_stats
178
+ }
179
+ assert_instance_of Hash, rval
180
+
181
+ # With servers specified
182
+ assert_nothing_raised {
183
+ rval = @memcache.server_size_stats( @memcache.servers )
184
+ }
185
+ assert_instance_of Hash, rval
186
+ end
187
+
188
+ def test_99_server_reset_stats
189
+ printTestHeader "Stats: Server reset stats method"
190
+ rval = nil
191
+
192
+ assert_respond_to @memcache, :server_reset_stats
193
+
194
+ # Without servers specified
195
+ assert_nothing_raised {
196
+ rval = @memcache.server_reset_stats
197
+ }
198
+ assert_instance_of Hash, rval
199
+ rval.each_pair do |svr,reply|
200
+ assert_equal true, reply
201
+ end
202
+
203
+ # With servers specified
204
+ assert_nothing_raised {
205
+ rval = @memcache.server_reset_stats( @memcache.servers )
206
+ }
207
+ assert_instance_of Hash, rval
208
+ rval.each_pair do |svr,reply|
209
+ assert_equal true, reply
210
+ end
211
+ end
212
+
213
+
214
+
215
+ end
216
+
@@ -0,0 +1,672 @@
1
+ #
2
+ # Install/distribution utility functions
3
+ # $Id: utils.rb 15 2004-10-03 21:18:58Z ged $
4
+ #
5
+ # Copyright (c) 2001-2004, 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
+ BEGIN {
13
+ require 'rbconfig'
14
+ require 'uri'
15
+ require 'find'
16
+
17
+ begin
18
+ require 'readline'
19
+ include Readline
20
+ rescue LoadError => e
21
+ $stderr.puts "Faking readline..."
22
+ def readline( prompt )
23
+ $stderr.print prompt.chomp
24
+ return $stdin.gets.chomp
25
+ end
26
+ end
27
+ }
28
+
29
+
30
+ module UtilityFunctions
31
+ include Config
32
+
33
+ # The list of regexen that eliminate files from the MANIFEST
34
+ ANTIMANIFEST = [
35
+ /makedist\.rb/,
36
+ /\bCVS\b/,
37
+ /~$/,
38
+ /^#/,
39
+ %r{docs/html},
40
+ %r{docs/man},
41
+ /\bTEMPLATE\.\w+\.tpl\b/,
42
+ /\.cvsignore/,
43
+ /\.s?o$/,
44
+ ]
45
+
46
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
47
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
48
+ AnsiAttributes = {
49
+ 'clear' => 0,
50
+ 'reset' => 0,
51
+ 'bold' => 1,
52
+ 'dark' => 2,
53
+ 'underline' => 4,
54
+ 'underscore' => 4,
55
+ 'blink' => 5,
56
+ 'reverse' => 7,
57
+ 'concealed' => 8,
58
+
59
+ 'black' => 30, 'on_black' => 40,
60
+ 'red' => 31, 'on_red' => 41,
61
+ 'green' => 32, 'on_green' => 42,
62
+ 'yellow' => 33, 'on_yellow' => 43,
63
+ 'blue' => 34, 'on_blue' => 44,
64
+ 'magenta' => 35, 'on_magenta' => 45,
65
+ 'cyan' => 36, 'on_cyan' => 46,
66
+ 'white' => 37, 'on_white' => 47
67
+ }
68
+
69
+ ErasePreviousLine = "\033[A\033[K"
70
+
71
+ ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' )
72
+ #
73
+ # Distribution Manifest
74
+ # Created: #{Time::now.to_s}
75
+ #
76
+
77
+ EOF
78
+
79
+ ###############
80
+ module_function
81
+ ###############
82
+
83
+ # Create a string that contains the ANSI codes specified and return it
84
+ def ansiCode( *attributes )
85
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
86
+ attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
87
+ if attr.empty?
88
+ return ''
89
+ else
90
+ return "\e[%sm" % attr
91
+ end
92
+ end
93
+
94
+ # Test for the presence of the specified <tt>library</tt>, and output a
95
+ # message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
96
+ # is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
97
+ def testForLibrary( library, nicename=nil, progress=false )
98
+ nicename ||= library
99
+ message( "Testing for the #{nicename} library..." ) if progress
100
+ if $LOAD_PATH.detect {|dir|
101
+ File.exists?(File.join(dir,"#{library}.rb")) ||
102
+ File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}"))
103
+ }
104
+ message( "found.\n" ) if progress
105
+ return true
106
+ else
107
+ message( "not found.\n" ) if progress
108
+ return false
109
+ end
110
+ end
111
+
112
+ # Test for the presence of the specified <tt>library</tt>, and output a
113
+ # message describing the problem using <tt>nicename</tt>. If
114
+ # <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
115
+ # to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
116
+ # specified, they are also use to build a message describing how to find the
117
+ # required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
118
+ # will cause the program to abort.
119
+ def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
120
+ nicename ||= library
121
+ unless testForLibrary( library, nicename )
122
+ msgs = [ "You are missing the required #{nicename} library.\n" ]
123
+ msgs << "RAA: #{raaUrl}\n" if raaUrl
124
+ msgs << "Download: #{downloadUrl}\n" if downloadUrl
125
+ if fatal
126
+ abort msgs.join('')
127
+ else
128
+ errorMessage msgs.join('')
129
+ end
130
+ end
131
+ return true
132
+ end
133
+
134
+ ### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
135
+ ### blue).
136
+ def header( msg )
137
+ msg.chomp!
138
+ $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
139
+ $stderr.flush
140
+ end
141
+
142
+ ### Output <tt>msg</tt> to STDERR and flush it.
143
+ def message( *msgs )
144
+ $stderr.print( msgs.join("\n") )
145
+ $stderr.flush
146
+ end
147
+
148
+ ### Output +msg+ to STDERR and flush it if $VERBOSE is true.
149
+ def verboseMsg( msg )
150
+ msg.chomp!
151
+ message( msg + "\n" ) if $VERBOSE
152
+ end
153
+
154
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
155
+ ### (white on red).
156
+ def errorMessage( msg )
157
+ message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
158
+ end
159
+
160
+ ### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
161
+ ### (yellow on blue).
162
+ def debugMsg( msg )
163
+ return unless $DEBUG
164
+ msg.chomp!
165
+ $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
166
+ $stderr.flush
167
+ end
168
+
169
+ ### Erase the previous line (if supported by your terminal) and output the
170
+ ### specified <tt>msg</tt> instead.
171
+ def replaceMessage( msg )
172
+ print ErasePreviousLine
173
+ message( msg )
174
+ end
175
+
176
+ ### Output a divider made up of <tt>length</tt> hyphen characters.
177
+ def divider( length=75 )
178
+ puts "\r" + ("-" * length )
179
+ end
180
+ alias :writeLine :divider
181
+
182
+
183
+ ### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
184
+ ### status of 1.
185
+ def abort( msg )
186
+ print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
187
+ Kernel.exit!( 1 )
188
+ end
189
+
190
+
191
+ ### Output the specified <tt>promptString</tt> as a prompt (in green) and
192
+ ### return the user's input with leading and trailing spaces removed. If a
193
+ ### test is provided, the prompt will repeat until the test returns true.
194
+ ### An optional failure message can also be passed in.
195
+ def prompt( promptString, failure_msg="Try again." ) # :yields: response
196
+ promptString.chomp!
197
+ response = nil
198
+
199
+ begin
200
+ response = readline( ansiCode('bold', 'green') +
201
+ "#{promptString}: " + ansiCode('reset') ).strip
202
+ if block_given? && ! yield( response )
203
+ errorMessage( failure_msg + "\n\n" )
204
+ response = nil
205
+ end
206
+ end until response
207
+
208
+ return response
209
+ end
210
+
211
+
212
+ ### Prompt the user with the given <tt>promptString</tt> via #prompt,
213
+ ### substituting the given <tt>default</tt> if the user doesn't input
214
+ ### anything. If a test is provided, the prompt will repeat until the test
215
+ ### returns true. An optional failure message can also be passed in.
216
+ def promptWithDefault( promptString, default, failure_msg="Try again." )
217
+ response = nil
218
+
219
+ begin
220
+ response = prompt( "%s [%s]" % [ promptString, default ] )
221
+ response = default if response.empty?
222
+
223
+ if block_given? && ! yield( response )
224
+ errorMessage( failure_msg + "\n\n" )
225
+ response = nil
226
+ end
227
+ end until response
228
+
229
+ return response
230
+ end
231
+
232
+
233
+ $programs = {}
234
+
235
+ ### Search for the program specified by the given <tt>progname</tt> in the
236
+ ### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
237
+ ### no such program is in the path.
238
+ def findProgram( progname )
239
+ unless $programs.key?( progname )
240
+ ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
241
+ file = File.join( d, progname )
242
+ if File.executable?( file )
243
+ $programs[ progname ] = file
244
+ break
245
+ end
246
+ }
247
+ end
248
+
249
+ return $programs[ progname ]
250
+ end
251
+
252
+
253
+ ### Search for the release version for the project in the specified
254
+ ### +directory+.
255
+ def extractVersion( directory='.' )
256
+ release = nil
257
+
258
+ Dir::chdir( directory ) do
259
+ if File::directory?( "CVS" )
260
+ verboseMsg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." )
261
+
262
+ if (( cvs = findProgram('cvs') ))
263
+ revs = []
264
+ output = %x{cvs log}
265
+ output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match|
266
+ rev = $1.split(/_/).collect {|s| Integer(s) rescue 0}
267
+ verboseMsg( "Found %s...\n" % rev.join('.') )
268
+ revs << rev
269
+ }
270
+
271
+ release = revs.sort.last
272
+ end
273
+
274
+ elsif File::directory?( '.svn' )
275
+ verboseMsg( "Project is versioned via Subversion" )
276
+
277
+ if (( svn = findProgram('svn') ))
278
+ output = %x{svn pg project-version}.chomp
279
+ unless output.empty?
280
+ verboseMsg( "Using 'project-version' property: %p" % output )
281
+ release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0}
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ return release
288
+ end
289
+
290
+
291
+ ### Find the current release version for the project in the specified
292
+ ### +directory+ and return its successor.
293
+ def extractNextVersion( directory='.' )
294
+ version = extractVersion( directory ) || [0,0,0]
295
+ version.compact!
296
+ version[-1] += 1
297
+
298
+ return version
299
+ end
300
+
301
+
302
+ # Pattern for extracting the name of the project from a Subversion URL
303
+ SVNUrlPath = %r{
304
+ .*/ # Skip all but the last bit
305
+ (\w+) # $1 = project name
306
+ / # Followed by / +
307
+ (?:
308
+ trunk | # 'trunk'
309
+ (
310
+ branches | # ...or branches/branch-name
311
+ tags # ...or tags/tag-name
312
+ )/\w
313
+ )
314
+ $ # bound to the end
315
+ }ix
316
+
317
+ ### Extract the project name (CVS Repository name) for the given +directory+.
318
+ def extractProjectName( directory='.' )
319
+ name = nil
320
+
321
+ Dir::chdir( directory ) do
322
+
323
+ # CVS-controlled
324
+ if File::directory?( "CVS" )
325
+ verboseMsg( "Project is versioned via CVS. Using repository name." )
326
+ name = File.open( "CVS/Repository", "r").readline.chomp
327
+ name.sub!( %r{.*/}, '' )
328
+
329
+ # Subversion-controlled
330
+ elsif File::directory?( '.svn' )
331
+ verboseMsg( "Project is versioned via Subversion" )
332
+
333
+ # If the machine has the svn tool, try to get the project name
334
+ if (( svn = findProgram( 'svn' ) ))
335
+
336
+ # First try an explicit property
337
+ output = shellCommand( svn, 'pg', 'project-name' )
338
+ if !output.empty?
339
+ verboseMsg( "Using 'project-name' property: %p" % output )
340
+ name = output.first.chomp
341
+
342
+ # If that doesn't work, try to figure it out from the URL
343
+ elsif (( uri = getSvnUri() ))
344
+ name = uri.path.sub( SVNUrlPath ) { $1 }
345
+ end
346
+ end
347
+ end
348
+
349
+ # Fall back to guessing based on the directory name
350
+ unless name
351
+ name = File::basename(File::dirname( File::expand_path(__FILE__) ))
352
+ end
353
+ end
354
+
355
+ return name
356
+ end
357
+
358
+
359
+ ### Extract the Subversion URL from the specified directory and return it as
360
+ ### a URI object.
361
+ def getSvnUri( directory='.' )
362
+ uri = nil
363
+
364
+ Dir::chdir( directory ) do
365
+ output = %x{svn info}
366
+ debugMsg( "Using info: %p" % output )
367
+
368
+ if /^URL: \s* ( .* )/xi.match( output )
369
+ uri = URI::parse( $1 )
370
+ end
371
+ end
372
+
373
+ return uri
374
+ end
375
+
376
+
377
+ ### (Re)make a manifest file in the specified +path+.
378
+ def makeManifest( path="MANIFEST" )
379
+ if File::exists?( path )
380
+ reply = promptWithDefault( "Replace current '#{path}'? [yN]", "n" )
381
+ return false unless /^y/i.match( reply )
382
+
383
+ verboseMsg "Replacing manifest at '#{path}'"
384
+ else
385
+ verboseMsg "Creating new manifest at '#{path}'"
386
+ end
387
+
388
+ files = []
389
+ verboseMsg( "Finding files...\n" )
390
+ Find::find( Dir::pwd ) do |f|
391
+ Find::prune if File::directory?( f ) &&
392
+ /^\./.match( File::basename(f) )
393
+ verboseMsg( " found: #{f}\n" )
394
+ files << f.sub( %r{^#{Dir::pwd}/?}, '' )
395
+ end
396
+ files = vetManifest( files )
397
+
398
+ verboseMsg( "Writing new manifest to #{path}..." )
399
+ File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
400
+ ofh.puts( ManifestHeader )
401
+ ofh.puts( files )
402
+ end
403
+ verboseMsg( "done." )
404
+ end
405
+
406
+
407
+ ### Read the specified <tt>manifestFile</tt>, which is a text file
408
+ ### describing which files to package up for a distribution. The manifest
409
+ ### should consist of one or more lines, each containing one filename or
410
+ ### shell glob pattern.
411
+ def readManifest( manifestFile="MANIFEST" )
412
+ verboseMsg "Building manifest..."
413
+ raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
414
+
415
+ manifest = IO::readlines( manifestFile ).collect {|line|
416
+ line.chomp
417
+ }.select {|line|
418
+ line !~ /^(\s*(#.*)?)?$/
419
+ }
420
+
421
+ filelist = []
422
+ for pat in manifest
423
+ verboseMsg "Adding files that match '#{pat}' to the file list"
424
+ filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
425
+ end
426
+
427
+ verboseMsg "found #{filelist.length} files.\n"
428
+ return filelist
429
+ end
430
+
431
+
432
+ ### Given a <tt>filelist</tt> like that returned by #readManifest, remove
433
+ ### the entries therein which match the Regexp objects in the given
434
+ ### <tt>antimanifest</tt> and return the resultant Array.
435
+ def vetManifest( filelist, antimanifest=ANTIMANIFEST )
436
+ origLength = filelist.length
437
+ verboseMsg "Vetting manifest..."
438
+
439
+ for regex in antimanifest
440
+ verboseMsg "\n\tPattern /#{regex.source}/ removed: " +
441
+ filelist.find_all {|file| regex.match(file)}.join(', ')
442
+ filelist.delete_if {|file| regex.match(file)}
443
+ end
444
+
445
+ verboseMsg "removed #{origLength - filelist.length} files from the list.\n"
446
+ return filelist
447
+ end
448
+
449
+
450
+ ### Combine a call to #readManifest with one to #vetManifest.
451
+ def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
452
+ vetManifest( readManifest(manifestFile), antimanifest )
453
+ end
454
+
455
+
456
+ ### Given a documentation <tt>catalogFile</tt>, extract the title, if
457
+ ### available, and return it. Otherwise generate a title from the name of
458
+ ### the CVS module.
459
+ def findRdocTitle( catalogFile="docs/CATALOG" )
460
+
461
+ # Try extracting it from the CATALOG file from a line that looks like:
462
+ # Title: Foo Bar Module
463
+ title = findCatalogKeyword( 'title', catalogFile )
464
+
465
+ # If that doesn't work for some reason, use the name of the project.
466
+ title = extractProjectName()
467
+
468
+ return title
469
+ end
470
+
471
+
472
+ ### Given a documentation <tt>catalogFile</tt>, extract the name of the file
473
+ ### to use as the initally displayed page. If extraction fails, the
474
+ ### +default+ will be used if it exists. Returns +nil+ if there is no main
475
+ ### file to be found.
476
+ def findRdocMain( catalogFile="docs/CATALOG", default="README" )
477
+
478
+ # Try extracting it from the CATALOG file from a line that looks like:
479
+ # Main: Foo Bar Module
480
+ main = findCatalogKeyword( 'main', catalogFile )
481
+
482
+ # Try to make some educated guesses if that doesn't work
483
+ if main.nil?
484
+ basedir = File::dirname( __FILE__ )
485
+ basedir = File::dirname( basedir ) if /docs$/ =~ basedir
486
+
487
+ if File::exists?( File::join(basedir, default) )
488
+ main = default
489
+ end
490
+ end
491
+
492
+ return main
493
+ end
494
+
495
+
496
+ ### Given a documentation <tt>catalogFile</tt>, extract an upload URL for
497
+ ### RDoc.
498
+ def findRdocUpload( catalogFile="docs/CATALOG" )
499
+ findCatalogKeyword( 'upload', catalogFile )
500
+ end
501
+
502
+
503
+ ### Given a documentation <tt>catalogFile</tt>, extract a CVS web frontend
504
+ ### URL for RDoc.
505
+ def findRdocCvsURL( catalogFile="docs/CATALOG" )
506
+ findCatalogKeyword( 'webcvs', catalogFile )
507
+ end
508
+
509
+
510
+ ### Given a documentation <tt>catalogFile</tt>, try extracting the given
511
+ ### +keyword+'s value from it. Keywords are lines that look like:
512
+ ### # <keyword>: <value>
513
+ ### Returns +nil+ if the catalog file was unreadable or didn't contain the
514
+ ### specified +keyword+.
515
+ def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" )
516
+ val = nil
517
+
518
+ if File::exists? catalogFile
519
+ verboseMsg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
520
+ File::foreach( catalogFile ) {|line|
521
+ debugMsg( "Examining line #{line.inspect}..." )
522
+ val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line
523
+ }
524
+ end
525
+
526
+ return val
527
+ end
528
+
529
+
530
+ ### Given a documentation <tt>catalogFile</tt>, which is in the same format
531
+ ### as that described by #readManifest, read and expand it, and then return
532
+ ### a list of those files which appear to have RDoc documentation in
533
+ ### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
534
+ ### file is used instead.
535
+ def findRdocableFiles( catalogFile="docs/CATALOG" )
536
+ startlist = []
537
+ if File.exists? catalogFile
538
+ verboseMsg "Using CATALOG file (%s).\n" % catalogFile
539
+ startlist = getVettedManifest( catalogFile )
540
+ else
541
+ verboseMsg "Using default MANIFEST\n"
542
+ startlist = getVettedManifest()
543
+ end
544
+
545
+ verboseMsg "Looking for RDoc comments in:\n"
546
+ startlist.select {|fn|
547
+ verboseMsg " #{fn}: "
548
+ found = false
549
+ File::open( fn, "r" ) {|fh|
550
+ fh.each {|line|
551
+ if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
552
+ found = true
553
+ break
554
+ end
555
+ }
556
+ }
557
+
558
+ verboseMsg( (found ? "yes" : "no") + "\n" )
559
+ found
560
+ }
561
+ end
562
+
563
+ ### Open a file and filter each of its lines through the given block a
564
+ ### <tt>line</tt> at a time. The return value of the block is used as the
565
+ ### new line, or omitted if the block returns <tt>nil</tt> or
566
+ ### <tt>false</tt>.
567
+ def editInPlace( file, testMode=false ) # :yields: line
568
+ raise "No block specified for editing operation" unless block_given?
569
+
570
+ tempName = "#{file}.#{$$}"
571
+ File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
572
+ File::open( file, File::RDONLY ) {|fh|
573
+ fh.each {|line|
574
+ newline = yield( line ) or next
575
+ tempfile.print( newline )
576
+ $deferr.puts "%p -> %p" % [ line, newline ] if
577
+ line != newline
578
+ }
579
+ }
580
+ }
581
+
582
+ if testMode
583
+ File::unlink( tempName )
584
+ else
585
+ File::rename( tempName, file )
586
+ end
587
+ end
588
+
589
+ ### Execute the specified shell <tt>command</tt>, read the results, and
590
+ ### return them. Like a %x{} that returns an Array instead of a String.
591
+ def shellCommand( *command )
592
+ raise "Empty command" if command.empty?
593
+
594
+ cmdpipe = IO::popen( command.join(' '), 'r' )
595
+ return cmdpipe.readlines
596
+ end
597
+
598
+ ### Execute a block with $VERBOSE set to +false+, restoring it to its
599
+ ### previous value before returning.
600
+ def verboseOff
601
+ raise LocalJumpError, "No block given" unless block_given?
602
+
603
+ thrcrit = Thread.critical
604
+ oldverbose = $VERBOSE
605
+ begin
606
+ Thread.critical = true
607
+ $VERBOSE = false
608
+ yield
609
+ ensure
610
+ $VERBOSE = oldverbose
611
+ Thread.critical = false
612
+ end
613
+ end
614
+
615
+
616
+ ### Try the specified code block, printing the given
617
+ def try( msg, bind=nil )
618
+ result = nil
619
+ if msg =~ /^to\s/
620
+ message = "Trying #{msg}..."
621
+ else
622
+ message = msg
623
+ end
624
+
625
+ begin
626
+ rval = nil
627
+ if block_given?
628
+ rval = yield
629
+ else
630
+ file, line = caller(1)[0].split(/:/,2)
631
+ rval = eval( msg, bind, file, line.to_i )
632
+ end
633
+
634
+ result = rval.inspect
635
+ rescue Exception => err
636
+ if err.backtrace
637
+ nicetrace = err.backtrace.delete_if {|frame|
638
+ /in `(try|eval)'/ =~ frame
639
+ }.join("\n\t")
640
+ else
641
+ nicetrace = "Exception had no backtrace"
642
+ end
643
+
644
+ result = err.message + "\n\t" + nicetrace
645
+ ensure
646
+ puts result
647
+ end
648
+ end
649
+ end
650
+
651
+
652
+ if __FILE__ == $0
653
+ # $DEBUG = true
654
+ include UtilityFunctions
655
+
656
+ projname = extractProjectName()
657
+ header "Project: #{projname}"
658
+
659
+ ver = extractVersion() || [0,0,1]
660
+ puts "Version: %s\n" % ver.join('.')
661
+
662
+ if File::directory?( "docs" )
663
+ puts "Rdoc:",
664
+ " Title: " + findRdocTitle(),
665
+ " Main: " + findRdocMain(),
666
+ " Upload: " + findRdocUpload(),
667
+ " SCCS URL: " + findRdocCvsURL()
668
+ end
669
+
670
+ puts "Manifest:",
671
+ " " + getVettedManifest().join("\n ")
672
+ end