wordnet 0.0.5
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/ChangeLog +720 -0
- data/LICENSE +27 -0
- data/README +93 -0
- data/Rakefile +291 -0
- data/Rakefile.local +46 -0
- data/convertdb.rb +417 -0
- data/examples/addLacedBoots.rb +27 -0
- data/examples/clothesWithCollars.rb +36 -0
- data/examples/clothesWithTongues.rb +28 -0
- data/examples/distance.rb +37 -0
- data/examples/domainTree.rb +27 -0
- data/examples/gcs.rb +54 -0
- data/examples/holonymTree.rb +27 -0
- data/examples/hypernymTree.rb +28 -0
- data/examples/hyponymTree.rb +28 -0
- data/examples/memberTree.rb +27 -0
- data/examples/meronymTree.rb +29 -0
- data/lib/wordnet.rb +87 -0
- data/lib/wordnet/constants.rb +301 -0
- data/lib/wordnet/lexicon.rb +430 -0
- data/lib/wordnet/synset.rb +908 -0
- data/rake/dependencies.rb +76 -0
- data/rake/helpers.rb +384 -0
- data/rake/manual.rb +755 -0
- data/rake/packaging.rb +112 -0
- data/rake/publishing.rb +303 -0
- data/rake/rdoc.rb +35 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +469 -0
- data/rake/testing.rb +192 -0
- data/rake/verifytask.rb +64 -0
- data/spec/lib/helpers.rb +155 -0
- data/spec/wordnet/lexicon_spec.rb +248 -0
- data/spec/wordnet/synset_spec.rb +288 -0
- data/utils.rb +838 -0
- metadata +216 -0
@@ -0,0 +1,288 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.parent
|
6
|
+
|
7
|
+
libdir = basedir + 'lib'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
|
10
|
+
}
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'fileutils'
|
14
|
+
require 'tmpdir'
|
15
|
+
require 'bdb'
|
16
|
+
require 'spec/runner'
|
17
|
+
require 'spec/lib/helpers'
|
18
|
+
|
19
|
+
require 'wordnet/lexicon'
|
20
|
+
require 'wordnet/synset'
|
21
|
+
rescue LoadError
|
22
|
+
unless Object.const_defined?( :Gem )
|
23
|
+
require 'rubygems'
|
24
|
+
retry
|
25
|
+
end
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
#####################################################################
|
31
|
+
### C O N T E X T S
|
32
|
+
#####################################################################
|
33
|
+
|
34
|
+
describe WordNet::Synset do
|
35
|
+
|
36
|
+
Accessors = [
|
37
|
+
:part_of_speech,
|
38
|
+
:offset,
|
39
|
+
:filenum,
|
40
|
+
:wordlist,
|
41
|
+
:pointerlist,
|
42
|
+
:frameslist,
|
43
|
+
:gloss,
|
44
|
+
]
|
45
|
+
|
46
|
+
RelationMethods = [
|
47
|
+
:antonyms,
|
48
|
+
:hypernyms,
|
49
|
+
:entailment,
|
50
|
+
:hyponyms,
|
51
|
+
:causes,
|
52
|
+
:verb_groups,
|
53
|
+
:similar_to,
|
54
|
+
:participles,
|
55
|
+
:pertainyms,
|
56
|
+
:attributes,
|
57
|
+
:derived_from,
|
58
|
+
:derivations,
|
59
|
+
:see_also,
|
60
|
+
|
61
|
+
:instance_hyponyms,
|
62
|
+
|
63
|
+
:instance_hypernyms,
|
64
|
+
|
65
|
+
:member_meronyms,
|
66
|
+
:stuff_meronyms,
|
67
|
+
:portion_meronyms,
|
68
|
+
:component_meronyms,
|
69
|
+
:feature_meronyms,
|
70
|
+
:phase_meronyms,
|
71
|
+
:place_meronyms,
|
72
|
+
|
73
|
+
:member_holonyms,
|
74
|
+
:stuff_holonyms,
|
75
|
+
:portion_holonyms,
|
76
|
+
:component_holonyms,
|
77
|
+
:feature_holonyms,
|
78
|
+
:phase_holonyms,
|
79
|
+
:place_holonyms,
|
80
|
+
|
81
|
+
:category_domains,
|
82
|
+
:region_domains,
|
83
|
+
:usage_domains,
|
84
|
+
|
85
|
+
:category_members,
|
86
|
+
:region_members,
|
87
|
+
:usage_members,
|
88
|
+
]
|
89
|
+
|
90
|
+
AggregateRelationMethods = [
|
91
|
+
:meronyms,
|
92
|
+
:holonyms,
|
93
|
+
:domains,
|
94
|
+
:members,
|
95
|
+
]
|
96
|
+
|
97
|
+
|
98
|
+
before( :each ) do
|
99
|
+
@blank_syn = WordNet::Synset::new( @lexicon, "1%n", WordNet::Noun )
|
100
|
+
@traversal_syn = @lexicon.lookup_synsets( 'linguistics', :noun, 1 )
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#################################################################
|
105
|
+
### T E S T S
|
106
|
+
#################################################################
|
107
|
+
|
108
|
+
### Accessors
|
109
|
+
def test_accessors
|
110
|
+
printTestHeader "Synset: Accessors"
|
111
|
+
rval = nil
|
112
|
+
|
113
|
+
assert_respond_to @blankSyn, :lexicon
|
114
|
+
|
115
|
+
Accessors.each do |meth|
|
116
|
+
assert_respond_to @blankSyn, meth
|
117
|
+
assert_respond_to @blankSyn, "#{meth}="
|
118
|
+
|
119
|
+
assert_nothing_raised do
|
120
|
+
rval = @blankSyn.send( meth )
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
### Relations
|
126
|
+
def test_relations
|
127
|
+
printTestHeader "Synset: Relation methods"
|
128
|
+
rval = nil
|
129
|
+
|
130
|
+
RelationMethods.each do |meth|
|
131
|
+
casemeth = meth.to_s.sub( /^(\w)/ ) {|char| char.upcase }.intern
|
132
|
+
|
133
|
+
assert_respond_to @blankSyn, meth
|
134
|
+
assert_respond_to @blankSyn, "#{meth}="
|
135
|
+
|
136
|
+
assert_nothing_raised {
|
137
|
+
rval = @blankSyn.send( meth )
|
138
|
+
}
|
139
|
+
|
140
|
+
assert_instance_of Array, rval
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
### Aggregate relation methods
|
145
|
+
def test_aggregate_relations
|
146
|
+
printTestHeader "Synset: Aggregate relations"
|
147
|
+
rval = nil
|
148
|
+
|
149
|
+
AggregateRelationMethods.each {|meth|
|
150
|
+
assert_respond_to @blankSyn, meth
|
151
|
+
|
152
|
+
assert_nothing_raised {
|
153
|
+
rval = @blankSyn.send( meth )
|
154
|
+
}
|
155
|
+
|
156
|
+
assert_instance_of Array, rval
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
### Traversal method
|
161
|
+
def test_synset_should_respond_to_traverse_method
|
162
|
+
printTestHeader "Synset: Traversal method"
|
163
|
+
assert_respond_to @traversalSyn, :traverse
|
164
|
+
end
|
165
|
+
|
166
|
+
### :TODO: This should really be split into two tests.
|
167
|
+
### Traversal: include origin, break loop
|
168
|
+
def test_traversal_with_true_second_arg_should_include_origin
|
169
|
+
printTestHeader "Synset: Traversal, including origin, break"
|
170
|
+
rval = nil
|
171
|
+
count = depth = 0
|
172
|
+
sets = []
|
173
|
+
|
174
|
+
assert_nothing_raised {
|
175
|
+
rval = @traversalSyn.traverse( :hyponyms, true ) {|tsyn,tdepth|
|
176
|
+
sets << tsyn
|
177
|
+
depth = tdepth
|
178
|
+
count += 1
|
179
|
+
return true
|
180
|
+
}
|
181
|
+
}
|
182
|
+
assert_equal true, rval
|
183
|
+
assert_equal 1, sets.length
|
184
|
+
assert_equal @traversalSyn, sets[0]
|
185
|
+
assert_equal 0, depth
|
186
|
+
assert_equal 1, count
|
187
|
+
end
|
188
|
+
|
189
|
+
### :TODO: This should really be split into two tests.
|
190
|
+
### Traversal: exclude origin, break loop
|
191
|
+
def test_traversal_with_false_second_arg_should_not_include_origin
|
192
|
+
printTestHeader "Synset: Traversal, excluding origin, break"
|
193
|
+
rval = nil
|
194
|
+
count = depth = 0
|
195
|
+
sets = []
|
196
|
+
|
197
|
+
assert_nothing_raised {
|
198
|
+
rval = @traversalSyn.traverse( :hyponyms, false ) {|tsyn,tdepth|
|
199
|
+
sets << tsyn
|
200
|
+
depth = tdepth
|
201
|
+
count += 1
|
202
|
+
return true
|
203
|
+
}
|
204
|
+
}
|
205
|
+
assert_equal true, rval
|
206
|
+
assert_equal 1, sets.length
|
207
|
+
assert_not_equal @traversalSyn, sets[0]
|
208
|
+
assert_equal 1, depth
|
209
|
+
assert_equal 1, count
|
210
|
+
end
|
211
|
+
|
212
|
+
### Traversal: include origin, nobreak, noblock
|
213
|
+
def test_hyponym_traversal_with_no_block_should_return_appropriate_hyponyms
|
214
|
+
printTestHeader "Synset: Traversal, include origin, nobreak, noblock"
|
215
|
+
sets = []
|
216
|
+
|
217
|
+
assert_nothing_raised {
|
218
|
+
sets = @traversalSyn.traverse( :hyponyms )
|
219
|
+
}
|
220
|
+
assert_block { sets.length > 1 }
|
221
|
+
assert_equal @traversalSyn, sets[0]
|
222
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "grammar" )} }
|
223
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "syntax" )} }
|
224
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "computational linguistics" )} }
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
### Traversal: exclude origin, nobreak, noblock
|
229
|
+
def test_hyponym_traversal_with_no_block_and_false_second_arg_should_return_holonyms_but_not_the_origin
|
230
|
+
printTestHeader "Synset: Traversal, exclude origin, nobreak, noblock"
|
231
|
+
sets = []
|
232
|
+
|
233
|
+
assert_nothing_raised {
|
234
|
+
sets = @traversalSyn.traverse( :hyponyms, false )
|
235
|
+
}
|
236
|
+
assert_block { sets.length > 1 }
|
237
|
+
assert_not_equal @traversalSyn, sets[0]
|
238
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "grammar" )} }
|
239
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "syntax" )} }
|
240
|
+
assert_block { sets.find {|hsyn| hsyn.words.include?( "computational linguistics" )} }
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
### Traversal: include origin, nobreak, noblock
|
245
|
+
def test_traversal_break_after_3_should_include_three_sets_plus_origin
|
246
|
+
printTestHeader "Synset: Traversal, break after 3"
|
247
|
+
rval = nil
|
248
|
+
sets = Hash::new {|hsh,key| hsh[key] = []}
|
249
|
+
|
250
|
+
assert_nothing_raised {
|
251
|
+
rval = @traversalSyn.traverse( :hyponyms ) {|tsyn,tdepth|
|
252
|
+
sets[tdepth] << tsyn
|
253
|
+
tdepth == 3
|
254
|
+
}
|
255
|
+
}
|
256
|
+
assert_equal 4, sets.keys.length
|
257
|
+
assert_equal [0,1,2,3], sets.keys.sort
|
258
|
+
assert_equal 1, sets[3].length
|
259
|
+
assert rval, "Break early flag expected to be set"
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
### Part of speech: part_of_speech
|
264
|
+
def test_part_of_speech_should_return_the_symbol_part_of_speech
|
265
|
+
printTestHeader "Synset: part_of_speech"
|
266
|
+
rval = nil
|
267
|
+
|
268
|
+
assert_nothing_raised { rval = @traversalSyn.part_of_speech }
|
269
|
+
assert_equal :noun, rval
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
### Part of speech: pos
|
274
|
+
def test_pos_should_return_the_synsets_singlechar_part_of_speech
|
275
|
+
printTestHeader "Synset: pos"
|
276
|
+
rval = nil
|
277
|
+
|
278
|
+
assert_nothing_raised { rval = @traversalSyn.pos }
|
279
|
+
assert_equal "n", rval
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
### :TODO: Test traversal, content, storing, higher-order functions
|
284
|
+
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
|
data/utils.rb
ADDED
@@ -0,0 +1,838 @@
|
|
1
|
+
#
|
2
|
+
# Install/distribution utility functions
|
3
|
+
# $Id: utils.rb 94 2008-07-25 02:47:42Z deveiant $
|
4
|
+
#
|
5
|
+
# Copyright (c) 2001-2008, The FaerieMUD Consortium.
|
6
|
+
#
|
7
|
+
# All rights reserved.
|
8
|
+
#
|
9
|
+
# Redistribution and use in source and binary forms, with or without modification, are
|
10
|
+
# permitted provided that the following conditions are met:
|
11
|
+
#
|
12
|
+
# * Redistributions of source code must retain the above copyright notice, this
|
13
|
+
# list of conditions and the following disclaimer.
|
14
|
+
#
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice, this
|
16
|
+
# list of conditions and the following disclaimer in the documentation and/or
|
17
|
+
# other materials provided with the distribution.
|
18
|
+
#
|
19
|
+
# * Neither the name of FaerieMUD, nor the names of its contributors may be used to
|
20
|
+
# endorse or promote products derived from this software without specific prior
|
21
|
+
# written permission.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
25
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
26
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
27
|
+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
28
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
29
|
+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
30
|
+
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
31
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
32
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
33
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
#
|
35
|
+
|
36
|
+
|
37
|
+
BEGIN {
|
38
|
+
require 'pathname'
|
39
|
+
require 'rbconfig'
|
40
|
+
require 'uri'
|
41
|
+
require 'find'
|
42
|
+
require 'pp'
|
43
|
+
require 'irb'
|
44
|
+
|
45
|
+
begin
|
46
|
+
require 'readline'
|
47
|
+
include Readline
|
48
|
+
rescue LoadError => e
|
49
|
+
$stderr.puts "Faking readline..."
|
50
|
+
def readline( prompt )
|
51
|
+
$stderr.print prompt.chomp
|
52
|
+
return $stdin.gets.chomp
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
### Command-line utility functions
|
60
|
+
module UtilityFunctions
|
61
|
+
include Config
|
62
|
+
|
63
|
+
# The list of regexen that eliminate files from the MANIFEST
|
64
|
+
ANTIMANIFEST = [
|
65
|
+
/makedist\.rb/,
|
66
|
+
/\bCVS\b/,
|
67
|
+
/~$/,
|
68
|
+
/^#/,
|
69
|
+
%r{docs/html},
|
70
|
+
%r{docs/man},
|
71
|
+
/\bTEMPLATE\.\w+\.tpl\b/,
|
72
|
+
/\.cvsignore/,
|
73
|
+
/\.s?o$/,
|
74
|
+
]
|
75
|
+
|
76
|
+
# Set some ANSI escape code constants (Shamelessly stolen from Perl's
|
77
|
+
# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
|
78
|
+
AnsiAttributes = {
|
79
|
+
'clear' => 0,
|
80
|
+
'reset' => 0,
|
81
|
+
'bold' => 1,
|
82
|
+
'dark' => 2,
|
83
|
+
'underline' => 4,
|
84
|
+
'underscore' => 4,
|
85
|
+
'blink' => 5,
|
86
|
+
'reverse' => 7,
|
87
|
+
'concealed' => 8,
|
88
|
+
|
89
|
+
'black' => 30, 'on_black' => 40,
|
90
|
+
'red' => 31, 'on_red' => 41,
|
91
|
+
'green' => 32, 'on_green' => 42,
|
92
|
+
'yellow' => 33, 'on_yellow' => 43,
|
93
|
+
'blue' => 34, 'on_blue' => 44,
|
94
|
+
'magenta' => 35, 'on_magenta' => 45,
|
95
|
+
'cyan' => 36, 'on_cyan' => 46,
|
96
|
+
'white' => 37, 'on_white' => 47
|
97
|
+
}
|
98
|
+
|
99
|
+
ErasePreviousLine = "\033[A\033[K"
|
100
|
+
|
101
|
+
ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' )
|
102
|
+
#
|
103
|
+
# Distribution Manifest
|
104
|
+
# Created: #{Time::now.to_s}
|
105
|
+
#
|
106
|
+
|
107
|
+
EOF
|
108
|
+
|
109
|
+
# A cache of programs found by find_program()
|
110
|
+
Programs = {}
|
111
|
+
|
112
|
+
|
113
|
+
###############
|
114
|
+
module_function
|
115
|
+
###############
|
116
|
+
|
117
|
+
# Create a string that contains the ANSI codes specified and return it
|
118
|
+
def ansi_code( *attributes )
|
119
|
+
attributes.flatten!
|
120
|
+
# $stderr.puts "Returning ansicode for TERM = %p: %p" %
|
121
|
+
# [ ENV['TERM'], attributes ]
|
122
|
+
return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
|
123
|
+
attributes = AnsiAttributes.values_at( *attributes ).compact.join(';')
|
124
|
+
|
125
|
+
# $stderr.puts " attr is: %p" % [attributes]
|
126
|
+
if attributes.empty?
|
127
|
+
return ''
|
128
|
+
else
|
129
|
+
return "\e[%sm" % attributes
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
### Colorize the given +string+ with the specified +attributes+ and return it, handling line-endings, etc.
|
135
|
+
def colorize( string, *attributes )
|
136
|
+
ending = string[/(\s)$/] || ''
|
137
|
+
string = string.rstrip
|
138
|
+
return ansi_code( attributes.flatten ) + string + ansi_code( 'reset' ) + ending
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
# Test for the presence of the specified <tt>library</tt>, and output a
|
143
|
+
# message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
|
144
|
+
# is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
|
145
|
+
def test_for_library( library, nicename=nil, progress=false )
|
146
|
+
nicename ||= library
|
147
|
+
message( "Testing for the #{nicename} library..." ) if progress
|
148
|
+
if $LOAD_PATH.detect {|dir|
|
149
|
+
File.exists?(File.join(dir,"#{library}.rb")) ||
|
150
|
+
File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}"))
|
151
|
+
}
|
152
|
+
message( "found.\n" ) if progress
|
153
|
+
return true
|
154
|
+
else
|
155
|
+
message( "not found.\n" ) if progress
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Test for the presence of the specified <tt>library</tt>, and output a
|
162
|
+
# message describing the problem using <tt>nicename</tt>. If
|
163
|
+
# <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
|
164
|
+
# to build a default. If <tt>raa_url</tt> and/or <tt>download_url</tt> are
|
165
|
+
# specified, they are also use to build a message describing how to find the
|
166
|
+
# required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
|
167
|
+
# will cause the program to abort.
|
168
|
+
def test_for_required_library( library, nicename=nil, raa_url=nil, download_url=nil, fatal=true )
|
169
|
+
nicename ||= library
|
170
|
+
unless test_for_library( library, nicename )
|
171
|
+
msgs = [ "You are missing the required #{nicename} library.\n" ]
|
172
|
+
msgs << "RAA: #{raa_url}\n" if raa_url
|
173
|
+
msgs << "Download: #{download_url}\n" if download_url
|
174
|
+
if fatal
|
175
|
+
abort msgs.join('')
|
176
|
+
else
|
177
|
+
error_message msgs.join('')
|
178
|
+
end
|
179
|
+
end
|
180
|
+
return true
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
|
185
|
+
### blue).
|
186
|
+
def header( msg )
|
187
|
+
msg.chomp!
|
188
|
+
$stderr.puts ansi_code( 'bold', 'white', 'on_blue' ) + msg + ansi_code( 'reset' )
|
189
|
+
$stderr.flush
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
### Output <tt>msg</tt> to STDERR and flush it.
|
194
|
+
def message( *msgs )
|
195
|
+
$stderr.print( msgs.join("\n") )
|
196
|
+
$stderr.flush
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
### Output +msg+ to STDERR and flush it if $VERBOSE is true.
|
201
|
+
def verbose_msg( msg )
|
202
|
+
msg.chomp!
|
203
|
+
message( msg + "\n" ) if $VERBOSE
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
### Output the specified <tt>msg</tt> as an ANSI-colored error message
|
208
|
+
### (white on red).
|
209
|
+
def error_msg( msg )
|
210
|
+
message ansi_code( 'bold', 'white', 'on_red' ) + msg + ansi_code( 'reset' )
|
211
|
+
end
|
212
|
+
alias :error_message :error_msg
|
213
|
+
|
214
|
+
|
215
|
+
### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
|
216
|
+
### (yellow on blue).
|
217
|
+
def debug_msg( msg )
|
218
|
+
return unless $DEBUG
|
219
|
+
msg.chomp!
|
220
|
+
$stderr.puts ansi_code( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansi_code( 'reset' )
|
221
|
+
$stderr.flush
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
### Erase the previous line (if supported by your terminal) and output the
|
226
|
+
### specified <tt>msg</tt> instead.
|
227
|
+
def replace_msg( msg )
|
228
|
+
$stderr.puts
|
229
|
+
$stderr.print ErasePreviousLine
|
230
|
+
message( msg )
|
231
|
+
end
|
232
|
+
alias :replace_message :replace_msg
|
233
|
+
|
234
|
+
|
235
|
+
### Output a divider made up of <tt>length</tt> hyphen characters.
|
236
|
+
def divider( length=75 )
|
237
|
+
$stderr.puts "\r" + ("-" * length )
|
238
|
+
end
|
239
|
+
alias :write_line :divider
|
240
|
+
|
241
|
+
|
242
|
+
### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
|
243
|
+
### status of 1.
|
244
|
+
def abort( msg )
|
245
|
+
print ansi_code( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansi_code( 'reset' ) + "\n\n"
|
246
|
+
Kernel.exit!( 1 )
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
|
251
|
+
### return the user's input with leading and trailing spaces removed. If a
|
252
|
+
### test is provided, the prompt will repeat until the test returns true.
|
253
|
+
### An optional failure message can also be passed in.
|
254
|
+
def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
|
255
|
+
prompt_string.chomp!
|
256
|
+
prompt_string << ":" unless /\W$/.match( prompt_string )
|
257
|
+
response = nil
|
258
|
+
|
259
|
+
begin
|
260
|
+
response = readline( ansi_code('bold', 'green') +
|
261
|
+
"#{prompt_string} " + ansi_code('reset') ) || ''
|
262
|
+
response.strip!
|
263
|
+
if block_given? && ! yield( response )
|
264
|
+
error_message( failure_msg + "\n\n" )
|
265
|
+
response = nil
|
266
|
+
end
|
267
|
+
end until response
|
268
|
+
|
269
|
+
return response
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
|
274
|
+
### substituting the given <tt>default</tt> if the user doesn't input
|
275
|
+
### anything. If a test is provided, the prompt will repeat until the test
|
276
|
+
### returns true. An optional failure message can also be passed in.
|
277
|
+
def prompt_with_default( prompt_string, default, failure_msg="Try again." )
|
278
|
+
response = nil
|
279
|
+
|
280
|
+
begin
|
281
|
+
response = prompt( "%s [%s]" % [ prompt_string, default ] )
|
282
|
+
response = default if response.empty?
|
283
|
+
|
284
|
+
if block_given? && ! yield( response )
|
285
|
+
error_message( failure_msg + "\n\n" )
|
286
|
+
response = nil
|
287
|
+
end
|
288
|
+
end until response
|
289
|
+
|
290
|
+
return response
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
### Display a description of a potentially-dangerous task, and prompt
|
295
|
+
### for confirmation. If the user answers with anything that begins
|
296
|
+
### with 'y', yield to the block, else raise with an error.
|
297
|
+
def ask_for_confirmation( description )
|
298
|
+
puts description
|
299
|
+
|
300
|
+
answer = prompt_with_default( "Continue?", 'n' ) do |input|
|
301
|
+
input =~ /^[yn]/i
|
302
|
+
end
|
303
|
+
|
304
|
+
case answer
|
305
|
+
when /^y/i
|
306
|
+
yield
|
307
|
+
else
|
308
|
+
error "Aborted."
|
309
|
+
fail
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
### Search for the program specified by the given <tt>progname</tt> in the
|
315
|
+
### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
|
316
|
+
### no such program is in the path.
|
317
|
+
def find_program( progname )
|
318
|
+
unless Programs.key?( progname )
|
319
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).
|
320
|
+
collect {|dir| Pathnanme.new(dir) }.each do |dir|
|
321
|
+
file = dir + progname
|
322
|
+
if file.executable?
|
323
|
+
Programs[ progname ] = file
|
324
|
+
break
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
return Programs[ progname ].to_s
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
### Search for the release version for the project in the specified
|
334
|
+
### +directory+ using tags named "RELEASE_<major>_<minor>" if it's a CVS project
|
335
|
+
### or the 'project-version' metadata value of the toplevel directory if it's
|
336
|
+
### a Subversion project.
|
337
|
+
def extract_version( directory='.' )
|
338
|
+
release = nil
|
339
|
+
|
340
|
+
Dir::chdir( directory ) do
|
341
|
+
if File::directory?( "CVS" )
|
342
|
+
verbose_msg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." )
|
343
|
+
|
344
|
+
if (( cvs = find_program('cvs') ))
|
345
|
+
revs = []
|
346
|
+
output = %x{cvs log}
|
347
|
+
output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match|
|
348
|
+
rev = $1.split(/_/).collect {|s| Integer(s) rescue 0}
|
349
|
+
verbose_msg( "Found %s...\n" % rev.join('.') )
|
350
|
+
revs << rev
|
351
|
+
}
|
352
|
+
|
353
|
+
release = revs.sort.last
|
354
|
+
end
|
355
|
+
|
356
|
+
elsif File::directory?( '.svn' )
|
357
|
+
verbose_msg( "Project is versioned via Subversion" )
|
358
|
+
|
359
|
+
if (( svn = find_program('svn') ))
|
360
|
+
output = %x{svn pg project-version}.chomp
|
361
|
+
unless output.empty?
|
362
|
+
verbose_msg( "Using 'project-version' property: %p" % output )
|
363
|
+
release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0}
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
return release
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
### Find the current release version for the project in the specified
|
374
|
+
### +directory+ and return its successor.
|
375
|
+
def extract_next_version( directory='.' )
|
376
|
+
version = extract_version( directory ) || [0,0,0]
|
377
|
+
version.compact!
|
378
|
+
version[-1] += 1
|
379
|
+
|
380
|
+
return version
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
# Pattern for extracting the name of the project from a Subversion URL
|
385
|
+
SVNUrlPath = %r{
|
386
|
+
.*/ # Skip all but the last bit
|
387
|
+
([^/]+) # $1 = project name
|
388
|
+
/ # Followed by / +
|
389
|
+
(?:
|
390
|
+
trunk | # 'trunk'
|
391
|
+
(
|
392
|
+
branches | # ...or branches/branch-name
|
393
|
+
tags # ...or tags/tag-name
|
394
|
+
)/\w
|
395
|
+
)
|
396
|
+
$ # bound to the end
|
397
|
+
}ix
|
398
|
+
|
399
|
+
### Extract the project name for the given +directory+. The project name is
|
400
|
+
### the repository name if it's versioned with CVS, set via the 'project-name'
|
401
|
+
### metadata value if versioned with Subversion, or just based on the name of the
|
402
|
+
### directory itself if it's not versioned with one of those two systems.
|
403
|
+
def extract_project_name( directory='.' )
|
404
|
+
name = nil
|
405
|
+
|
406
|
+
Dir::chdir( directory ) do
|
407
|
+
|
408
|
+
# CVS-controlled
|
409
|
+
if File::directory?( "CVS" )
|
410
|
+
verbose_msg( "Project is versioned via CVS. Using repository name." )
|
411
|
+
name = File.open( "CVS/Repository", "r").readline.chomp
|
412
|
+
name.sub!( %r{.*/}, '' )
|
413
|
+
|
414
|
+
# Subversion-controlled
|
415
|
+
elsif File::directory?( '.svn' )
|
416
|
+
verbose_msg( "Project is versioned via Subversion" )
|
417
|
+
|
418
|
+
# If the machine has the svn tool, try to get the project name
|
419
|
+
if (( svn = find_program( 'svn' ) ))
|
420
|
+
|
421
|
+
# First try an explicit property
|
422
|
+
output = shell_command( svn, 'pg', 'project-name' )
|
423
|
+
if !output.empty?
|
424
|
+
verbose_msg( "Using 'project-name' property: %p" % output )
|
425
|
+
name = output.first.chomp
|
426
|
+
|
427
|
+
# If that doesn't work, try to figure it out from the URL
|
428
|
+
elsif (( uri = get_svn_uri() ))
|
429
|
+
name = uri.path.sub( SVNUrlPath ) { $1 }
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# Fall back to guessing based on the directory name
|
435
|
+
unless name
|
436
|
+
name = File::basename(File::dirname( File::expand_path(__FILE__) ))
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
return name
|
441
|
+
end
|
442
|
+
|
443
|
+
|
444
|
+
### Extract the Subversion URL from the specified directory and return it as
|
445
|
+
### a URI object.
|
446
|
+
def get_svn_uri( directory='.' )
|
447
|
+
uri = nil
|
448
|
+
|
449
|
+
Dir::chdir( directory ) do
|
450
|
+
output = %x{svn info}
|
451
|
+
debug_msg( "Using info: %p" % output )
|
452
|
+
|
453
|
+
if /^URL: \s* ( .* )/xi.match( output )
|
454
|
+
uri = URI::parse( $1 )
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
return uri
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
### (Re)make a manifest file in the specified +path+.
|
463
|
+
def make_manifest( path="MANIFEST" )
|
464
|
+
if File::exists?( path )
|
465
|
+
reply = prompt_with_default( "Replace current '#{path}'? [yN]", "n" )
|
466
|
+
return false unless /^y/i.match( reply )
|
467
|
+
|
468
|
+
verbose_msg "Replacing manifest at '#{path}'"
|
469
|
+
else
|
470
|
+
verbose_msg "Creating new manifest at '#{path}'"
|
471
|
+
end
|
472
|
+
|
473
|
+
files = []
|
474
|
+
verbose_msg( "Finding files...\n" )
|
475
|
+
Find::find( Dir::pwd ) do |f|
|
476
|
+
Find::prune if File::directory?( f ) &&
|
477
|
+
/^\./.match( File::basename(f) )
|
478
|
+
verbose_msg( " found: #{f}\n" )
|
479
|
+
files << f.sub( %r{^#{Dir::pwd}/?}, '' )
|
480
|
+
end
|
481
|
+
files = vet_manifest( files )
|
482
|
+
|
483
|
+
verbose_msg( "Writing new manifest to #{path}..." )
|
484
|
+
File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
|
485
|
+
ofh.puts( ManifestHeader )
|
486
|
+
ofh.puts( files )
|
487
|
+
end
|
488
|
+
verbose_msg( "done." )
|
489
|
+
end
|
490
|
+
|
491
|
+
|
492
|
+
### Read the specified <tt>manifest_file</tt>, which is a text file
|
493
|
+
### describing which files to package up for a distribution. The manifest
|
494
|
+
### should consist of one or more lines, each containing one filename or
|
495
|
+
### shell glob pattern.
|
496
|
+
def read_manifest( manifest_file="MANIFEST" )
|
497
|
+
verbose_msg "Building manifest..."
|
498
|
+
raise "Missing #{manifest_file}, please remake it" unless File.exists? manifest_file
|
499
|
+
|
500
|
+
manifest = IO::readlines( manifest_file ).collect {|line|
|
501
|
+
line.chomp
|
502
|
+
}.select {|line|
|
503
|
+
line !~ /^(\s*(#.*)?)?$/
|
504
|
+
}
|
505
|
+
|
506
|
+
filelist = []
|
507
|
+
for pat in manifest
|
508
|
+
verbose_msg "Adding files that match '#{pat}' to the file list"
|
509
|
+
filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
|
510
|
+
end
|
511
|
+
|
512
|
+
verbose_msg "found #{filelist.length} files.\n"
|
513
|
+
return filelist
|
514
|
+
end
|
515
|
+
|
516
|
+
|
517
|
+
### Given a <tt>filelist</tt> like that returned by #read_manifest, remove
|
518
|
+
### the entries therein which match the Regexp objects in the given
|
519
|
+
### <tt>antimanifest</tt> and return the resultant Array.
|
520
|
+
def vet_manifest( filelist, antimanifest=ANTIMANIFEST )
|
521
|
+
orig_length = filelist.length
|
522
|
+
verbose_msg "Vetting manifest..."
|
523
|
+
|
524
|
+
for regex in antimanifest
|
525
|
+
verbose_msg "\n\tPattern /#{regex.source}/ removed: " +
|
526
|
+
filelist.find_all {|file| regex.match(file)}.join(', ')
|
527
|
+
filelist.delete_if {|file| regex.match(file)}
|
528
|
+
end
|
529
|
+
|
530
|
+
verbose_msg "removed #{orig_length - filelist.length} files from the list.\n"
|
531
|
+
return filelist
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
### Combine a call to #read_manifest with one to #vet_manifest.
|
536
|
+
def get_vetted_manifest( manifest_file="MANIFEST", antimanifest=ANTIMANIFEST )
|
537
|
+
vet_manifest( read_manifest(manifest_file), antimanifest )
|
538
|
+
end
|
539
|
+
|
540
|
+
|
541
|
+
### Given a documentation <tt>catalog_file</tt>, extract the title, if
|
542
|
+
### available, and return it. Otherwise generate a title from the name of
|
543
|
+
### the CVS module.
|
544
|
+
def find_rdoc_title( catalog_file="docs/CATALOG" )
|
545
|
+
|
546
|
+
# Try extracting it from the CATALOG file from a line that looks like:
|
547
|
+
# Title: Foo Bar Module
|
548
|
+
title = find_catalog_keyword( 'title', catalog_file )
|
549
|
+
|
550
|
+
# If that doesn't work for some reason, use the name of the project.
|
551
|
+
title = extract_project_name()
|
552
|
+
|
553
|
+
return title
|
554
|
+
end
|
555
|
+
|
556
|
+
|
557
|
+
### Given a documentation <tt>catalog_file</tt>, extract the name of the file
|
558
|
+
### to use as the initally displayed page. If extraction fails, the
|
559
|
+
### +default+ will be used if it exists. Returns +nil+ if there is no main
|
560
|
+
### file to be found.
|
561
|
+
def find_rdoc_main( catalog_file="docs/CATALOG", default="README" )
|
562
|
+
|
563
|
+
# Try extracting it from the CATALOG file from a line that looks like:
|
564
|
+
# Main: Foo Bar Module
|
565
|
+
main = find_catalog_keyword( 'main', catalog_file )
|
566
|
+
|
567
|
+
# Try to make some educated guesses if that doesn't work
|
568
|
+
if main.nil?
|
569
|
+
basedir = File::dirname( __FILE__ )
|
570
|
+
basedir = File::dirname( basedir ) if /docs$/ =~ basedir
|
571
|
+
|
572
|
+
if File::exists?( File::join(basedir, default) )
|
573
|
+
main = default
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
return main
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
### Given a documentation <tt>catalog_file</tt>, extract an upload URL for
|
582
|
+
### RDoc.
|
583
|
+
def find_rdoc_upload( catalog_file="docs/CATALOG" )
|
584
|
+
find_catalog_keyword( 'upload', catalog_file )
|
585
|
+
end
|
586
|
+
|
587
|
+
|
588
|
+
### Given a documentation <tt>catalog_file</tt>, extract a CVS web frontend
|
589
|
+
### URL for RDoc.
|
590
|
+
def find_rdoc_cvs_url( catalog_file="docs/CATALOG" )
|
591
|
+
find_catalog_keyword( 'webcvs', catalog_file )
|
592
|
+
end
|
593
|
+
|
594
|
+
|
595
|
+
### Find one or more 'accessor' directives in the catalog if they exist and
|
596
|
+
### return an Array of them.
|
597
|
+
def find_rdoc_accessors( catalog_file="docs/CATALOG" )
|
598
|
+
accessors = []
|
599
|
+
in_attr_section = false
|
600
|
+
indent = ''
|
601
|
+
|
602
|
+
if File::exists?( catalog_file )
|
603
|
+
verbose_msg "Extracting accessors from CATALOG file (%s).\n" % catalog_file
|
604
|
+
|
605
|
+
# Read lines from the catalog
|
606
|
+
File::foreach( catalog_file ) do |line|
|
607
|
+
debug_msg( " Examining line #{line.inspect}..." )
|
608
|
+
|
609
|
+
# Multi-line accessors
|
610
|
+
if in_attr_section
|
611
|
+
if /^#\s+([a-z0-9_]+(?:\s*=\s*.*)?)$/i.match( line )
|
612
|
+
debug_msg( " Found accessor: #$1" )
|
613
|
+
accessors << $1
|
614
|
+
next
|
615
|
+
end
|
616
|
+
|
617
|
+
debug_msg( " End of accessors section." )
|
618
|
+
in_attr_section = false
|
619
|
+
|
620
|
+
# Single-line accessor
|
621
|
+
elsif /^#\s*Accessors:\s*(\S+)$/i.match( line )
|
622
|
+
debug_msg( " Found single accessors line: #$1" )
|
623
|
+
vals = $1.split(/,/).collect {|val| val.strip }
|
624
|
+
accessors.replace( vals )
|
625
|
+
|
626
|
+
# Multi-line accessor header
|
627
|
+
elsif /^#\s*Accessors:\s*$/i.match( line )
|
628
|
+
debug_msg( " Start of accessors section." )
|
629
|
+
in_attr_section = true
|
630
|
+
end
|
631
|
+
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
debug_msg( "Found accessors: %s" % accessors.join(",") )
|
636
|
+
return accessors
|
637
|
+
end
|
638
|
+
|
639
|
+
|
640
|
+
### Given a documentation <tt>catalog_file</tt>, try extracting the given
|
641
|
+
### +keyword+'s value from it. Keywords are lines that look like:
|
642
|
+
### # <keyword>: <value>
|
643
|
+
### Returns +nil+ if the catalog file was unreadable or didn't contain the
|
644
|
+
### specified +keyword+.
|
645
|
+
def find_catalog_keyword( keyword, catalog_file="docs/CATALOG" )
|
646
|
+
val = nil
|
647
|
+
|
648
|
+
if File::exists? catalog_file
|
649
|
+
verbose_msg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalog_file
|
650
|
+
File::foreach( catalog_file ) do |line|
|
651
|
+
debug_msg( "Examining line #{line.inspect}..." )
|
652
|
+
val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i.match( line )
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
return val
|
657
|
+
end
|
658
|
+
|
659
|
+
|
660
|
+
### Given a documentation <tt>catalog_file</tt>, which is in the same format
|
661
|
+
### as that described by #read_manifest, read and expand it, and then return
|
662
|
+
### a list of those files which appear to have RDoc documentation in
|
663
|
+
### them. If <tt>catalog_file</tt> is nil or does not exist, the MANIFEST
|
664
|
+
### file is used instead.
|
665
|
+
def find_rdocable_files( catalog_file="docs/CATALOG" )
|
666
|
+
startlist = []
|
667
|
+
if File.exists? catalog_file
|
668
|
+
verbose_msg "Using CATALOG file (%s).\n" % catalog_file
|
669
|
+
startlist = get_vetted_manifest( catalog_file )
|
670
|
+
else
|
671
|
+
verbose_msg "Using default MANIFEST\n"
|
672
|
+
startlist = get_vetted_manifest()
|
673
|
+
end
|
674
|
+
|
675
|
+
verbose_msg "Looking for RDoc comments in:\n"
|
676
|
+
startlist.select {|fn|
|
677
|
+
verbose_msg " #{fn}: "
|
678
|
+
found = false
|
679
|
+
File::open( fn, "r" ) {|fh|
|
680
|
+
fh.each {|line|
|
681
|
+
if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
|
682
|
+
found = true
|
683
|
+
break
|
684
|
+
end
|
685
|
+
}
|
686
|
+
}
|
687
|
+
|
688
|
+
verbose_msg( (found ? "yes" : "no") + "\n" )
|
689
|
+
found
|
690
|
+
}
|
691
|
+
end
|
692
|
+
|
693
|
+
|
694
|
+
### Open a file and filter each of its lines through the given block a
|
695
|
+
### <tt>line</tt> at a time. The return value of the block is used as the
|
696
|
+
### new line, or omitted if the block returns <tt>nil</tt> or
|
697
|
+
### <tt>false</tt>.
|
698
|
+
def edit_in_place( file, test_mode=false ) # :yields: line
|
699
|
+
raise "No block specified for editing operation" unless block_given?
|
700
|
+
|
701
|
+
temp_name = "#{file}.#{$$}"
|
702
|
+
File::open( temp_name, File::RDWR|File::CREAT, 0600 ) do |tempfile|
|
703
|
+
File::open( file, File::RDONLY ) do |fh|
|
704
|
+
fh.each do |line|
|
705
|
+
newline = yield( line ) or next
|
706
|
+
tempfile.print( newline )
|
707
|
+
$stderr.puts "%p -> %p" % [ line, newline ] if
|
708
|
+
line != newline
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
if test_mode
|
714
|
+
File::unlink( temp_name )
|
715
|
+
else
|
716
|
+
File::rename( temp_name, file )
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
|
721
|
+
### Execute the specified shell <tt>command</tt>, read the results, and
|
722
|
+
### return them. Like a %x{} that returns an Array instead of a String.
|
723
|
+
def shell_command( *command )
|
724
|
+
raise "Empty command" if command.empty?
|
725
|
+
|
726
|
+
cmdpipe = IO::open( '|-' ) or exec( *command )
|
727
|
+
return cmdpipe.readlines
|
728
|
+
end
|
729
|
+
|
730
|
+
|
731
|
+
### Execute a block with $VERBOSE set to +false+, restoring it to its
|
732
|
+
### previous value before returning.
|
733
|
+
def verbose_off
|
734
|
+
raise LocalJumpError, "No block given" unless block_given?
|
735
|
+
|
736
|
+
thrcrit = Thread.critical
|
737
|
+
oldverbose = $VERBOSE
|
738
|
+
begin
|
739
|
+
Thread.critical = true
|
740
|
+
$VERBOSE = false
|
741
|
+
yield
|
742
|
+
ensure
|
743
|
+
$VERBOSE = oldverbose
|
744
|
+
Thread.critical = false
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
|
749
|
+
### Try the specified code block, printing the given
|
750
|
+
def try( msg, bind=TOPLEVEL_BINDING )
|
751
|
+
result = ''
|
752
|
+
if msg =~ /^to\s/
|
753
|
+
message "Trying #{msg}...\n"
|
754
|
+
else
|
755
|
+
message msg + "\n"
|
756
|
+
end
|
757
|
+
|
758
|
+
begin
|
759
|
+
rval = nil
|
760
|
+
if block_given?
|
761
|
+
rval = yield
|
762
|
+
else
|
763
|
+
file, line = caller(1)[0].split(/:/,2)
|
764
|
+
rval = eval( msg, bind, file, line.to_i )
|
765
|
+
end
|
766
|
+
|
767
|
+
PP.pp( rval, result )
|
768
|
+
|
769
|
+
rescue Exception => err
|
770
|
+
if err.backtrace
|
771
|
+
nicetrace = err.backtrace.delete_if {|frame|
|
772
|
+
/in `(try|eval)'/ =~ frame
|
773
|
+
}.join("\n\t")
|
774
|
+
else
|
775
|
+
nicetrace = "Exception had no backtrace"
|
776
|
+
end
|
777
|
+
|
778
|
+
result = err.message + "\n\t" + nicetrace
|
779
|
+
|
780
|
+
ensure
|
781
|
+
divider
|
782
|
+
message result.chomp + "\n"
|
783
|
+
divider
|
784
|
+
$stderr.puts
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
|
789
|
+
### Start an IRB session with the specified binding +b+ as the current scope.
|
790
|
+
def start_irb_session( b )
|
791
|
+
IRB.setup(nil)
|
792
|
+
|
793
|
+
workspace = IRB::WorkSpace.new( b )
|
794
|
+
|
795
|
+
if IRB.conf[:SCRIPT]
|
796
|
+
irb = IRB::Irb.new( workspace, IRB.conf[:SCRIPT] )
|
797
|
+
else
|
798
|
+
irb = IRB::Irb.new( workspace )
|
799
|
+
end
|
800
|
+
|
801
|
+
IRB.conf[:IRB_RC].call( irb.context ) if IRB.conf[:IRB_RC]
|
802
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
803
|
+
|
804
|
+
trap("SIGINT") do
|
805
|
+
irb.signal_handle
|
806
|
+
end
|
807
|
+
|
808
|
+
catch(:IRB_EXIT) do
|
809
|
+
irb.eval_input
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
end # module UtilityFunctions
|
814
|
+
|
815
|
+
|
816
|
+
|
817
|
+
if __FILE__ == $0
|
818
|
+
# $DEBUG = true
|
819
|
+
include UtilityFunctions
|
820
|
+
|
821
|
+
projname = extract_project_name()
|
822
|
+
header "Project: #{projname}"
|
823
|
+
|
824
|
+
ver = extract_version() || [0,0,1]
|
825
|
+
puts "Version: %s\n" % ver.join('.')
|
826
|
+
|
827
|
+
if File::directory?( "docs" )
|
828
|
+
puts "Rdoc:",
|
829
|
+
" Title: " + find_rdoc_title(),
|
830
|
+
" Main: " + find_rdoc_main(),
|
831
|
+
" Upload: " + find_rdoc_upload(),
|
832
|
+
" VCS URL: " + find_rdoc_cvs_url(),
|
833
|
+
" Accessors: " + find_rdoc_accessors().join(",")
|
834
|
+
end
|
835
|
+
|
836
|
+
puts "Manifest:",
|
837
|
+
" " + get_vetted_manifest().join("\n ")
|
838
|
+
end
|