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.
- data/CHANGES +506 -0
- data/LICENSE +340 -0
- data/README +99 -0
- data/bin/bluecloth +83 -0
- data/install.rb +185 -0
- data/lib/bluecloth.rb +1144 -0
- data/test.rb +117 -0
- data/tests/00_Class.tests.rb +71 -0
- data/tests/05_Markdown.tests.rb +1541 -0
- data/tests/10_Bug.tests.rb +67 -0
- data/tests/15_Contrib.tests.rb +132 -0
- data/tests/bctestcase.rb +283 -0
- data/tests/data/antsugar.txt +34 -0
- data/tests/data/hr-dos.txt +4 -0
- data/tests/data/ml-announce.txt +17 -0
- data/tests/data/re-overflow.txt +67 -0
- data/tests/data/re-overflow2.txt +281 -0
- data/utils.rb +739 -0
- metadata +74 -0
@@ -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,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
|
+
|
data/utils.rb
ADDED
@@ -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
|