forked from tomaz/appledoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDocSetOutputGenerator.m
475 lines (406 loc) · 20.5 KB
/
DocSetOutputGenerator.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
//
// DocSetOutputGenerator.m
// appledoc
//
// Created by Tomaz Kragelj on 11.6.09.
// Copyright (C) 2009, Tomaz Kragelj. All rights reserved.
//
#import "DocSetOutputGenerator.h"
#import "CommandLineParser.h"
#import "LoggingProvider.h"
#import "Systemator.h"
@implementation DocSetOutputGenerator
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark OutputInfoProvider protocol implementation
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (NSString*) outputFilesExtension
{
return @".html";
}
//----------------------------------------------------------------------------------------
- (NSString*) outputBasePath
{
return [cmd.outputPath stringByAppendingPathComponent:@"docset"];
}
//----------------------------------------------------------------------------------------
- (NSString*) outputContentsPath
{
return [self.outputBasePath stringByAppendingPathComponent:@"Contents"];
}
//----------------------------------------------------------------------------------------
- (NSString*) outputResourcesPath
{
return [self.outputContentsPath stringByAppendingPathComponent:@"Resources"];
}
//----------------------------------------------------------------------------------------
- (NSString*) outputDocumentsPath
{
return [self.outputResourcesPath stringByAppendingPathComponent:@"Documents"];
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Specific output generation entry points
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (BOOL) isOutputGenerationEnabled
{
return cmd.createDocSet;
}
//----------------------------------------------------------------------------------------
- (void) generateSpecificOutput
{
if (!self.documentationFilesInfoProvider)
[Systemator throwExceptionWithName:kTKConverterException
withDescription:@"documentationFilesInfoProvider not set"];
[self createDocSetSourcePlistFile];
[self createDocSetNodesFile];
[self createDocSetTokesFile];
[self createDocSetBundle];
}
//----------------------------------------------------------------------------------------
- (void) createOutputDirectories
{
// Note that we only manually create temporary documentation set directory here,
// if the documentation set is installed, it will be copied as a bundle to the
// appropriate path.
[Systemator createDirectory:[self outputBasePath]];
[Systemator createDirectory:[self outputContentsPath]];
[Systemator createDirectory:[self outputResourcesPath]];
[Systemator createDirectory:[self outputDocumentsPath]];
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Documentation set handling
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (void) createDocSetSourcePlistFile
{
// If the docset source plist doesn't yet exist, create it, otherwise read the data
// from it in case the user changes it.
if (![manager fileExistsAtPath:cmd.docsetSourcePlistPath])
{
logNormal(@"Creating DocSet info plist...");
// Create the dictionary and populate it with data.
NSMutableDictionary* docsetInfo = [[NSMutableDictionary alloc] init];
[docsetInfo setObject:cmd.docsetBundleID forKey:(NSString*)kCFBundleIdentifierKey];
[docsetInfo setObject:cmd.projectName forKey:(NSString*)kCFBundleNameKey];
[docsetInfo setObject:cmd.docsetBundleFeed forKey:@"DocSetFeedName"];
// Convert the dictionary to property list. Exit if anything goes wrong.
@try
{
[Systemator writePropertyList:docsetInfo toFile:cmd.docsetSourcePlistPath];
}
@finally
{
[docsetInfo release];
}
logInfo(@"Finished creating DocSet info plist.");
}
// If the plist already exists, read the data from it and set it to command line
// parser so the rest of the code execute after this already uses it. Note that we
// don't have to handle this if the file doesn't exists - we just created it and
// populated with default options in such case.
else
{
logNormal(@"Reading DocSet info plist data...");
@try
{
// Read the property list from the file and extract the properties.
NSString* value = nil;
NSDictionary* docsetInfo = [Systemator readPropertyListFromFile:cmd.docsetSourcePlistPath];
value = [docsetInfo objectForKey:(NSString*)kCFBundleIdentifierKey];
if (value)
{
logVerbose(@"- Updating DocSet bundle ID '%@' from info plist...", value);
cmd.docsetBundleID = value;
}
value = [docsetInfo objectForKey:@"DocSetFeedName"];
if (value)
{
logVerbose(@"- Updating DocSet bundle feed '%@' from info plist...", value);
cmd.docsetBundleFeed = value;
}
}
@catch (NSException* e)
{
logError(@"Failed readong DocSet info plist data from '%@', error was %@!",
cmd.docsetSourcePlistPath,
[e reason]);
@throw;
}
logInfo(@"Finished reading DocSet info plist data.");
}
}
//----------------------------------------------------------------------------------------
- (void) createDocSetNodesFile
{
logNormal(@"Creating DocSet Nodes.xml file...");
NSAutoreleasePool* loopAutoreleasePool = [[NSAutoreleasePool alloc] init];
NSXMLDocument* document = [NSXMLDocument document];
NSString* indexFileName = [documentationFilesInfoProvider outputIndexFilename];
NSString* hierarchyFileName = [documentationFilesInfoProvider outputHierarchyFilename];
// Create the version and ecoding elements.
[document setVersion:@"1.0"];
[document setCharacterEncoding:@"UTF-8"];
// Create the root <DocSetNodes version="1.0"> element.
NSXMLElement* rootElement = [NSXMLNode elementWithName:@"DocSetNodes"];
[rootElement addAttribute:[NSXMLNode attributeWithName:@"version" stringValue:@"1.0"]];
[document setRootElement:rootElement];
// Create <TOC> element.
NSXMLElement* tocElement = [NSXMLNode elementWithName:@"TOC"];
[rootElement addChild:tocElement];
// Create <Node> element for main index file and all it's descriptors.
NSXMLElement* indexNodeElement = [NSXMLNode elementWithName:@"Node"];
[tocElement addChild:indexNodeElement];
NSXMLElement* indexNameElement = [NSXMLNode elementWithName:@"Name" stringValue:cmd.projectName];
[indexNodeElement addChild:indexNameElement];
NSXMLElement* indexPathElement = [NSXMLNode elementWithName:@"Path" stringValue:indexFileName];
[indexNodeElement addChild:indexPathElement];
NSXMLElement* indexSubnodesElement = [NSXMLNode elementWithName:@"Subnodes"];
[indexNodeElement addChild:indexSubnodesElement];
// Since we will create the structure that groups classes, categories and protocols
// we'll use the directories key - it is structured in exactly the desired way.
NSDictionary* directories = [database objectForKey:kTKDataMainDirectoriesKey];
for (NSString* directoryName in directories)
{
// Create the node for the directory. The directory node is of type folder and
// since we don't use a separate html page for it, we map it to the main index.
NSXMLElement* directoryNodeElement = [NSXMLNode elementWithName:@"Node"];
[directoryNodeElement addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"folder"]];
[indexSubnodesElement addChild:directoryNodeElement];
NSXMLElement* directoryNameElement = [NSXMLNode elementWithName:@"Name" stringValue:directoryName];
[directoryNodeElement addChild:directoryNameElement];
NSXMLElement* directoryPathElement = [NSXMLNode elementWithName:@"Path" stringValue:indexFileName];
[directoryNodeElement addChild:directoryPathElement];
NSXMLElement* directorySubnodesElement = [NSXMLNode elementWithName:@"Subnodes"];
[directoryNodeElement addChild:directorySubnodesElement];
// The directory is represented by an array of object data dictionaries which are
// simply links to the main "Objects" dictionary, so we can use the data from there.
NSArray* directoryObjects = [directories objectForKey:directoryName];
for (NSDictionary* objectData in directoryObjects)
{
NSString* objectName = [objectData objectForKey:kTKDataObjectNameKey];
NSString* objectPath = [documentationFilesInfoProvider outputObjectFilenameForObject:objectData];
NSXMLElement* objectElement = [NSXMLNode elementWithName:@"Node"];
[directorySubnodesElement addChild:objectElement];
NSXMLElement* objectNameElement = [NSXMLNode elementWithName:@"Name" stringValue:objectName];
[objectElement addChild:objectNameElement];
NSXMLElement* objectPathElement = [NSXMLNode elementWithName:@"Path" stringValue:objectPath];
[objectElement addChild:objectPathElement];
}
}
// At the end of the directories create class hierarchy.
NSXMLElement* hierarchyNodeElement = [NSXMLNode elementWithName:@"Node"];
[hierarchyNodeElement addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"folder"]];
[indexSubnodesElement addChild:hierarchyNodeElement];
NSXMLElement* hierarchyNameElement = [NSXMLNode elementWithName:@"Name" stringValue:@"Class hierarchy"];
[hierarchyNodeElement addChild:hierarchyNameElement];
NSXMLElement* hierarchyPathElement = [NSXMLNode elementWithName:@"Path" stringValue:hierarchyFileName];
[hierarchyNodeElement addChild:hierarchyPathElement];
NSXMLElement* hierarchySubnodesElement = [NSXMLNode elementWithName:@"Subnodes"];
[hierarchyNodeElement addChild:hierarchySubnodesElement];
// Scan for all classes in the hierarchy.
NSMutableDictionary* hierarchies = [database objectForKey:kTKDataMainHierarchiesKey];
for (NSString* objectName in hierarchies)
{
NSDictionary* hierarchyData = [hierarchies objectForKey:objectName];
[self addDocSetNodeToElement:hierarchySubnodesElement fromHierarchyData:hierarchyData];
}
// Save the document.
NSError* error = nil;
NSString* filename = [self.outputResourcesPath stringByAppendingPathComponent:@"Nodes.xml"];
NSData* documentData = [document XMLDataWithOptions:NSXMLNodePrettyPrint];
if (![documentData writeToFile:filename options:0 error:&error])
{
[loopAutoreleasePool drain];
logError(@"Failed saving DocSet Nodes.xml to '%@'!", filename);
[Systemator throwExceptionWithName:kTKConverterException basedOnError:error];
}
[loopAutoreleasePool drain];
logInfo(@"Finished creating DocSet Nodes.xml file.");
}
//----------------------------------------------------------------------------------------
- (void) createDocSetTokesFile
{
logNormal(@"Creating DocSet Tokens.xml file...");
NSAutoreleasePool* loopAutoreleasePool = [[NSAutoreleasePool alloc] init];
NSXMLDocument* document = [NSXMLDocument document];
// Create the version and ecoding elements.
[document setVersion:@"1.0"];
[document setCharacterEncoding:@"UTF-8"];
// Create the root <Tokens version="1.0"> element.
NSXMLElement* rootElement = [NSXMLNode elementWithName:@"Tokens"];
[rootElement addAttribute:[NSXMLNode attributeWithName:@"version" stringValue:@"1.0"]];
[document setRootElement:rootElement];
// The root <Tokens> element contains <File> elements representing individual
// object files, so we can enumerate over the objects dictionary.
NSDictionary* objects = [database objectForKey:kTKDataMainObjectsKey];
for (NSString* objectName in objects)
{
// Get required object data.
NSDictionary* objectData = [objects objectForKey:objectName];
NSXMLDocument* objectDocument = [objectData objectForKey:kTKDataObjectMarkupKey];
NSString* objectKind = [objectData objectForKey:kTKDataObjectKindKey];
NSString* objectRelPath = [documentationFilesInfoProvider outputObjectFilenameForObject:objectData];
// Prepare the object identifier.
NSString* objectIdentifier = nil;
if ([objectKind isEqualToString:@"category"])
objectIdentifier = @"//apple_ref/occ/cat/";
else if ([objectKind isEqualToString:@"protocol"])
objectIdentifier = @"//apple_ref/occ/intf/";
else
objectIdentifier = @"//apple_ref/occ/cl/";
objectIdentifier = [objectIdentifier stringByAppendingString:objectName];
// Prepare the object filename.
NSArray* fileNodes = [objectDocument nodesForXPath:@"object/file" error:nil];
NSString* objectSrcFilename = ([fileNodes count] > 0) ? [[fileNodes objectAtIndex:0] stringValue] : @"";
// Prepare the object description.
NSArray* descriptionNodes = [objectDocument nodesForXPath:@"object/description" error:nil];
NSString* objectDescription = ([descriptionNodes count] > 0) ? [[descriptionNodes objectAtIndex:0] stringValue] : @"";
// Create the <File> element.
NSXMLElement* fileElement = [NSXMLNode elementWithName:@"File"];
[fileElement addAttribute:[NSXMLNode attributeWithName:@"path" stringValue:objectRelPath]];
[rootElement addChild:fileElement];
// Add the object <Token> element.
NSXMLElement* objectTokenElement = [NSXMLNode elementWithName:@"Token"];
[fileElement addChild:objectTokenElement];
NSXMLElement* objectIdentElement = [NSXMLNode elementWithName:@"TokenIdentifier" stringValue:objectIdentifier];
[objectTokenElement addChild:objectIdentElement];
NSXMLElement* objectDeclaredInElement = [NSXMLNode elementWithName:@"DeclaredIn" stringValue:objectSrcFilename];
[objectTokenElement addChild:objectDeclaredInElement];
NSXMLElement* objectAbstractElement = [NSXMLNode elementWithName:@"Abstract" stringValue:objectDescription];
[objectTokenElement addChild:objectAbstractElement];
// Handle all object members.
NSArray* memberNodes = [objectDocument nodesForXPath:@"//member" error:nil];
for (NSXMLElement* memberNode in memberNodes)
{
// Prepare member name.
NSArray* memberNameNodes = [memberNode nodesForXPath:@"name" error:nil];
NSString* memberName = ([memberNameNodes count] > 0) ?
[[memberNameNodes objectAtIndex:0] stringValue] : @"";
// Prepare member file.
NSArray* memberFileNodes = [memberNode nodesForXPath:@"file" error:nil];
NSString* memberFile = ([memberFileNodes count] > 0) ?
[[memberFileNodes objectAtIndex:0] stringValue] : @"";
// Prepare member prototype.
NSArray* memberPrototypeNodes = [memberNode nodesForXPath:@"prototype" error:nil];
NSString* memberPrototype = ([memberPrototypeNodes count] > 0) ?
[[memberPrototypeNodes objectAtIndex:0] stringValue] : @"";
// Prepare member description.
NSArray* memberDescNodes = [memberNode nodesForXPath:@"description" error:nil];
NSString* memberDescription = ([memberDescNodes count] > 0) ?
[[memberDescNodes objectAtIndex:0] stringValue] : @"";
// Prepare member identifier.
NSString* memberIdentifier = nil;
if ([objectKind isEqualToString:@"class"])
memberIdentifier = @"//apple_ref/occ/instm/";
else
memberIdentifier = @"//apple_ref/occ/intfm/";
memberIdentifier = [memberIdentifier stringByAppendingFormat:@"%@/%@", objectName, memberName];
// Add the member <Token> element.
NSXMLElement* memberTokenElement = [NSXMLNode elementWithName:@"Token"];
[fileElement addChild:memberTokenElement];
NSXMLElement* memberIdentElement = [NSXMLNode elementWithName:@"TokenIdentifier" stringValue:memberIdentifier];
[memberTokenElement addChild:memberIdentElement];
NSXMLElement* memberAnchorElement = [NSXMLNode elementWithName:@"Anchor" stringValue:memberName];
[memberTokenElement addChild:memberAnchorElement];
NSXMLElement* memberDeclaredInElement = [NSXMLNode elementWithName:@"DeclaredIn" stringValue:memberFile];
[memberTokenElement addChild:memberDeclaredInElement];
NSXMLElement* memberDeclElement = [NSXMLNode elementWithName:@"Declaration" stringValue:memberPrototype];
[memberTokenElement addChild:memberDeclElement];
NSXMLElement* memberAbstractElement = [NSXMLNode elementWithName:@"Abstract" stringValue:memberDescription];
[memberTokenElement addChild:memberAbstractElement];
}
}
// Save the document.
NSError* error = nil;
NSString* filename = [self.outputResourcesPath stringByAppendingPathComponent:@"Tokens.xml"];
NSData* documentData = [document XMLDataWithOptions:NSXMLNodePrettyPrint];
if (![documentData writeToFile:filename options:0 error:&error])
{
[loopAutoreleasePool drain];
logError(@"Failed saving DocSet Tokens.xml to '%@'!", filename);
[Systemator throwExceptionWithName:kTKConverterException basedOnError:error];
}
[loopAutoreleasePool drain];
logInfo(@"Finished creating DocSet Tokens.xml file.");
}
//----------------------------------------------------------------------------------------
- (void) createDocSetBundle
{
logNormal(@"Creating DocSet bundle...");
// First copy the info plist file into the contents output.
NSString* plistDestPath = [self.outputContentsPath stringByAppendingPathComponent:@"Info.plist"];
logVerbose(@"- Copying info plist file to '%@'...", plistDestPath);
[Systemator copyItemAtPath:cmd.docsetSourcePlistPath toPath:plistDestPath];
// Copy all html files to the bundle structure.
logVerbose(@"- Copying documentation to '%@'...", self.outputDocumentsPath);
[Systemator copyItemAtPath:[documentationFilesInfoProvider outputBasePath]
toPath:self.outputDocumentsPath];
// Index the documentation set.
logVerbose(@"- Indexing DocSet...");
[Systemator runTask:cmd.docsetutilCommandLine, @"index", [self outputBasePath], nil];
// Copy the documentation set to the proper directory. First we need to remove
// previous files otherwise copying will fail. Note that we need to create the
// documentation set directory otherwise copying fails too...
[Systemator createDirectory:cmd.docsetInstallPath];
NSString* docsetInstallPath = [cmd.docsetInstallPath stringByAppendingPathComponent:cmd.docsetBundleID];
logVerbose(@"- Copying DocSet bundle to '%@'...", docsetInstallPath);
[Systemator copyItemAtPath:[self outputBasePath] toPath:docsetInstallPath];
// Install the script to the Xcode.
logVerbose(@"- Installing DocSet to Xcode...");
NSMutableString* installCode = [NSMutableString string];
[installCode appendString:@"tell application \"Xcode\"\n"];
[installCode appendFormat:@"\tload documentation set with path \"%@\"\n", docsetInstallPath];
[installCode appendString:@"end tell"];
NSAppleScript* installScript = [[NSAppleScript alloc] initWithSource:installCode];
NSDictionary* errorDict = nil;
if (![installScript executeAndReturnError:&errorDict])
{
[installScript release];
NSString* message = [NSString stringWithFormat:@"Installation of DocSet failed with message:\n'%@'!",
[errorDict objectForKey:NSAppleScriptErrorMessage]];
logError(@"Failed installing DocSet to Xcode documentation!");
[Systemator throwExceptionWithName:kTKConverterException withDescription:message];
return;
}
[installScript release];
logInfo(@"Finished creating DocSet bundle.");
}
//----------------------------------------------------------------------------------------
- (void) addDocSetNodeToElement:(NSXMLElement*) parent
fromHierarchyData:(NSDictionary*) data
{
NSDictionary* children = [data objectForKey:kTKDataHierarchyChildrenKey];
NSDictionary* objectData = [data objectForKey:kTKDataHierarchyObjectDataKey];
NSString* objectName = [data objectForKey:kTKDataHierarchyObjectNameKey];
NSString* objectPath = [objectData objectForKey:kTKDataObjectRelPathKey];
objectPath = objectPath ?
[documentationFilesInfoProvider outputObjectFilenameForObject:objectData] :
[documentationFilesInfoProvider outputHierarchyFilename];
// Create the main node that will represent the object.
NSXMLElement* node = [NSXMLNode elementWithName:@"Node"];
[parent addChild:node];
// Create the name and path subnodes. Note that the path will be the main hierarchy
// index file if the node is not documented.
NSXMLElement* nameElement = [NSXMLNode elementWithName:@"Name" stringValue:objectName];
[node addChild:nameElement];
NSXMLElement* pathElement = [NSXMLNode elementWithName:@"Path" stringValue:objectPath];
[node addChild:pathElement];
// If there are children, set the node type to folder and add subnodes.
if ([children count] > 0)
{
[node addAttribute:[NSXMLNode attributeWithName:@"type" stringValue:@"folder"]];
NSXMLElement* subnodesElement = [NSXMLNode elementWithName:@"Subnodes"];
[node addChild:subnodesElement];
for (NSString* childName in children)
{
NSDictionary* childHierarchyData = [children objectForKey:childName];
[self addDocSetNodeToElement:subnodesElement fromHierarchyData:childHierarchyData];
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
//////////////////////////////////////////////////////////////////////////////////////////
@synthesize documentationFilesInfoProvider;
@end