BlueCloth 1.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,34 @@
1
+ The Ant-Sugar Tales
2
+ ===================
3
+
4
+ By Candice Yellowflower
5
+
6
+ The _Ant-Sugar Tales_ is a collection of short stories told from the
7
+ perspective of a fine young lady from [Venice][1], who has some run-ins
8
+ with a few [inquisitive insects][2]. Each tale presents a moral quandry,
9
+ which the ants are quick to solve with their antly wisdom and
10
+ know-how. Some of the moral lessons presented are:
11
+
12
+ * Laundry: How not to get caught in soiled knickers.
13
+ * Used Ticket Stubs and Their Impact on the Universe
14
+ * I'm Keeping a Birdhouse in my Attic
15
+
16
+ Use of Metaphor
17
+ ---------------
18
+
19
+ The author's splended use of metaphor can be attributed to her growing
20
+ up in a art-supply store. Her characters are richly outlined, but her
21
+ unusual descriptions can sometimes be a bit jarring in places, such as
22
+ her description of the old caretaker that lives inside a hollow tree in
23
+ her yard:
24
+
25
+ > His skin was smooth like Magnani Pescia 100% acid-free cold pressed
26
+ > 22x30" Soft White Paper, with fine hair like the bristles of a Habico
27
+ > Lasur Superb Oil Glazing Brush Size 10.
28
+
29
+
30
+ [1]: http://www.azureva.com/gb/italie/mags/grand-canal.php3
31
+ (Venice: The Grand Canal)
32
+ [2]: http://www.fortunecity.com/emachines/e11/86/tourist4d.html
33
+
34
+
@@ -0,0 +1,4 @@
1
+
2
+ This line of markdown below will hang you if you're running BlueCloth.
3
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
4
+
@@ -0,0 +1,17 @@
1
+ Hi,
2
+
3
+ I'd like to announce the alpha release of a Markdown library for [Ruby][1]
4
+ called "BlueCloth". It's mostly a direct port of the most recent Perl version,
5
+ minus the various plugin hooks and whatnot.
6
+
7
+ More information can be found on [the project page][2], or feel free to ask me
8
+ directly. I don't have much in the way of a demo yet, but will be working on
9
+ getting something set up in the coming days.
10
+
11
+ [1]: http://www.ruby-lang.org/
12
+ [2]: http://bluecloth.rubyforge.org/
13
+
14
+ --
15
+ Michael Granger <ged@FaerieMUD.org>
16
+ Rubymage, Believer, Architect
17
+ The FaerieMUD Consortium <http://www.FaerieMUD.org/>
@@ -0,0 +1,67 @@
1
+ * xx xxxxxxx xx xxxxxx.
2
+
3
+ * xxx xxxxxxx xxxx xx xxxxxxxxxxx xx:
4
+
5
+ * xxxxxxx xxxxxxx: xxxxx xxxx xxxx xxxxxxx xxxxxxx xxxxxxxx xxxxxx xx
6
+ xxxxxxx xxx xxxxxxxxx, xxx x xxxxx xxxxx xxx xxxxxxxx xx xxx xxxxxx xxxx
7
+ xxx xx xxxxxxxxx xx xxxx.
8
+
9
+ xxxxx xxxxxxx xx xxx xxxx xx xx xxxxxxxxx, xxx xxxx xxxxxx xx xxxxxxx xxxx
10
+ xxx xxxxxxx'x xxxxxx xxx. xx xxxxxxxx xxxxxxxxxxxxx xxxxxxxx.
11
+
12
+ * xxxxxxxxx xxxxxxx: xxxxx xxxx xxx xxxxx xx xxxxx xxx xxxxxxxx xxxxxxxxx
13
+ xx xxx xxxxxxxx, xxx xxxxx xxxxx xxxx xxxx xxxxx xxxxxxxxxxxx xx xxx
14
+ xxxxxxxxxxx xxxx xxx xx xxxxxxxxx xx xxxx.
15
+
16
+ xxxxx xxxxxxx xxx xx xxxxxxxxx xxxxxx xxx-xxxx xxxxx (xx xx xxxxxxxxxx)
17
+ xx, xx xxxxxxxxx xxxxxxxx xxxxxxx xx xxxxxxxx xx xxxxxx xxx xxxxxxx
18
+ xxxxxxx xx xxx xxxxxxx, xxxxxx xxx xxxx xxx.
19
+
20
+ xxxxx xxxxxxxxxx xxx xxxx xxxx xx xxxxxxxxx xxx xx xxxxx xxx xxxxx xxxxx
21
+ xxx xxxx xxx xxxx xxxxxxxxx. xxxxxxxx xxxxxxxxxxxxx xxx xxxx-xxxxxxxxx,
22
+ xxxx xx xxxxxx xxx xxxx.
23
+
24
+ * xxxxx xxxxxxx: xxxxx xxxx xxxxxx xxxx xxxxxxx xx xxxxxxx x xxxxxxxxxxx
25
+ xxxxxx, xxxxxxx xx xxxxxxx xxxxxxxxxxxx. xxxxx xxxxxxx xxxx xx xxxxxxxxx
26
+ xxxxxx xxx-xxxx xxxxx.
27
+
28
+ xxxxxx xxxxxxxxx xxx x xxxx xxxxxxxxx, xxxx xx x-xxxx.
29
+
30
+ * xxxx xxx x xxxxxx xxxxxxx xxxx: xxxxx xxxxxxx xxxx xx xxxxxxxx, xxx xxxxxxx
31
+ xxx xxx xxxxxx, xxx xxxxx, xxx xxxxxxxxx xxx xxxxxxx xxxx xxx xxxxxxx
32
+ xxxxxxxx xxxx, xxx xxxx-xxx xxxx, xxx xxxxxxxx xx xxx xxxx, xxx xxx xxxxxxxx
33
+ xx xxx xxxxxxxxx xxxx-xxx.
34
+
35
+ * xxx xxxxxxxxxxxx xxxxxxxxxxx (x.x.x. xxx xxxxxxxx xx xxxxxxx xxxxxxxx, xx
36
+ xxxxxxxx xxxxxx, xxx.), xxx xxxxxxx xxxxxxxxxxx xx x xxxxxx xxxxxxx xxxx
37
+ xxxx xx xxxxxxxxx: x xxxx-xxxxxx xx xxxx-xxxxx xxxxxxxx xx xxx xxxxxxxxxx.
38
+
39
+ * xxx xxx xxxx xxxxxxx xxx, xx xxxxx xxxxxx xx xxxx xx xxx xxxxxxx'x xxxxxx
40
+ xxx. xxxxxxxx xxxxxxx xxxxxx xx xxxx xxx xxxxxxx xxxxxxx.
41
+
42
+ x xxxxxx xxx xxx xxxxxxx xxxx xx xxxx xx xxxxxxxx. xxxxx xxxxxxxxxxxxx
43
+ xxxxxx xx x xxxxxx xxxx xx xxxxxxx xxxx xxxx xxxxxx'x xxxxxx xxx xxx xxxx
44
+ xxxxxxx xxx xxxxxxxxx xxxxxxxxxxx:
45
+
46
+ * xxxxxxxxx xx xxxxxx xxxxxxx xxxxxx xxxx xxxxxx (xx xxxxx xxxxxx xx xx
47
+ xxxxxxxxxx).
48
+
49
+ * xxxxxxxxxxx xx xxxx xxxxxxx xxx.
50
+
51
+ * xxxx xx xxxxx xxxxxxx xx xxx xxxxxx.
52
+
53
+ * xxxx xxx xxxx xx xxxxxx xx xxxx-xx-xx xx:xx xxx (xx xxx) xxxxxx.
54
+
55
+ * xxxx xxx xxxxxxxx xx xxxxxxxxxxx xx xxxxxx.
56
+
57
+ * xxxxxx xx xxxxxxx xxxx xx xxxxxxxx xxxxxxx xxx xxxx xxxx xx xxxxxx
58
+ xxxxx-xxxxxxxxxxxx xxxxxx xxxxxxxxxx xxxxxxx. xxxxxxxx xxxxxxx xxx xx
59
+ xxxxxxxx xx xxxxxxxxxxx xx xxxx xxxx.
60
+
61
+ * xx x xxxxx xxxx:
62
+
63
+ * xxxx xxxxxxx xxxxxx xxxx x xxxxx-xxx xxx xxxxxx xxxxxxx, xxxxxxxx xxxxxxx,
64
+ xxx xxxxxxxx xxxxx xxxxxxx xxxx xxxxxxxx xxxxxxx, xx xxx xxx. xxxxxxx,
65
+ xxxx xxxxxx xxx xxxx xx xxx xxxxxxx xx xxx xxxxxx xx xxx xxxxxxx xxxxxx
66
+ -- xxxxx xxx, xx xxxxx xxxxxx xxxxx xx xxxxx xxx xxxx xxxxxxxx -- xxx xxxx
67
+ xxxxx xxx xxx xxxxxxxx xx xxxxxxxxx xxxxxx-xxxxxxxx xxxxxxxx.
@@ -0,0 +1,281 @@
1
+ <strong>iFotobilder</strong> will be an iPhoto export plugin that will let you manage your Fotobilder pictures through iPhoto.
2
+
3
+ ## Getting Started
4
+
5
+ Since iPhoto's APIs aren't public, and because my Objective C is extremely rusty, I wanted a couple of examples of other people's plugins before I dove into this.
6
+
7
+ Here's what I found:
8
+
9
+ * [Writing Plugins for Cocoa][1]
10
+
11
+ [1]: http://www.stone.com/The_Cocoa_Files/Writing_PlugIns.html
12
+
13
+ ## The iPhoto Export API
14
+
15
+ Using the `class-dump` tool, I dumped the export API:
16
+
17
+ /*
18
+ * Generated by class-dump 3.0.
19
+ *
20
+ * class-dump is Copyright (C) 1997-1998, 2000-2001, 2004 by Steve Nygard.
21
+ */
22
+
23
+ /*
24
+ * File: /Applications/iPhoto.app/Contents/MacOS/iPhoto
25
+ */
26
+
27
+ @protocol ExportImageProtocol
28
+ - (unsigned int)imageCount;
29
+ - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp20;
30
+ - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16;
31
+ - (unsigned int)imageFormatAtIndex:(unsigned int)fp16;
32
+ - (id)imageCaptionAtIndex:(unsigned int)fp16;
33
+ - (id)imagePathAtIndex:(unsigned int)fp16;
34
+ - (id)thumbnailPathAtIndex:(unsigned int)fp16;
35
+ - (id)imageDictionaryAtIndex:(unsigned int)fp16;
36
+ - (float)imageAspectRatioAtIndex:(unsigned int)fp16;
37
+ - (id)albumName;
38
+ - (id)albumMusicPath;
39
+ - (unsigned int)albumCount;
40
+ - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp16;
41
+ - (id)window;
42
+ - (void)enableControls;
43
+ - (void)disableControls;
44
+ - (void)clickExport;
45
+ - (void)startExport;
46
+ - (void)cancelExport;
47
+ - (void)cancelExportBeforeBeginning;
48
+ - (id)directoryPath;
49
+ - (id)temporaryDirectory;
50
+ - (BOOL)doesFileExist:(id)fp16;
51
+ - (BOOL)doesDirectoryExist:(id)fp16;
52
+ - (BOOL)createDir:(id)fp16;
53
+ - (id)uniqueSubPath:(id)fp12 child:(id)fp20;
54
+ - (id)makeUniquePath:(id)fp16;
55
+ - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp20;
56
+ - (id)makeUniqueFileNameWithTime:(id)fp16;
57
+ - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp20;
58
+ - (id)pathForFSSpec:(id)fp16;
59
+ - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp27;
60
+ - (id)pathForFSRef:(struct FSRef *)fp16;
61
+ - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp23;
62
+ - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp23;
63
+ - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp27;
64
+ - (BOOL)isAliasFileAtPath:(id)fp16;
65
+ - (id)pathContentOfAliasAtPath:(id)fp16;
66
+ - (id)stringByResolvingAliasesInPath:(id)fp16;
67
+ - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp20;
68
+ - (id)validFilename:(id)fp16;
69
+ - (id)getExtensionForImageFormat:(unsigned int)fp16;
70
+ - (unsigned int)getImageFormatForExtension:(id)fp16;
71
+ - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp32;
72
+ - (void *)createThumbnailer;
73
+ - (void *)retainThumbnailer:(void *)fp16;
74
+ - (void *)autoreleaseThumbnailer:(void *)fp16;
75
+ - (void)releaseThumbnailer:(void *)fp16;
76
+ - (void)setThumbnailer:(void *)fp16 maxBytes:(unsigned int)fp20 maxWidth:(unsigned int)fp24 maxHeight:(unsigned int)fp32;
77
+ - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16;
78
+ - (void)setThumbnailer:(void *)fp12 quality:(int)fp20;
79
+ - (int)thumbnailerQuality:(void *)fp16;
80
+ - (void)setThumbnailer:(void *)fp12 rotation:(float)fp20;
81
+ - (float)thumbnailerRotation:(void *)fp16;
82
+ - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp20;
83
+ - (unsigned int)thumbnailerOutputFormat:(void *)fp16;
84
+ - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp20;
85
+ - (id)thumbnailerOutputExtension:(void *)fp16;
86
+ - (BOOL)thumbnailer:(void *)fp16 createThumbnail:(id)fp20 dest:(id)fp28;
87
+ - (struct _NSSize)lastImageSize:(void *)fp20;
88
+ - (struct _NSSize)lastThumbnailSize:(void *)fp16;
89
+ @end
90
+
91
+ @protocol ExportPluginBoxProtocol
92
+ - (BOOL)performKeyEquivalent:(id)fp16;
93
+ @end
94
+
95
+ @protocol ExportPluginProtocol
96
+ - (id)initWithExportImageObj:(id)fp16;
97
+ - (id)settingsView;
98
+ - (id)firstView;
99
+ - (id)lastView;
100
+ - (void)viewWillBeActivated;
101
+ - (void)viewWillBeDeactivated;
102
+ - (id)requiredFileType;
103
+ - (BOOL)wantsDestinationPrompt;
104
+ - (id)getDestinationPath;
105
+ - (id)defaultFileName;
106
+ - (id)defaultDirectory;
107
+ - (BOOL)treatSingleSelectionDifferently;
108
+ - (BOOL)validateUserCreatedPath:(id)fp16;
109
+ - (void)clickExport;
110
+ - (void)startExport:(id)fp16;
111
+ - (void)performExport:(id)fp16;
112
+ - (CDAnonymousStruct12 *)progress;
113
+ - (void)lockProgress;
114
+ - (void)unlockProgress;
115
+ - (void)cancelExport;
116
+ - (id)name;
117
+ - (id)description;
118
+ @end
119
+
120
+ @interface ExportController : NSObject
121
+ {
122
+ id mWindow;
123
+ id mExportView;
124
+ id mExportButton;
125
+ id mImageCount;
126
+ ExportMgr *mExportMgr;
127
+ ExportMgrRec *mCurrentPluginRec;
128
+ ProgressController *mProgressController;
129
+ BOOL mCancelExport;
130
+ NSTimer *mTimer;
131
+ NSString *mDirectoryPath;
132
+ }
133
+
134
+ - (void)awakeFromNib;
135
+ - (void)dealloc;
136
+ - (id)currentPlugin;
137
+ - (id)currentPluginRec;
138
+ - (void)setCurrentPluginRec:(id)fp12;
139
+ - (id)directoryPath;
140
+ - (void)setDirectoryPath:(id)fp12;
141
+ - (void)show;
142
+ - (void)_openPanelDidEnd:(id)fp12 returnCode:(int)fp16 contextInfo:(void *)fp20;
143
+ - (id)panel:(id)fp12 userEnteredFilename:(id)fp16 confirmed:(BOOL)fp20;
144
+ - (BOOL)panel:(id)fp12 shouldShowFilename:(id)fp16;
145
+ - (BOOL)panel:(id)fp12 isValidFilename:(id)fp16;
146
+ - (BOOL)filesWillFitOnDisk;
147
+ - (void)export:(id)fp12;
148
+ - (void)_exportThread:(id)fp12;
149
+ - (void)_exportProgress:(id)fp12;
150
+ - (void)startExport:(id)fp12;
151
+ - (void)finishExport;
152
+ - (void)cancelExport;
153
+ - (void)cancel:(id)fp12;
154
+ - (void)enableControls;
155
+ - (id)window;
156
+ - (void)disableControls;
157
+ - (void)tabView:(id)fp12 willSelectTabViewItem:(id)fp16;
158
+ - (void)tabView:(id)fp12 didSelectTabViewItem:(id)fp16;
159
+ - (void)selectExporter:(id)fp12;
160
+ - (id)exportView;
161
+ - (BOOL)_hasPlugins;
162
+ - (void)_resizeExporterToFitView:(id)fp12;
163
+ - (void)_updateImageCount;
164
+
165
+ @end
166
+
167
+ @interface ExportMgr : NSObject <ExportImageProtocol>
168
+ {
169
+ ArchiveDocument *mDocument;
170
+ NSMutableArray *mExporters;
171
+ Album *mExportAlbum;
172
+ NSArray *mSelection;
173
+ ExportController *mExportController;
174
+ }
175
+
176
+ + (id)exportMgr;
177
+ + (id)exportMgrNoAlloc;
178
+ - (id)init;
179
+ - (void)dealloc;
180
+ - (void)releasePlugins;
181
+ - (void)setExportController:(id)fp12;
182
+ - (id)exportController;
183
+ - (void)setDocument:(id)fp12;
184
+ - (id)document;
185
+ - (void)updateDocumentSelection;
186
+ - (unsigned int)count;
187
+ - (id)recAtIndex:(unsigned int)fp12;
188
+ - (void)scanForExporters;
189
+ - (unsigned int)imageCount;
190
+ - (BOOL)imageIsPortraitAtIndex:(unsigned int)fp12;
191
+ - (id)imagePathAtIndex:(unsigned int)fp12;
192
+ - (struct _NSSize)imageSizeAtIndex:(unsigned int)fp16;
193
+ - (unsigned int)imageFormatAtIndex:(unsigned int)fp12;
194
+ - (id)imageCaptionAtIndex:(unsigned int)fp12;
195
+ - (id)thumbnailPathAtIndex:(unsigned int)fp12;
196
+ - (id)imageDictionaryAtIndex:(unsigned int)fp12;
197
+ - (float)imageAspectRatioAtIndex:(unsigned int)fp12;
198
+ - (id)albumName;
199
+ - (id)albumMusicPath;
200
+ - (unsigned int)albumCount;
201
+ - (unsigned int)albumPositionOfImageAtIndex:(unsigned int)fp12;
202
+ - (id)imageRecAtIndex:(unsigned int)fp12;
203
+ - (id)currentAlbum;
204
+ - (void)enableControls;
205
+ - (void)disableControls;
206
+ - (id)window;
207
+ - (void)clickExport;
208
+ - (void)startExport;
209
+ - (void)cancelExport;
210
+ - (void)cancelExportBeforeBeginning;
211
+ - (id)directoryPath;
212
+ - (void)_copySelection:(id)fp12;
213
+ - (id)temporaryDirectory;
214
+ - (BOOL)doesFileExist:(id)fp12;
215
+ - (BOOL)doesDirectoryExist:(id)fp12;
216
+ - (BOOL)createDir:(id)fp12;
217
+ - (id)uniqueSubPath:(id)fp12 child:(id)fp16;
218
+ - (id)makeUniquePath:(id)fp12;
219
+ - (id)makeUniqueFilePath:(id)fp12 extension:(id)fp16;
220
+ - (id)makeUniqueFileNameWithTime:(id)fp12;
221
+ - (BOOL)makeFSSpec:(id)fp12 spec:(struct FSSpec *)fp16;
222
+ - (id)pathForFSSpec:(id)fp12;
223
+ - (BOOL)getFSRef:(struct FSRef *)fp12 forPath:(id)fp16 isDirectory:(BOOL)fp20;
224
+ - (id)pathForFSRef:(struct FSRef *)fp12;
225
+ - (unsigned long)countFiles:(id)fp12 descend:(BOOL)fp16;
226
+ - (unsigned long)countFilesFromArray:(id)fp12 descend:(BOOL)fp16;
227
+ - (unsigned long long)sizeAtPath:(id)fp12 count:(unsigned long *)fp16 physical:(BOOL)fp20;
228
+ - (BOOL)isAliasFileAtPath:(id)fp12;
229
+ - (id)pathContentOfAliasAtPath:(id)fp12;
230
+ - (id)stringByResolvingAliasesInPath:(id)fp12;
231
+ - (BOOL)ensurePermissions:(unsigned long)fp12 forPath:(id)fp16;
232
+ - (id)validFilename:(id)fp12;
233
+ - (id)getExtensionForImageFormat:(unsigned int)fp12;
234
+ - (unsigned int)getImageFormatForExtension:(id)fp12;
235
+ - (struct OpaqueGrafPtr *)uncompressImage:(id)fp12 size:(struct _NSSize)fp16 pixelFormat:(unsigned int)fp24 rotation:(float)fp36;
236
+ - (void *)createThumbnailer;
237
+ - (void *)retainThumbnailer:(void *)fp12;
238
+ - (void *)autoreleaseThumbnailer:(void *)fp12;
239
+ - (void)releaseThumbnailer:(void *)fp12;
240
+ - (void)setThumbnailer:(void *)fp12 maxBytes:(unsigned int)fp16 maxWidth:(unsigned int)fp20 maxHeight:(unsigned int)fp24;
241
+ - (struct _NSSize)thumbnailerMaxBounds:(void *)fp16;
242
+ - (void)setThumbnailer:(void *)fp12 quality:(int)fp16;
243
+ - (int)thumbnailerQuality:(void *)fp12;
244
+ - (void)setThumbnailer:(void *)fp12 rotation:(float)fp36;
245
+ - (float)thumbnailerRotation:(void *)fp12;
246
+ - (void)setThumbnailer:(void *)fp12 outputFormat:(unsigned int)fp16;
247
+ - (unsigned int)thumbnailerOutputFormat:(void *)fp12;
248
+ - (void)setThumbnailer:(void *)fp12 outputExtension:(id)fp16;
249
+ - (id)thumbnailerOutputExtension:(void *)fp12;
250
+ - (BOOL)thumbnailer:(void *)fp12 createThumbnail:(id)fp16 dest:(id)fp20;
251
+ - (struct _NSSize)lastImageSize:(void *)fp16;
252
+ - (struct _NSSize)lastThumbnailSize:(void *)fp16;
253
+
254
+ @end
255
+
256
+ @interface ExportMgrRec : NSObject
257
+ {
258
+ NSString *mPath;
259
+ NSBundle *mBundle;
260
+ id mPlugin;
261
+ struct _NSSize mViewSize;
262
+ }
263
+
264
+ - (void)dealloc;
265
+ - (BOOL)isEqual:(id)fp12;
266
+ - (id)description;
267
+ - (id)initWithPath:(id)fp12;
268
+ - (id)path;
269
+ - (id)bundle;
270
+ - (id)bundleInfo;
271
+ - (BOOL)isValidExportPlugin;
272
+ - (BOOL)loadPlugin;
273
+ - (id)exportPlugin;
274
+ - (void)unloadPlugin;
275
+ - (id)view;
276
+ - (struct _NSSize)viewSize;
277
+ - (void)setPath:(id)fp12;
278
+ - (void)setBundle:(id)fp12;
279
+
280
+ @end
281
+
@@ -0,0 +1,739 @@
1
+ #
2
+ # Install/distribution utility functions
3
+ # $Id: utils.rb 15 2005-12-09 20:45:29Z ged $
4
+ #
5
+ # Copyright (c) 2001-2006, 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
+ require 'pp'
17
+
18
+ begin
19
+ require 'readline'
20
+ include Readline
21
+ rescue LoadError => e
22
+ $stderr.puts "Faking readline..."
23
+ def readline( prompt )
24
+ $stderr.print prompt.chomp
25
+ return $stdin.gets.chomp
26
+ end
27
+ end
28
+
29
+ }
30
+
31
+
32
+ module UtilityFunctions
33
+ include Config
34
+
35
+ # The list of regexen that eliminate files from the MANIFEST
36
+ ANTIMANIFEST = [
37
+ /makedist\.rb/,
38
+ /\bCVS\b/,
39
+ /~$/,
40
+ /^#/,
41
+ %r{docs/html},
42
+ %r{docs/man},
43
+ /\bTEMPLATE\.\w+\.tpl\b/,
44
+ /\.cvsignore/,
45
+ /\.s?o$/,
46
+ ]
47
+
48
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
49
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
50
+ AnsiAttributes = {
51
+ 'clear' => 0,
52
+ 'reset' => 0,
53
+ 'bold' => 1,
54
+ 'dark' => 2,
55
+ 'underline' => 4,
56
+ 'underscore' => 4,
57
+ 'blink' => 5,
58
+ 'reverse' => 7,
59
+ 'concealed' => 8,
60
+
61
+ 'black' => 30, 'on_black' => 40,
62
+ 'red' => 31, 'on_red' => 41,
63
+ 'green' => 32, 'on_green' => 42,
64
+ 'yellow' => 33, 'on_yellow' => 43,
65
+ 'blue' => 34, 'on_blue' => 44,
66
+ 'magenta' => 35, 'on_magenta' => 45,
67
+ 'cyan' => 36, 'on_cyan' => 46,
68
+ 'white' => 37, 'on_white' => 47
69
+ }
70
+
71
+ ErasePreviousLine = "\033[A\033[K"
72
+
73
+ ManifestHeader = (<<-"EOF").gsub( /^\t+/, '' )
74
+ #
75
+ # Distribution Manifest
76
+ # Created: #{Time::now.to_s}
77
+ #
78
+
79
+ EOF
80
+
81
+ ###############
82
+ module_function
83
+ ###############
84
+
85
+ # Create a string that contains the ANSI codes specified and return it
86
+ def ansiCode( *attributes )
87
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
88
+ attr = attributes.collect {|a| AnsiAttributes[a] ? AnsiAttributes[a] : nil}.compact.join(';')
89
+ if attr.empty?
90
+ return ''
91
+ else
92
+ return "\e[%sm" % attr
93
+ end
94
+ end
95
+
96
+
97
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling line-endings, etc.
98
+ def colorize( string, *attributes )
99
+ ending = string[/(\s)$/] || ''
100
+ string = string.rstrip
101
+ return ansiCode( attributes ) + string + ansiCode( 'reset' ) + ending
102
+ end
103
+
104
+
105
+ # Test for the presence of the specified <tt>library</tt>, and output a
106
+ # message describing the test using <tt>nicename</tt>. If <tt>nicename</tt>
107
+ # is <tt>nil</tt>, the value in <tt>library</tt> is used to build a default.
108
+ def testForLibrary( library, nicename=nil, progress=false )
109
+ nicename ||= library
110
+ message( "Testing for the #{nicename} library..." ) if progress
111
+ if $LOAD_PATH.detect {|dir|
112
+ File.exists?(File.join(dir,"#{library}.rb")) ||
113
+ File.exists?(File.join(dir,"#{library}.#{CONFIG['DLEXT']}"))
114
+ }
115
+ message( "found.\n" ) if progress
116
+ return true
117
+ else
118
+ message( "not found.\n" ) if progress
119
+ return false
120
+ end
121
+ end
122
+
123
+ # Test for the presence of the specified <tt>library</tt>, and output a
124
+ # message describing the problem using <tt>nicename</tt>. If
125
+ # <tt>nicename</tt> is <tt>nil</tt>, the value in <tt>library</tt> is used
126
+ # to build a default. If <tt>raaUrl</tt> and/or <tt>downloadUrl</tt> are
127
+ # specified, they are also use to build a message describing how to find the
128
+ # required library. If <tt>fatal</tt> is <tt>true</tt>, a missing library
129
+ # will cause the program to abort.
130
+ def testForRequiredLibrary( library, nicename=nil, raaUrl=nil, downloadUrl=nil, fatal=true )
131
+ nicename ||= library
132
+ unless testForLibrary( library, nicename )
133
+ msgs = [ "You are missing the required #{nicename} library.\n" ]
134
+ msgs << "RAA: #{raaUrl}\n" if raaUrl
135
+ msgs << "Download: #{downloadUrl}\n" if downloadUrl
136
+ if fatal
137
+ abort msgs.join('')
138
+ else
139
+ errorMessage msgs.join('')
140
+ end
141
+ end
142
+ return true
143
+ end
144
+
145
+ ### Output <tt>msg</tt> as a ANSI-colored program/section header (white on
146
+ ### blue).
147
+ def header( msg )
148
+ msg.chomp!
149
+ $stderr.puts ansiCode( 'bold', 'white', 'on_blue' ) + msg + ansiCode( 'reset' )
150
+ $stderr.flush
151
+ end
152
+
153
+ ### Output <tt>msg</tt> to STDERR and flush it.
154
+ def message( *msgs )
155
+ $stderr.print( msgs.join("\n") )
156
+ $stderr.flush
157
+ end
158
+
159
+ ### Output +msg+ to STDERR and flush it if $VERBOSE is true.
160
+ def verboseMsg( msg )
161
+ msg.chomp!
162
+ message( msg + "\n" ) if $VERBOSE
163
+ end
164
+
165
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
166
+ ### (white on red).
167
+ def errorMessage( msg )
168
+ message ansiCode( 'bold', 'white', 'on_red' ) + msg + ansiCode( 'reset' )
169
+ end
170
+
171
+ ### Output the specified <tt>msg</tt> as an ANSI-colored debugging message
172
+ ### (yellow on blue).
173
+ def debugMsg( msg )
174
+ return unless $DEBUG
175
+ msg.chomp!
176
+ $stderr.puts ansiCode( 'bold', 'yellow', 'on_blue' ) + ">>> #{msg}" + ansiCode( 'reset' )
177
+ $stderr.flush
178
+ end
179
+
180
+ ### Erase the previous line (if supported by your terminal) and output the
181
+ ### specified <tt>msg</tt> instead.
182
+ def replaceMessage( msg )
183
+ $stderr.print ErasePreviousLine
184
+ message( msg )
185
+ end
186
+
187
+ ### Output a divider made up of <tt>length</tt> hyphen characters.
188
+ def divider( length=75 )
189
+ $stderr.puts "\r" + ("-" * length )
190
+ end
191
+ alias :writeLine :divider
192
+
193
+
194
+ ### Output the specified <tt>msg</tt> colored in ANSI red and exit with a
195
+ ### status of 1.
196
+ def abort( msg )
197
+ print ansiCode( 'bold', 'red' ) + "Aborted: " + msg.chomp + ansiCode( 'reset' ) + "\n\n"
198
+ Kernel.exit!( 1 )
199
+ end
200
+
201
+
202
+ ### Output the specified <tt>promptString</tt> as a prompt (in green) and
203
+ ### return the user's input with leading and trailing spaces removed. If a
204
+ ### test is provided, the prompt will repeat until the test returns true.
205
+ ### An optional failure message can also be passed in.
206
+ def prompt( promptString, failure_msg="Try again." ) # :yields: response
207
+ promptString.chomp!
208
+ promptString << ":" unless /\W$/.match( promptString )
209
+ response = nil
210
+
211
+ begin
212
+ response = readline( ansiCode('bold', 'green') +
213
+ "#{promptString} " + ansiCode('reset') ) || ''
214
+ response.strip!
215
+ if block_given? && ! yield( response )
216
+ errorMessage( failure_msg + "\n\n" )
217
+ response = nil
218
+ end
219
+ end until response
220
+
221
+ return response
222
+ end
223
+
224
+
225
+ ### Prompt the user with the given <tt>promptString</tt> via #prompt,
226
+ ### substituting the given <tt>default</tt> if the user doesn't input
227
+ ### anything. If a test is provided, the prompt will repeat until the test
228
+ ### returns true. An optional failure message can also be passed in.
229
+ def promptWithDefault( promptString, default, failure_msg="Try again." )
230
+ response = nil
231
+
232
+ begin
233
+ response = prompt( "%s [%s]" % [ promptString, default ] )
234
+ response = default if response.empty?
235
+
236
+ if block_given? && ! yield( response )
237
+ errorMessage( failure_msg + "\n\n" )
238
+ response = nil
239
+ end
240
+ end until response
241
+
242
+ return response
243
+ end
244
+
245
+
246
+ $programs = {}
247
+
248
+ ### Search for the program specified by the given <tt>progname</tt> in the
249
+ ### user's <tt>PATH</tt>, and return the full path to it, or <tt>nil</tt> if
250
+ ### no such program is in the path.
251
+ def findProgram( progname )
252
+ unless $programs.key?( progname )
253
+ ENV['PATH'].split(File::PATH_SEPARATOR).each {|d|
254
+ file = File.join( d, progname )
255
+ if File.executable?( file )
256
+ $programs[ progname ] = file
257
+ break
258
+ end
259
+ }
260
+ end
261
+
262
+ return $programs[ progname ]
263
+ end
264
+
265
+
266
+ ### Search for the release version for the project in the specified
267
+ ### +directory+.
268
+ def extractVersion( directory='.' )
269
+ release = nil
270
+
271
+ Dir::chdir( directory ) do
272
+ if File::directory?( "CVS" )
273
+ verboseMsg( "Project is versioned via CVS. Searching for RELEASE_*_* tags..." )
274
+
275
+ if (( cvs = findProgram('cvs') ))
276
+ revs = []
277
+ output = %x{cvs log}
278
+ output.scan( /RELEASE_(\d+(?:_\d\w+)*)/ ) {|match|
279
+ rev = $1.split(/_/).collect {|s| Integer(s) rescue 0}
280
+ verboseMsg( "Found %s...\n" % rev.join('.') )
281
+ revs << rev
282
+ }
283
+
284
+ release = revs.sort.last
285
+ end
286
+
287
+ elsif File::directory?( '.svn' )
288
+ verboseMsg( "Project is versioned via Subversion" )
289
+
290
+ if (( svn = findProgram('svn') ))
291
+ output = %x{svn pg project-version}.chomp
292
+ unless output.empty?
293
+ verboseMsg( "Using 'project-version' property: %p" % output )
294
+ release = output.split( /[._]/ ).collect {|s| Integer(s) rescue 0}
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ return release
301
+ end
302
+
303
+
304
+ ### Find the current release version for the project in the specified
305
+ ### +directory+ and return its successor.
306
+ def extractNextVersion( directory='.' )
307
+ version = extractVersion( directory ) || [0,0,0]
308
+ version.compact!
309
+ version[-1] += 1
310
+
311
+ return version
312
+ end
313
+
314
+
315
+ # Pattern for extracting the name of the project from a Subversion URL
316
+ SVNUrlPath = %r{
317
+ .*/ # Skip all but the last bit
318
+ ([^/]+) # $1 = project name
319
+ / # Followed by / +
320
+ (?:
321
+ trunk | # 'trunk'
322
+ (
323
+ branches | # ...or branches/branch-name
324
+ tags # ...or tags/tag-name
325
+ )/\w
326
+ )
327
+ $ # bound to the end
328
+ }ix
329
+
330
+ ### Extract the project name (CVS Repository name) for the given +directory+.
331
+ def extractProjectName( directory='.' )
332
+ name = nil
333
+
334
+ Dir::chdir( directory ) do
335
+
336
+ # CVS-controlled
337
+ if File::directory?( "CVS" )
338
+ verboseMsg( "Project is versioned via CVS. Using repository name." )
339
+ name = File.open( "CVS/Repository", "r").readline.chomp
340
+ name.sub!( %r{.*/}, '' )
341
+
342
+ # Subversion-controlled
343
+ elsif File::directory?( '.svn' )
344
+ verboseMsg( "Project is versioned via Subversion" )
345
+
346
+ # If the machine has the svn tool, try to get the project name
347
+ if (( svn = findProgram( 'svn' ) ))
348
+
349
+ # First try an explicit property
350
+ output = shellCommand( svn, 'pg', 'project-name' )
351
+ if !output.empty?
352
+ verboseMsg( "Using 'project-name' property: %p" % output )
353
+ name = output.first.chomp
354
+
355
+ # If that doesn't work, try to figure it out from the URL
356
+ elsif (( uri = getSvnUri() ))
357
+ name = uri.path.sub( SVNUrlPath ) { $1 }
358
+ end
359
+ end
360
+ end
361
+
362
+ # Fall back to guessing based on the directory name
363
+ unless name
364
+ name = File::basename(File::dirname( File::expand_path(__FILE__) ))
365
+ end
366
+ end
367
+
368
+ return name
369
+ end
370
+
371
+
372
+ ### Extract the Subversion URL from the specified directory and return it as
373
+ ### a URI object.
374
+ def getSvnUri( directory='.' )
375
+ uri = nil
376
+
377
+ Dir::chdir( directory ) do
378
+ output = %x{svn info}
379
+ debugMsg( "Using info: %p" % output )
380
+
381
+ if /^URL: \s* ( .* )/xi.match( output )
382
+ uri = URI::parse( $1 )
383
+ end
384
+ end
385
+
386
+ return uri
387
+ end
388
+
389
+
390
+ ### (Re)make a manifest file in the specified +path+.
391
+ def makeManifest( path="MANIFEST" )
392
+ if File::exists?( path )
393
+ reply = promptWithDefault( "Replace current '#{path}'? [yN]", "n" )
394
+ return false unless /^y/i.match( reply )
395
+
396
+ verboseMsg "Replacing manifest at '#{path}'"
397
+ else
398
+ verboseMsg "Creating new manifest at '#{path}'"
399
+ end
400
+
401
+ files = []
402
+ verboseMsg( "Finding files...\n" )
403
+ Find::find( Dir::pwd ) do |f|
404
+ Find::prune if File::directory?( f ) &&
405
+ /^\./.match( File::basename(f) )
406
+ verboseMsg( " found: #{f}\n" )
407
+ files << f.sub( %r{^#{Dir::pwd}/?}, '' )
408
+ end
409
+ files = vetManifest( files )
410
+
411
+ verboseMsg( "Writing new manifest to #{path}..." )
412
+ File::open( path, File::WRONLY|File::CREAT|File::TRUNC ) do |ofh|
413
+ ofh.puts( ManifestHeader )
414
+ ofh.puts( files )
415
+ end
416
+ verboseMsg( "done." )
417
+ end
418
+
419
+
420
+ ### Read the specified <tt>manifestFile</tt>, which is a text file
421
+ ### describing which files to package up for a distribution. The manifest
422
+ ### should consist of one or more lines, each containing one filename or
423
+ ### shell glob pattern.
424
+ def readManifest( manifestFile="MANIFEST" )
425
+ verboseMsg "Building manifest..."
426
+ raise "Missing #{manifestFile}, please remake it" unless File.exists? manifestFile
427
+
428
+ manifest = IO::readlines( manifestFile ).collect {|line|
429
+ line.chomp
430
+ }.select {|line|
431
+ line !~ /^(\s*(#.*)?)?$/
432
+ }
433
+
434
+ filelist = []
435
+ for pat in manifest
436
+ verboseMsg "Adding files that match '#{pat}' to the file list"
437
+ filelist |= Dir.glob( pat ).find_all {|f| FileTest.file?(f)}
438
+ end
439
+
440
+ verboseMsg "found #{filelist.length} files.\n"
441
+ return filelist
442
+ end
443
+
444
+
445
+ ### Given a <tt>filelist</tt> like that returned by #readManifest, remove
446
+ ### the entries therein which match the Regexp objects in the given
447
+ ### <tt>antimanifest</tt> and return the resultant Array.
448
+ def vetManifest( filelist, antimanifest=ANTIMANIFEST )
449
+ origLength = filelist.length
450
+ verboseMsg "Vetting manifest..."
451
+
452
+ for regex in antimanifest
453
+ verboseMsg "\n\tPattern /#{regex.source}/ removed: " +
454
+ filelist.find_all {|file| regex.match(file)}.join(', ')
455
+ filelist.delete_if {|file| regex.match(file)}
456
+ end
457
+
458
+ verboseMsg "removed #{origLength - filelist.length} files from the list.\n"
459
+ return filelist
460
+ end
461
+
462
+
463
+ ### Combine a call to #readManifest with one to #vetManifest.
464
+ def getVettedManifest( manifestFile="MANIFEST", antimanifest=ANTIMANIFEST )
465
+ vetManifest( readManifest(manifestFile), antimanifest )
466
+ end
467
+
468
+
469
+ ### Given a documentation <tt>catalogFile</tt>, extract the title, if
470
+ ### available, and return it. Otherwise generate a title from the name of
471
+ ### the CVS module.
472
+ def findRdocTitle( catalogFile="docs/CATALOG" )
473
+
474
+ # Try extracting it from the CATALOG file from a line that looks like:
475
+ # Title: Foo Bar Module
476
+ title = findCatalogKeyword( 'title', catalogFile )
477
+
478
+ # If that doesn't work for some reason, use the name of the project.
479
+ title = extractProjectName()
480
+
481
+ return title
482
+ end
483
+
484
+
485
+ ### Given a documentation <tt>catalogFile</tt>, extract the name of the file
486
+ ### to use as the initally displayed page. If extraction fails, the
487
+ ### +default+ will be used if it exists. Returns +nil+ if there is no main
488
+ ### file to be found.
489
+ def findRdocMain( catalogFile="docs/CATALOG", default="README" )
490
+
491
+ # Try extracting it from the CATALOG file from a line that looks like:
492
+ # Main: Foo Bar Module
493
+ main = findCatalogKeyword( 'main', catalogFile )
494
+
495
+ # Try to make some educated guesses if that doesn't work
496
+ if main.nil?
497
+ basedir = File::dirname( __FILE__ )
498
+ basedir = File::dirname( basedir ) if /docs$/ =~ basedir
499
+
500
+ if File::exists?( File::join(basedir, default) )
501
+ main = default
502
+ end
503
+ end
504
+
505
+ return main
506
+ end
507
+
508
+
509
+ ### Given a documentation <tt>catalogFile</tt>, extract an upload URL for
510
+ ### RDoc.
511
+ def findRdocUpload( catalogFile="docs/CATALOG" )
512
+ findCatalogKeyword( 'upload', catalogFile )
513
+ end
514
+
515
+
516
+ ### Given a documentation <tt>catalogFile</tt>, extract a CVS web frontend
517
+ ### URL for RDoc.
518
+ def findRdocCvsURL( catalogFile="docs/CATALOG" )
519
+ findCatalogKeyword( 'webcvs', catalogFile )
520
+ end
521
+
522
+
523
+ ### Find one or more 'accessor' directives in the catalog if they exist and
524
+ ### return an Array of them.
525
+ def findRdocAccessors( catalogFile="docs/CATALOG" )
526
+ accessors = []
527
+ in_attr_section = false
528
+ indent = ''
529
+
530
+ if File::exists?( catalogFile )
531
+ verboseMsg "Extracting accessors from CATALOG file (%s).\n" % catalogFile
532
+
533
+ # Read lines from the catalog
534
+ File::foreach( catalogFile ) do |line|
535
+ debugMsg( " Examining line #{line.inspect}..." )
536
+
537
+ # Multi-line accessors
538
+ if in_attr_section
539
+ if /^#\s+([a-z0-9_]+(?:\s*=\s*.*)?)$/i.match( line )
540
+ debugMsg( " Found accessor: #$1" )
541
+ accessors << $1
542
+ next
543
+ end
544
+
545
+ debugMsg( " End of accessors section." )
546
+ in_attr_section = false
547
+
548
+ # Single-line accessor
549
+ elsif /^#\s*Accessors:\s*(\S+)$/i.match( line )
550
+ debugMsg( " Found single accessors line: #$1" )
551
+ vals = $1.split(/,/).collect {|val| val.strip }
552
+ accessors.replace( vals )
553
+
554
+ # Multi-line accessor header
555
+ elsif /^#\s*Accessors:\s*$/i.match( line )
556
+ debugMsg( " Start of accessors section." )
557
+ in_attr_section = true
558
+ end
559
+
560
+ end
561
+ end
562
+
563
+ debugMsg( "Found accessors: %s" % accessors.join(",") )
564
+ return accessors
565
+ end
566
+
567
+
568
+ ### Given a documentation <tt>catalogFile</tt>, try extracting the given
569
+ ### +keyword+'s value from it. Keywords are lines that look like:
570
+ ### # <keyword>: <value>
571
+ ### Returns +nil+ if the catalog file was unreadable or didn't contain the
572
+ ### specified +keyword+.
573
+ def findCatalogKeyword( keyword, catalogFile="docs/CATALOG" )
574
+ val = nil
575
+
576
+ if File::exists? catalogFile
577
+ verboseMsg "Extracting '#{keyword}' from CATALOG file (%s).\n" % catalogFile
578
+ File::foreach( catalogFile ) do |line|
579
+ debugMsg( "Examining line #{line.inspect}..." )
580
+ val = $1.strip and break if /^#\s*#{keyword}:\s*(.*)$/i.match( line )
581
+ end
582
+ end
583
+
584
+ return val
585
+ end
586
+
587
+
588
+ ### Given a documentation <tt>catalogFile</tt>, which is in the same format
589
+ ### as that described by #readManifest, read and expand it, and then return
590
+ ### a list of those files which appear to have RDoc documentation in
591
+ ### them. If <tt>catalogFile</tt> is nil or does not exist, the MANIFEST
592
+ ### file is used instead.
593
+ def findRdocableFiles( catalogFile="docs/CATALOG" )
594
+ startlist = []
595
+ if File.exists? catalogFile
596
+ verboseMsg "Using CATALOG file (%s).\n" % catalogFile
597
+ startlist = getVettedManifest( catalogFile )
598
+ else
599
+ verboseMsg "Using default MANIFEST\n"
600
+ startlist = getVettedManifest()
601
+ end
602
+
603
+ verboseMsg "Looking for RDoc comments in:\n"
604
+ startlist.select {|fn|
605
+ verboseMsg " #{fn}: "
606
+ found = false
607
+ File::open( fn, "r" ) {|fh|
608
+ fh.each {|line|
609
+ if line =~ /^(\s*#)?\s*=/ || line =~ /:\w+:/ || line =~ %r{/\*}
610
+ found = true
611
+ break
612
+ end
613
+ }
614
+ }
615
+
616
+ verboseMsg( (found ? "yes" : "no") + "\n" )
617
+ found
618
+ }
619
+ end
620
+
621
+
622
+ ### Open a file and filter each of its lines through the given block a
623
+ ### <tt>line</tt> at a time. The return value of the block is used as the
624
+ ### new line, or omitted if the block returns <tt>nil</tt> or
625
+ ### <tt>false</tt>.
626
+ def editInPlace( file, testMode=false ) # :yields: line
627
+ raise "No block specified for editing operation" unless block_given?
628
+
629
+ tempName = "#{file}.#{$$}"
630
+ File::open( tempName, File::RDWR|File::CREAT, 0600 ) {|tempfile|
631
+ File::open( file, File::RDONLY ) {|fh|
632
+ fh.each {|line|
633
+ newline = yield( line ) or next
634
+ tempfile.print( newline )
635
+ $deferr.puts "%p -> %p" % [ line, newline ] if
636
+ line != newline
637
+ }
638
+ }
639
+ }
640
+
641
+ if testMode
642
+ File::unlink( tempName )
643
+ else
644
+ File::rename( tempName, file )
645
+ end
646
+ end
647
+
648
+
649
+ ### Execute the specified shell <tt>command</tt>, read the results, and
650
+ ### return them. Like a %x{} that returns an Array instead of a String.
651
+ def shellCommand( *command )
652
+ raise "Empty command" if command.empty?
653
+
654
+ cmdpipe = IO::popen( command.join(' '), 'r' )
655
+ return cmdpipe.readlines
656
+ end
657
+
658
+
659
+ ### Execute a block with $VERBOSE set to +false+, restoring it to its
660
+ ### previous value before returning.
661
+ def verboseOff
662
+ raise LocalJumpError, "No block given" unless block_given?
663
+
664
+ thrcrit = Thread.critical
665
+ oldverbose = $VERBOSE
666
+ begin
667
+ Thread.critical = true
668
+ $VERBOSE = false
669
+ yield
670
+ ensure
671
+ $VERBOSE = oldverbose
672
+ Thread.critical = false
673
+ end
674
+ end
675
+
676
+
677
+ ### Try the specified code block, printing the given
678
+ def try( msg, bind=TOPLEVEL_BINDING )
679
+ result = ''
680
+ if msg =~ /^to\s/
681
+ message "Trying #{msg}...\n"
682
+ else
683
+ message msg + "\n"
684
+ end
685
+
686
+ begin
687
+ rval = nil
688
+ if block_given?
689
+ rval = yield
690
+ else
691
+ file, line = caller(1)[0].split(/:/,2)
692
+ rval = eval( msg, bind, file, line.to_i )
693
+ end
694
+
695
+ PP.pp( rval, result )
696
+
697
+ rescue Exception => err
698
+ if err.backtrace
699
+ nicetrace = err.backtrace.delete_if {|frame|
700
+ /in `(try|eval)'/ =~ frame
701
+ }.join("\n\t")
702
+ else
703
+ nicetrace = "Exception had no backtrace"
704
+ end
705
+
706
+ result = err.message + "\n\t" + nicetrace
707
+
708
+ ensure
709
+ divider
710
+ message result.chomp + "\n"
711
+ divider
712
+ $deferr.puts
713
+ end
714
+ end
715
+ end
716
+
717
+
718
+ if __FILE__ == $0
719
+ # $DEBUG = true
720
+ include UtilityFunctions
721
+
722
+ projname = extractProjectName()
723
+ header "Project: #{projname}"
724
+
725
+ ver = extractVersion() || [0,0,1]
726
+ puts "Version: %s\n" % ver.join('.')
727
+
728
+ if File::directory?( "docs" )
729
+ puts "Rdoc:",
730
+ " Title: " + findRdocTitle(),
731
+ " Main: " + findRdocMain(),
732
+ " Upload: " + findRdocUpload(),
733
+ " SCCS URL: " + findRdocCvsURL(),
734
+ " Accessors: " + findRdocAccessors().join(",")
735
+ end
736
+
737
+ puts "Manifest:",
738
+ " " + getVettedManifest().join("\n ")
739
+ end