io-reactor 0.05
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/CATALOG +10 -0
- data/ChangeLog +303 -0
- data/README +77 -0
- data/examples/chatserver.rb +347 -0
- data/install.rb +85 -0
- data/io-reactor.gemspec +21 -0
- data/lib/io/reactor.rb +317 -0
- data/makedocs.rb +79 -0
- data/test.rb +212 -0
- data/utils.rb +484 -0
- metadata +53 -0
data/test.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# = test.rb
|
3
|
+
#
|
4
|
+
# Test suite for Ruby-Poll
|
5
|
+
#
|
6
|
+
# == Author
|
7
|
+
#
|
8
|
+
# Michael Granger <ged@FaerieMUD.org>
|
9
|
+
#
|
10
|
+
# Copyright (c) 2002, 2003 The FaerieMUD Consortium. All rights reserved.
|
11
|
+
#
|
12
|
+
# This program is free software. You may use, modify, and/or redistribute this
|
13
|
+
# software under the same terms as Ruby itself.
|
14
|
+
#
|
15
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
16
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
17
|
+
# FOR A PARTICULAR PURPOSE.
|
18
|
+
#
|
19
|
+
# == Version
|
20
|
+
#
|
21
|
+
# $Id: test.rb,v 1.8 2003/08/04 23:57:07 deveiant Exp $
|
22
|
+
#
|
23
|
+
#
|
24
|
+
|
25
|
+
$:.unshift "lib", "tests"
|
26
|
+
|
27
|
+
require 'test/unit'
|
28
|
+
require 'io/reactor'
|
29
|
+
require 'socket'
|
30
|
+
|
31
|
+
TMPFILE = "testfile.#{$$}"
|
32
|
+
HOST = 'localhost'
|
33
|
+
PORT = 5656
|
34
|
+
|
35
|
+
$stderr.sync = $stdout.sync = true
|
36
|
+
|
37
|
+
### Reactor test case
|
38
|
+
class IOReactorTestCase < Test::Unit::TestCase
|
39
|
+
|
40
|
+
# Setup method
|
41
|
+
def setup
|
42
|
+
@reactor = IO::Reactor::new
|
43
|
+
@tmpfile = File::open( TMPFILE, "w" )
|
44
|
+
File::unlink TMPFILE
|
45
|
+
@sock = TCPServer::new( HOST, PORT )
|
46
|
+
end
|
47
|
+
alias_method :set_up, :setup
|
48
|
+
|
49
|
+
# Teardown method
|
50
|
+
def teardown
|
51
|
+
@reactor = nil
|
52
|
+
@tmpfile.close
|
53
|
+
@sock.close
|
54
|
+
end
|
55
|
+
alias_method :tear_down, :teardown
|
56
|
+
|
57
|
+
|
58
|
+
# Test to make sure require worked
|
59
|
+
def test_00_Requires
|
60
|
+
assert_instance_of Class, IO::Reactor
|
61
|
+
assert_instance_of IO::Reactor, @reactor
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Test set and reset with an IO
|
66
|
+
def test_10_RegisterIO
|
67
|
+
assert_nothing_raised { @reactor.register $stdout, :write }
|
68
|
+
assert_nothing_raised { @reactor.add $stdout, :write }
|
69
|
+
assert @reactor.registered?( $stdout )
|
70
|
+
assert_equal [:write], @reactor.handles[ $stdout ][:events]
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Test set and reset with a File
|
75
|
+
def test_11_RegisterFilehandle
|
76
|
+
assert_nothing_raised { @reactor.register @tmpfile, :write }
|
77
|
+
assert_nothing_raised { @reactor.add @tmpfile, :write }
|
78
|
+
assert @reactor.registered?( @tmpfile )
|
79
|
+
assert_equal [:write], @reactor.handles[ @tmpfile ][:events]
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Test set and reset with a File
|
84
|
+
def test_12_RegisterSocket
|
85
|
+
assert_nothing_raised { @reactor.register @sock, :read, :write }
|
86
|
+
assert_nothing_raised { @reactor.add @sock, :read }
|
87
|
+
assert @reactor.registered?( @sock )
|
88
|
+
assert_equal [:read], @reactor.handles[ @sock ][:events]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Test registration with a callback as an inline block
|
93
|
+
def test_20_RegisterWithBlock
|
94
|
+
assert_nothing_raised {
|
95
|
+
@reactor.register($stdout, :write) {|io,eventMask|
|
96
|
+
$stderr.puts "Got an output event for STDOUT"
|
97
|
+
}
|
98
|
+
}
|
99
|
+
assert @reactor.handles.key?( $stdout ),
|
100
|
+
"handles hash doesn't contain $stdout"
|
101
|
+
assert_equal [:write], @reactor.handles[ $stdout ][:events]
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# Test registration with a Proc argument
|
106
|
+
def test_21_RegisterWithProc
|
107
|
+
handlerProc = Proc::new {|io,eventMask|
|
108
|
+
$stderr.puts "Got an output event for STDOUT"
|
109
|
+
}
|
110
|
+
assert_nothing_raised {
|
111
|
+
@reactor.register( $stdout, :write, &handlerProc )
|
112
|
+
}
|
113
|
+
assert @reactor.handles.key?( $stdout ),
|
114
|
+
"handles hash doesn't contain $stdout"
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# Test registration with a Method argument
|
119
|
+
def test_22_RegisterWithMethod
|
120
|
+
assert_nothing_raised {
|
121
|
+
@reactor.register $stdout, :write, &$stderr.method( :puts )
|
122
|
+
}
|
123
|
+
assert @reactor.handles.key?( $stdout ),
|
124
|
+
"handles hash doesn't contain $stdout"
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Test registering with an argument
|
129
|
+
def test_23_RegisterWithArgs
|
130
|
+
assert_nothing_raised {
|
131
|
+
@reactor.register $stdout, :write, "foo", &$stderr.method( :puts )
|
132
|
+
}
|
133
|
+
assert @reactor.handles.key?( $stdout ),
|
134
|
+
"handles hash doesn't contain $stdout"
|
135
|
+
assert_equal ["foo"], @reactor.handles[$stdout][:args]
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Test the clear method
|
140
|
+
def test_30_Clear
|
141
|
+
# Make sure it's empty
|
142
|
+
assert_nothing_raised {
|
143
|
+
@reactor.clear
|
144
|
+
}
|
145
|
+
|
146
|
+
# Test it with one registered
|
147
|
+
assert_nothing_raised {
|
148
|
+
@reactor.register $stdout, :write, &$stdout.method( :puts )
|
149
|
+
@reactor.clear
|
150
|
+
}
|
151
|
+
assert ! @reactor.registered?( $stdout ),
|
152
|
+
"$stdout still registed with the poll handle after clear"
|
153
|
+
assert_equal 0, @reactor.handles.length
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Test the #poll method
|
158
|
+
def test_40_Poll
|
159
|
+
rv = nil
|
160
|
+
|
161
|
+
@reactor.register $stdout, :write
|
162
|
+
@reactor.register @tmpfile, :write
|
163
|
+
|
164
|
+
assert_nothing_raised { rv = @reactor.poll(0.1) }
|
165
|
+
assert_equal 2, rv
|
166
|
+
|
167
|
+
assert_nothing_raised { @reactor.pendingEvents.keys.include?($stdout) }
|
168
|
+
assert_nothing_raised { @reactor.pendingEvents.keys.include?(@tmpfile) }
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# Test #poll with a block default handler
|
173
|
+
def test_41_PollWithBlock
|
174
|
+
rv = nil
|
175
|
+
|
176
|
+
@reactor.register $stdout, :write
|
177
|
+
@reactor.register @tmpfile, :write
|
178
|
+
|
179
|
+
assert_nothing_raised {
|
180
|
+
rv = @reactor.poll( 15 ) {|io,event|
|
181
|
+
$stderr.puts "Default handler got #{io.inspect} with mask #{event}" if $VERBOSE
|
182
|
+
}
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
# Test polling with an argument
|
187
|
+
def test_42_PollWithArgs
|
188
|
+
setval = nil
|
189
|
+
testAry = %w{foo bar baz}
|
190
|
+
|
191
|
+
@reactor.register( $stdout, :write, *testAry )
|
192
|
+
assert_equal testAry, @reactor.handles[$stdout][:args]
|
193
|
+
|
194
|
+
assert_nothing_raised {
|
195
|
+
@reactor.poll( 15 ) {|io,ev,*args|
|
196
|
+
setval = args
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
assert_equal testAry, setval
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
end # class PollTestCase
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
|
data/utils.rb
ADDED
@@ -0,0 +1,484 @@
|
|
1
|
+
#
|
2
|
+
# Install/distribution utility functions
|
3
|
+
# $Id: utils.rb,v 1.5 2003/08/04 23:52:07 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
|
+
begin
|
26
|
+
require 'yaml'
|
27
|
+
$yaml = true
|
28
|
+
rescue LoadError => e
|
29
|
+
$stderr.puts "No YAML; try() will use .inspect instead."
|
30
|
+
$yaml = false
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
module UtilityFunctions
|
35
|
+
|
36
|
+
# The list of regexen that eliminate files from the MANIFEST
|
37
|
+
ANTIMANIFEST = [
|
38
|
+
/makedist\.rb/,
|
39
|
+
/\bCVS\b/,
|
40
|
+
/~$/,
|
41
|
+
/^#/,
|
42
|
+
%r{docs/html},
|
43
|
+
%r{docs/man},
|
44
|
+
/^TEMPLATE/,
|
45
|
+
/\.cvsignore/,
|
46
|
+
/\.s?o$/
|
47
|
+
]
|
48
|
+
|
49
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
50
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
|
51
|
+
AnsiAttributes = {
|
52
|
+
'clear' => 0,
|
53
|
+
'reset' => 0,
|
54
|
+
'bold' => 1,
|
55
|
+
'dark' => 2,
|
56
|
+
'underline' => 4,
|
57
|
+
'underscore' => 4,
|
58
|
+
'blink' => 5,
|
59
|
+
'reverse' => 7,
|
60
|
+
'concealed' => 8,
|
61
|
+
|
62
|
+
'black' => 30, 'on_black' => 40,
|
63
|
+
'red' => 31, 'on_red' => 41,
|
64
|
+
'green' => 32, 'on_green' => 42,
|
65
|
+
'yellow' => 33, 'on_yellow' => 43,
|
66
|
+
'blue' => 34, 'on_blue' => 44,
|
67
|
+
'magenta' => 35, 'on_magenta' => 45,
|
68
|
+
'cyan' => 36, 'on_cyan' => 46,
|
69
|
+
'white' => 37, 'on_white' => 47
|
70
|
+
}
|
71
|
+
|
72
|
+
ErasePreviousLine = "\033[A\033[K"
|
73
|
+
|
74
|
+
|
75
|
+
###############
|
76
|
+
module_function
|
77
|
+
###############
|
78
|
+
|
79
|
+
# Create a string that contains the ANSI codes specified and return it
|
80
|
+
def ansiCode( *attributes )
|
81
|
+
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux)/i =~ ENV['TERM']
|
82
|
+
attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
|
83
|
+
if attr.empty?
|
84
|
+
return ''
|
85
|
+
else
|
86
|
+
return "\e[%sm" % attr
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Test for the presence of the specified <tt>library</tt>, and output a
|
91
|
+
# message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
|
92
|
+
# is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
|
93
|
+
def testForLibrary( library, nicename=nil )
|
94
|
+
nicename ||= library
|
95
|
+
message( "Testing for the #{nicename} library..." )
|
96
|
+
if $:.detect {|dir| File.exists?(File.join(dir,"#{library}.rb")) || File.exists?(File.join(dir,"#{library}.so"))}
|
97
|
+
message( "found.\n" )
|
98
|
+
return true
|
99
|
+
else
|
100
|
+
message( "not found.\n" )
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Test for the presence of the specified <tt>library</tt>, and output a
|
106
|
+
# message describing the problem using <tt>nicename</tt>. If
|
107
|
+
# <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
|
108
|
+
# to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
|
109
|
+
# specified, they are also use to build a message describing how to find the
|
110
|
+
# required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
|
111
|
+
# will cause the program to abort.
|
112
|
+
def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
|
113
|
+
nicename ||= library
|
114
|
+
unless testForLibrary( library, nicename )
|
115
|
+
msgs = [ "You are missing the required #{nicename} library.\n" ]
|
116
|
+
msgs << "RAA: #{raaUrl}\n" if raaUrl
|
117
|
+
msgs << "Download: #{downloadUrl}\n" if downloadUrl
|
118
|
+
if fatal
|
119
|
+
abort msgs.join('')
|
120
|
+
else
|
121
|
+
errorMessage msgs.join('')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
return true
|
125
|
+
end
|
126
|
+
|
127
|
+
### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
|
128
|
+
### blue).
|
129
|
+
def header( msg )
|
130
|
+
msg.chomp!
|
131
|
+
$stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
|
132
|
+
$stderr.flush
|
133
|
+
end
|
134
|
+
|
135
|
+
### Output <tt>msg</tt> to STDERR and flush it.
|
136
|
+
def message( msg )
|
137
|
+
$stderr.print ansiCode( 'cyan' ) + msg + ansiCode( 'reset' )
|
138
|
+
$stderr.flush
|
139
|
+
end
|
140
|
+
|
141
|
+
### Output the specified <tt>msg</tt> as an ANSI-colored error message
|
142
|
+
### (white on red).
|
143
|
+
def errorMessage( msg )
|
144
|
+
message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
|
145
|
+
end
|
146
|
+
|
147
|
+
### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
|
148
|
+
### (yellow on blue).
|
149
|
+
def debugMsg( msg )
|
150
|
+
return unless $DEBUG
|
151
|
+
msg.chomp!
|
152
|
+
$stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
|
153
|
+
$stderr.flush
|
154
|
+
end
|
155
|
+
|
156
|
+
### Erase the previous line (if supported by your terminal) and output the
|
157
|
+
### specified <tt>msg</tt> instead.
|
158
|
+
def replaceMessage( msg )
|
159
|
+
print ErasePreviousLine
|
160
|
+
message( msg )
|
161
|
+
end
|
162
|
+
|
163
|
+
### Output a divider made up of <tt>length</tt> hyphen characters.
|
164
|
+
def divider( length=75 )
|
165
|
+
puts "\r" + ("-" * length )
|
166
|
+
end
|
167
|
+
alias :writeLine :divider
|
168
|
+
|
169
|
+
### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
|
170
|
+
### status of 1.
|
171
|
+
def abort( msg )
|
172
|
+
print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
|
173
|
+
Kernel.exit!( 1 )
|
174
|
+
end
|
175
|
+
|
176
|
+
### Output the specified <tt>promptString</tt> as a prompt (in green) and
|
177
|
+
### return the user's input with leading and trailing spaces removed.
|
178
|
+
def prompt( promptString )
|
179
|
+
promptString.chomp!
|
180
|
+
return readline( ansiCode('bold', 'green') + "#{promptString}: " + ansiCode('reset') ).strip
|
181
|
+
end
|
182
|
+
|
183
|
+
### Prompt the user with the given <tt>promptString</tt> via #prompt,
|
184
|
+
### substituting the given <tt>default</tt> if the user doesn't input
|
185
|
+
### anything.
|
186
|
+
def promptWithDefault( promptString, default )
|
187
|
+
response = prompt( "%s [%s]" % [ promptString, default ] )
|
188
|
+
if response.empty?
|
189
|
+
return default
|
190
|
+
else
|
191
|
+
return response
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
### Search for the program specified by the given <tt>progname</tt> in the
|
196
|
+
### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
|
197
|
+
### no such program is in the path.
|
198
|
+
def findProgram( progname )
|
199
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
|
200
|
+
file = File.join( d, progname )
|
201
|
+
return file if File.executable?( file )
|
202
|
+
}
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
|
206
|
+
### Using the CVS log for the given <tt>file</tt> attempt to guess what the
|
207
|
+
### next release version might be. This only works if releases are tagged
|
208
|
+
### with tags like 'RELEASE_x_y'.
|
209
|
+
def extractNextVersionFromTags( file )
|
210
|
+
message "Attempting to extract next release version from CVS tags for #{file}...\n"
|
211
|
+
raise RuntimeError, "No such file '#{file}'" unless File.exists?( file )
|
212
|
+
cvsPath = findProgram( 'cvs' ) or
|
213
|
+
raise RuntimeError, "Cannot find the 'cvs' program. Aborting."
|
214
|
+
|
215
|
+
output = %x{#{cvsPath} log #{file}}
|
216
|
+
release = [ 0, 0 ]
|
217
|
+
output.scan( /RELEASE_(\d+)_(\d+)/ ) {|match|
|
218
|
+
if $1.to_i > release[0] || $2.to_i > release[1]
|
219
|
+
release = [ $1.to_i, $2.to_i ]
|
220
|
+
replaceMessage( "Found %d.%02d...\n" % release )
|
221
|
+
end
|
222
|
+
}
|
223
|
+
|
224
|
+
if release[1] >= 99
|
225
|
+
release[0] += 1
|
226
|
+
release[1] = 1
|
227
|
+
else
|
228
|
+
release[1] += 1
|
229
|
+
end
|
230
|
+
|
231
|
+
return "%d.%02d" % release
|
232
|
+
end
|
233
|
+
|
234
|
+
### Extract the project name (CVS Repository name) for the given directory.
|
235
|
+
def extractProjectName
|
236
|
+
File.open( "CVS/Repository", "r").readline.chomp
|
237
|
+
end
|
238
|
+
|
239
|
+
### Read the specified <tt>manifestFile</tt>, which is a text file
|
240
|
+
### describing which files to package up for a distribution. The manifest
|
241
|
+
### should consist of one or more lines, each containing one filename or
|
242
|
+
### shell glob pattern.
|
243
|
+
def readManifest( manifestFile="MANIFEST" )
|
244
|
+
message "Building manifest..."
|
245
|
+
raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
|
246
|
+
|
247
|
+
manifest = IO::readlines( manifestFile ).collect {|line|
|
248
|
+
line.chomp
|
249
|
+
}.select {|line|
|
250
|
+
line !~ /^(\s*(#.*)?)?$/
|
251
|
+
}
|
252
|
+
|
253
|
+
filelist = []
|
254
|
+
for pat in manifest
|
255
|
+
$stderr.puts "Adding files that match '#{pat}' to the file list" if $VERBOSE
|
256
|
+
filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
|
257
|
+
end
|
258
|
+
|
259
|
+
message "found #{filelist.length} files.\n"
|
260
|
+
return filelist
|
261
|
+
end
|
262
|
+
|
263
|
+
### Given a <tt>filelist</tt> like that returned by #readManifest, remove
|
264
|
+
### the entries therein which match the Regexp objects in the given
|
265
|
+
### <tt>antimanifest</tt> and return the resultant Array.
|
266
|
+
def vetManifest( filelist, antimanifest=ANITMANIFEST )
|
267
|
+
origLength = filelist.length
|
268
|
+
message "Vetting manifest..."
|
269
|
+
|
270
|
+
for regex in antimanifest
|
271
|
+
if $VERBOSE
|
272
|
+
message "\n\tPattern /#{regex.source}/ removed: " +
|
273
|
+
filelist.find_all {|file| regex.match(file)}.join(', ')
|
274
|
+
end
|
275
|
+
filelist.delete_if {|file| regex.match(file)}
|
276
|
+
end
|
277
|
+
|
278
|
+
message "removed #{origLength - filelist.length} files from the list.\n"
|
279
|
+
return filelist
|
280
|
+
end
|
281
|
+
|
282
|
+
### Combine a call to #readManifest with one to #vetManifest.
|
283
|
+
def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
|
284
|
+
vetManifest( readManifest(manifestFile), antimanifest )
|
285
|
+
end
|
286
|
+
|
287
|
+
### Given a documentation <tt>catalogFile</tt>, extract the title, if
|
288
|
+
### available, and return it. Otherwise generate a title from the name of
|
289
|
+
### the CVS module.
|
290
|
+
def findRdocTitle( catalogFile="docs/CATALOG" )
|
291
|
+
|
292
|
+
# Try extracting it from the CATALOG file from a line that looks like:
|
293
|
+
# Title: Foo Bar Module
|
294
|
+
title = findCatalogKeyword( 'title', catalogFile )
|
295
|
+
|
296
|
+
# If that doesn't work for some reason, try grabbing the name of the CVS
|
297
|
+
# repository the directory belongs to.
|
298
|
+
if title.nil? && File::directory?( "CVS" ) &&
|
299
|
+
File::exists?( "CVS/Repository" )
|
300
|
+
title = File::read( "CVS/Repository" ).chomp
|
301
|
+
end
|
302
|
+
|
303
|
+
# As a last resort, use the name of the project directory
|
304
|
+
if title.nil?
|
305
|
+
distdir = File::dirname( __FILE__ )
|
306
|
+
distdir = File::dirname( distdir ) if /docs$/ =~ distdir
|
307
|
+
title = File::basename( distdir )
|
308
|
+
end
|
309
|
+
|
310
|
+
return title
|
311
|
+
end
|
312
|
+
|
313
|
+
### Given a documentation <tt>catalogFile</tt>, extract the name of the file
|
314
|
+
### to use as the initally displayed page. If extraction fails, the
|
315
|
+
### +default+ will be used if it exists. Returns +nil+ if there is no main
|
316
|
+
### file to be found.
|
317
|
+
def findRdocMain( catalogFile="docs/CATALOG", default="README" )
|
318
|
+
|
319
|
+
# Try extracting it from the CATALOG file from a line that looks like:
|
320
|
+
# Main: Foo Bar Module
|
321
|
+
main = findCatalogKeyword( 'main', catalogFile )
|
322
|
+
|
323
|
+
# Try to make some educated guesses if that doesn't work
|
324
|
+
if main.nil?
|
325
|
+
basedir = File::dirname( __FILE__ )
|
326
|
+
basedir = File::dirname( basedir ) if /docs$/ =~ basedir
|
327
|
+
|
328
|
+
if File::exists?( File::join(basedir, default) )
|
329
|
+
main = default
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
return main
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
### Given a documentation <tt>catalogFile</tt>, extract an upload URL for
|
338
|
+
### RDoc.
|
339
|
+
def findRdocUpload( catalogFile="docs/CATALOG" )
|
340
|
+
findCatalogKeyword( 'upload', catalogFile )
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
### Given a documentation <tt>catalogFile</tt>, extract a CVS web frontend
|
345
|
+
### URL for RDoc.
|
346
|
+
def findRdocCvsURL( catalogFile="docs/CATALOG" )
|
347
|
+
findCatalogKeyword( 'webcvs', catalogFile )
|
348
|
+
end
|
349
|
+
|
350
|
+
|
351
|
+
### Given a documentation <tt>catalogFile</tt>, try extracting the given
|
352
|
+
### +keyword+'s value from it. Keywords are lines that look like:
|
353
|
+
### # <keyword>: <value>
|
354
|
+
### Returns +nil+ if the catalog file was unreadable or didn't contain the
|
355
|
+
### specified +keyword+.
|
356
|
+
def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" )
|
357
|
+
val = nil
|
358
|
+
|
359
|
+
if File::exists? catalogFile
|
360
|
+
message "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
|
361
|
+
File::foreach( catalogFile ) {|line|
|
362
|
+
debugMsg( "Examining line #{line.inspect}..." )
|
363
|
+
val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i =~ line
|
364
|
+
}
|
365
|
+
end
|
366
|
+
|
367
|
+
return val
|
368
|
+
end
|
369
|
+
|
370
|
+
|
371
|
+
### Given a documentation <tt>catalogFile</tt>, which is in the same format
|
372
|
+
### as that described by #readManifest, read and expand it, and then return
|
373
|
+
### a list of those files which appear to have RDoc documentation in
|
374
|
+
### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
|
375
|
+
### file is used instead.
|
376
|
+
def findRdocableFiles( catalogFile="docs/CATALOG" )
|
377
|
+
startlist = []
|
378
|
+
if File.exists? catalogFile
|
379
|
+
message "Using CATALOG file (%s).\n" % catalogFile
|
380
|
+
startlist = getVettedManifest( catalogFile )
|
381
|
+
else
|
382
|
+
message "Using default MANIFEST\n"
|
383
|
+
startlist = getVettedManifest()
|
384
|
+
end
|
385
|
+
|
386
|
+
message "Looking for RDoc comments in:\n" if $VERBOSE
|
387
|
+
startlist.select {|fn|
|
388
|
+
message " #{fn}: " if $VERBOSE
|
389
|
+
found = false
|
390
|
+
File::open( fn, "r" ) {|fh|
|
391
|
+
fh.each {|line|
|
392
|
+
if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
|
393
|
+
found = true
|
394
|
+
break
|
395
|
+
end
|
396
|
+
}
|
397
|
+
}
|
398
|
+
|
399
|
+
message( (found ? "yes" : "no") + "\n" ) if $VERBOSE
|
400
|
+
found
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
### Open a file and filter each of its lines through the given block a
|
405
|
+
### <tt>line</tt> at a time. The return value of the block is used as the
|
406
|
+
### new line, or omitted if the block returns <tt>nil</tt> or
|
407
|
+
### <tt>false</tt>.
|
408
|
+
def editInPlace( file ) # :yields: line
|
409
|
+
raise "No block specified for editing operation" unless block_given?
|
410
|
+
|
411
|
+
tempName = "#{file}.#{$$}"
|
412
|
+
File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
|
413
|
+
File::unlink( tempName )
|
414
|
+
File::open( file, File::RDONLY ) {|fh|
|
415
|
+
fh.each {|line|
|
416
|
+
newline = yield( line ) or next
|
417
|
+
tempfile.print( newline )
|
418
|
+
}
|
419
|
+
}
|
420
|
+
|
421
|
+
tempfile.seek(0)
|
422
|
+
|
423
|
+
File::open( file, File::TRUNC|File::WRONLY, 0644 ) {|newfile|
|
424
|
+
newfile.print( tempfile.read )
|
425
|
+
}
|
426
|
+
}
|
427
|
+
end
|
428
|
+
|
429
|
+
### Execute the specified shell <tt>command</tt>, read the results, and
|
430
|
+
### return them. Like a %x{} that returns an Array instead of a String.
|
431
|
+
def shellCommand( *command )
|
432
|
+
raise "Empty command" if command.empty?
|
433
|
+
|
434
|
+
cmdpipe = IO::popen( command.join(' '), 'r' )
|
435
|
+
return cmdpipe.readlines
|
436
|
+
end
|
437
|
+
|
438
|
+
### Execute a block with $VERBOSE set to +false+, restoring it to its
|
439
|
+
### previous value before returning.
|
440
|
+
def verboseOff
|
441
|
+
raise LocalJumpError, "No block given" unless block_given?
|
442
|
+
|
443
|
+
thrcrit = Thread.critical
|
444
|
+
oldverbose = $VERBOSE
|
445
|
+
begin
|
446
|
+
Thread.critical = true
|
447
|
+
$VERBOSE = false
|
448
|
+
yield
|
449
|
+
ensure
|
450
|
+
$VERBOSE = oldverbose
|
451
|
+
Thread.critical = false
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
### Try the specified code block, printing the given
|
457
|
+
def try( msg, bind=nil )
|
458
|
+
result = nil
|
459
|
+
message "Trying #{msg}..."
|
460
|
+
|
461
|
+
begin
|
462
|
+
rval = nil
|
463
|
+
if block_given?
|
464
|
+
rval = yield
|
465
|
+
else
|
466
|
+
file, line = caller(1)[0].split(/:/,2)
|
467
|
+
rval = eval( msg, bind, file, line.to_i )
|
468
|
+
end
|
469
|
+
|
470
|
+
if $yaml
|
471
|
+
result = rval.to_yaml
|
472
|
+
else
|
473
|
+
result = rval.inspect
|
474
|
+
end
|
475
|
+
rescue Exception => err
|
476
|
+
nicetrace = err.backtrace.delete_if {|frame|
|
477
|
+
/in `(try|eval)'/ =~ frame
|
478
|
+
}.join("\n\t")
|
479
|
+
result = err.message + "\n\t" + nicetrace
|
480
|
+
ensure
|
481
|
+
puts result
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|