/** This tool produces GSDoc files from source files.
The autogsdoc tool is a command-line utility that helps developers produce reference documentation for GNUstep APIs. It also enables developers to write and maintain other documentation in XML and have it converted to HTML. In detail, autogsdoc will:
synopsis: autogsdoc (options) (files)
(options) described below
(files) .h
, .m
, .gsdoc
, and/or .html
files, in any order.
The most common usage this is to run the command with one or more header file names as arguments ... the tool will automatically parse corresponding source files in the same directory as the headers (or the current directory, or the directory specified using the DocumentationDirectory default), and produce GSDoc and HTML files as output. For best results this mode should be run from the directory containing the source files.
GSDoc files may also be given directly in addition or by themselves, and
will be converted to HTML. See the
Finally, HTML files may be given on the command line. Cross-references to other parts of code documentation found within them will be rewritten based on what is found in the project currently.
The source code parser will automatically produce GSDoc documents listing the methods in the classes found in the source files, and it will include text from specially formatted comments from the source files.
Any comment beginning with slash and two asterisks rather than
the common slash and single asterisk, is taken to be GSDoc markup, to
be use as the description of the class or method following it. This
comment text is reformatted and then inserted into the output.
Where multiple comments are associated with the same item, they are
joined together with a line break (<br />) between each if
necessary.
Within a comment the special markup <ignore> and </ignore>
may be used to tell autogsdoc to completely ignore the sourcecode
between these two pieces of markup (ie. the parser will skip from the
point just before it is told to start ignoring, to just after the point
where it is told to stop (or end of file if that occurs first).
The tool can easily be used to document programs as well as libraries, simply by giving it the name of the source file containing the main() function of the program - it takes the special comments from that function and handles them specially, inserting them as a section at the end of the first chapter of the document (it creates the first chapter if necessary).
Options are described in the section Arguments and Defaults below.
There are some cases where special extra processing is performed, predominantly in the first comment found in the source file, from which various chunks of GSDoc markup may be extracted and placed into appropriate locations in the output document -
AutogsdocSource
: is found, the
remainder of the line is taken as a source file name to be used
instead of making the assumption that each .h file processed uses
a .m file of the same name. You may supply multiple
AutogsdocSource
: lines where a header file declares
items which are defined in multiple source files.DocumentationDirectory
default.
Author
: name <email-address>',
or 'By
: name <email-address>',
or 'Author
: name' or 'By
: name'
will be recognised and converted to an author element,
possibly containing an email element.
NBThe markup just described may be used within class, category, or protocol documentation ... if so, it is extracted and wrapped round the rest of the documentation for the class as the class's chapter. The rest of the class documentation is normally inserted at the end of the chapter, but may instead be substituted in in place of the <unit /> pseudo-element within the <chapter> element.
In comments being used to provide text for a method description, the following markup is removed from the text and handled specially -
Generally, the text in comments is reformatted to standardise and
indent it nicely ... the reformatting is not performed on
any text inside an <example> element.
When the text is reformatted, it is broken into whitespace separated
'words' which are then subjected to some extra processing ...
[
NSObject-init]
,
will create a reference to the init method of NSObject (either the
class proper, or any of its categories), while
[
(NSCopying)-copyWithZone:]
, creates a
reference to a method in the NSCopying protocol.
The tool accepts certain user defaults (which can of course be
supplied as command-line arguments by prepending '-' before the default
name and giving the value afterwards, as in -Clean YES
):
"-Declared Foundation"
when generating documentation for the GNUstep base library. This
would result in the documentation saying that NSString is declared
in Foundation/NSString.h
.gsdoc
files
that are passed in on the command line. Any path information given
for these files is removed and they are
searched for in DocumentationDirectory
(even though they
may not have been autogenerated).
make
system, which is
expected to manage dependency checking itself.
.igsdoc
extension, and the
indexing information from those files is used.Project
).
Foo
is found in the file Foo
, and the
path associated with that project index is /usr/doc/proj
,
Then generated html output may reference the class as being in
/usr/doc/prj/Foo.html
. Note that a dictionary may be
given on the command line by using the standard PropertyList format
(not the XML format of OS X), using semicolons as line-separators, and
enclosing it in single quotes.
.igsdoc
extension, and the
indexing information from those files is used.
The 'Up' default is used to specify the name of a document which
should be used as the 'up' link for any other documents used.
This name must not include a path or extension.
Generally, the document referred to by this default should be a
hand-edited GSDoc document which should have a back
section containing a project index. e.g.
The autogsdoc tool internally makes use of the following four classes-
tags when possible",
@"GenerateParagraphMarkup",
nil];
argSet = [NSSet setWithArray: [argsRecognized allKeys]];
argsGiven = [[NSProcessInfo processInfo] arguments];
for (i = 0; i < [argsGiven count]; i++)
{
arg = [argsGiven objectAtIndex: i];
if ([arg characterAtIndex: 0] == '-')
{
opt = ([arg characterAtIndex: 1] == '-') ?
[arg substringFromIndex: 2] : [arg substringFromIndex: 1];
}
else
{
continue;
}
if (![argSet containsObject: opt] || [@"help" isEqual: opt])
{
NSArray *args = [argsRecognized allKeys];
GSPrintf(stderr, @"Usage:\n");
GSPrintf(stderr, [NSString stringWithFormat:
@" %@ [options] [files]\n", [argsGiven objectAtIndex: 0]]);
GSPrintf(stderr, @"\n Options:\n");
for (i = 0; i < [args count]; i++)
{
arg = [args objectAtIndex: i];
GSPrintf(stderr,
[NSString stringWithFormat: @" -%@\t%@\n\n",
arg, [argsRecognized objectForKey: arg]]);
}
GSPrintf(stderr, @"\n Files:\n");
GSPrintf(stderr, @" [.h files]\t\tMust be in 'HeaderDirectory'\n");
GSPrintf(stderr,
@" [.m files]\t\tAbsolute or relative path (from here)\n");
GSPrintf(stderr,
@" [.gsdoc files]\tMust be in 'DocumentationDirectory'\n\n");
exit(1);
}
}
mgr = [NSFileManager defaultManager];
if ([GSXMLParser respondsToSelector: @selector(setDTDs:)])
{
[GSXMLParser setDTDs: [defs stringForKey: @"DTDs"]];
}
verbose = [defs boolForKey: @"Verbose"];
warn = [defs boolForKey: @"Warn"];
ignoreDependencies = [defs boolForKey: @"IgnoreDependencies"];
showDependencies = [defs boolForKey: @"ShowDependencies"];
if (ignoreDependencies == YES)
{
if (showDependencies == YES)
{
showDependencies = NO;
NSLog(@"ShowDependencies(YES) used with IgnoreDependencies(YES)");
}
}
obj = [defs objectForKey: @"GenerateHtml"];
if (obj != nil)
{
generateHtml = [defs boolForKey: @"GenerateHtml"];
}
obj = [defs objectForKey: @"InstanceVariablesAtEnd"];
if (obj != nil)
{
instanceVarsAtEnd = [defs boolForKey: @"InstanceVariablesAtEnd"];
}
declared = [defs stringForKey: @"Declared"];
project = [defs stringForKey: @"Project"];
refsName = [[project stringByAppendingPathExtension: @"igsdoc"] copy];
headerDirectory = [defs stringForKey: @"HeaderDirectory"];
if (headerDirectory == nil)
{
headerDirectory = @"";
}
documentationDirectory = [defs stringForKey: @"DocumentationDirectory"];
if (documentationDirectory == nil)
{
documentationDirectory = @"";
}
if ([documentationDirectory length] > 0
&& [mgr fileExistsAtPath: documentationDirectory] == NO)
{
[mgr createDirectoryAtPath: documentationDirectory attributes: nil];
}
symbolDeclsFile = [documentationDirectory
stringByAppendingPathComponent: @"OrderedSymbolDeclarations.plist"];
proc = [NSProcessInfo processInfo];
if (proc == nil)
{
NSLog(@"unable to get process information!");
exit(EXIT_FAILURE);
}
/*
* 2) Build an array of files to be processed.
*/
obj = [defs stringForKey: @"Files"];
if (obj != nil)
{
files = [NSArray arrayWithContentsOfFile: obj];
if (files == nil)
{
NSLog(@"Failed to load files from '%@'", obj);
exit(EXIT_FAILURE);
}
firstFile = 0; // Not an argument list ... read from index 0
}
else
{
files = [proc arguments];
firstFile = 1; // An argument list ... ignore the program name.
}
sFiles = [NSMutableArray array];
gFiles = [NSMutableArray array];
hFiles = [NSMutableArray array];
count = [files count];
if (verbose == YES)
{
NSLog(@"Proc ... %@", proc);
NSLog(@"Name ... %@", [proc processName]);
NSLog(@"Files ... %@", files);
NSLog(@"HeaderDirectory ... %@", headerDirectory);
NSLog(@"DocumentationDirectory ... %@", documentationDirectory);
}
for (i = firstFile; i < count; i++)
{
NSString *arg = [files objectAtIndex: i];
if ([arg hasPrefix: @"-"] == YES)
{
i++; // a default
}
else if ([arg hasSuffix: @".h"] == YES)
{
[sFiles addObject: arg];
}
else if (([arg hasSuffix: @".m"] == YES)
|| ([arg hasSuffix: @".c"] == YES))
{
[sFiles addObject: arg];
}
else if ([arg hasSuffix: @".gsdoc"] == YES)
{
[gFiles addObject: arg];
}
else if ([arg hasSuffix: @".html"] == YES)
{
[hFiles addObject: arg];
}
else
{
// Skip this value ... not a known file type.
NSLog(@"Unknown argument '%@' ... ignored", arg);
}
}
/*
* Note explicitly supplied gsdoc files for dependencies later.
*/
deps = [NSMutableSet setWithCapacity: 1024];
[deps addObjectsFromArray: gFiles];
/*
* 3) Load old project indexing information from the .igsdoc file if
* present and determine when the indexing information was last
* updated (never ==> distant past).
*/
refsFile = [documentationDirectory
stringByAppendingPathComponent: project];
refsFile = [refsFile stringByAppendingPathExtension: @"igsdoc"];
projectRefs = [AGSIndex new];
originalIndex = nil;
rDate = [NSDate distantPast];
if ([mgr isReadableFileAtPath: refsFile] == YES)
{
originalIndex
= [[NSDictionary alloc] initWithContentsOfFile: refsFile];
if (originalIndex == nil)
{
NSLog(@"Unable to read project file '%@'", refsFile);
}
else
{
NSDictionary *dict;
[projectRefs mergeRefs: originalIndex override: NO];
dict = [mgr fileAttributesAtPath: refsFile traverseLink: YES];
rDate = [dict fileModificationDate];
}
}
/*
* Load old OrderedSymbolDeclarations.plist to merge it later
*/
if ([mgr isReadableFileAtPath: symbolDeclsFile])
{
symbolDecls =
[NSMutableDictionary dictionaryWithContentsOfFile: symbolDeclsFile];
if (symbolDeclsFile == nil)
{
NSLog(@"Unable to read ordered symbols file '%@'", symbolDeclsFile);
}
}
if (symbolDecls == nil)
{
symbolDecls = [NSMutableDictionary dictionary];
}
/*
* 4) Clean if desired:
*/
if ([defs boolForKey: @"Clean"] == YES)
{
NSDictionary *output;
NSEnumerator *enumerator;
NSArray *outputNames;
NSMutableSet *allPaths;
NSMutableSet *templates = nil;
NSSet *preserve = nil;
NSString *path;
NSArray *keys = [NSArray arrayWithObjects:
@"Constants",
@"Functions",
@"Macros",
@"Typedefs",
@"Variables",
nil];
/*
* 4a) Build a set of all template files.
*/
templates = AUTORELEASE([NSMutableSet new]);
enumerator = [keys objectEnumerator];
while ((path = [enumerator nextObject]) != nil)
{
path = [path stringByAppendingString: @"Template"];
path = [defs stringForKey: path];
if (path != nil)
{
path = [path stringByAppendingPathExtension: @"gsdoc"];
if ([path isAbsolutePath] == NO)
{
path = [documentationDirectory
stringByAppendingPathComponent: path];
}
[templates addObject: path];
}
}
/*
* 4b) Unless we are supposed to clean templates, we preserve any
* template gsdoc files, but remove any generated content.
*/
if ([defs boolForKey: @"CleanTemplates"] == NO)
{
preserve = templates;
enumerator = [templates objectEnumerator];
while ((path = [enumerator nextObject]) != nil)
{
if ([mgr isReadableFileAtPath: path] == YES)
{
NSMutableString *ms;
NSEnumerator *e = [keys objectEnumerator];
NSString *k;
unsigned length;
ms = [[NSMutableString alloc] initWithContentsOfFile: path];
if (ms == nil)
{
NSLog(@"Cleaning ... failed to read '%@'", path);
continue;
}
length = [ms length];
while ((k = [e nextObject]) != nil)
{
NSString *ss;
NSString *es;
NSRange sr;
NSRange er;
ss = [NSString stringWithFormat: @"", k];
sr = [ms rangeOfString: ss];
es = [NSString stringWithFormat: @"", k];
er = [ms rangeOfString: es];
if (sr.length > 0 && er.length > 0
&& er.location > sr.location)
{
NSRange r;
r.location = sr.location;
r.length = NSMaxRange(er) - r.location;
[ms replaceCharactersInRange: r withString: @""];
}
}
if ([ms length] != length)
{
if ([ms writeToFile: path atomically: YES] == NO)
{
NSLog(@"Cleaning ... failed to write '%@'", path);
}
}
}
}
}
/*
* 4b) Build a list of all generated gsdoc files, then remove them
* and their corresponding html documents.
*/
output = [[projectRefs refs] objectForKey: @"output"];
enumerator = [output objectEnumerator];
allPaths = [[NSMutableSet alloc] initWithSet: templates];
while ((outputNames = [enumerator nextObject]) != nil)
{
[allPaths addObjectsFromArray: outputNames];
}
enumerator = [allPaths objectEnumerator];
while ((path = [enumerator nextObject]) != nil)
{
/*
* Delete any gsdoc files which are not in the preserve set.
*/
if ([preserve member: path] == nil)
{
if ([mgr fileExistsAtPath: path] == YES)
{
if ([mgr removeFileAtPath: path handler: nil] == NO)
{
NSLog(@"Cleaning ... failed to remove %@", path);
}
}
}
path = [path stringByDeletingPathExtension];
path = [path stringByAppendingPathExtension: @"html"];
if ([mgr fileExistsAtPath: path] == YES)
{
if ([mgr removeFileAtPath: path handler: nil] == NO)
{
NSLog(@"Cleaning ... failed to remove %@", path);
}
}
}
RELEASE(allPaths);
/*
* 4c) Remove the project index file.
*/
if ([mgr fileExistsAtPath: refsFile] == YES)
{
if ([mgr removeFileAtPath: refsFile handler: nil] == NO)
{
NSLog(@"Cleaning ... failed to remove %@", refsFile);
}
}
/*
* 4d) Remove any HTML documents resulting from gsdoc files which
* were specified on the command line rather than generated.
*/
enumerator = [gFiles objectEnumerator];
while ((path = [enumerator nextObject]) != nil)
{
path = [path lastPathComponent];
path = [path stringByDeletingPathExtension];
path = [path stringByAppendingPathExtension: @"html"];
path = [documentationDirectory
stringByAppendingPathComponent: path];
if ([mgr fileExistsAtPath: path] == YES)
{
if ([mgr removeFileAtPath: path handler: nil] == NO)
{
NSLog(@"Cleaning ... failed to remove %@", path);
}
}
}
/*
* 4e) Remove the OrderedSymbolDeclarations plist file.
*/
if ([mgr fileExistsAtPath: symbolDeclsFile])
{
if ([mgr removeFileAtPath: symbolDeclsFile handler: nil] == NO)
{
NSLog(@"Cleaning ... failed to remove %@", symbolDeclsFile);
}
}
return 0;
}
if ([sFiles count] == 0 && [gFiles count] == 0 && [hFiles count] == 0)
{
NSLog(@"No .h, .m, .c, .gsdoc, or .html filename arguments found ... giving up");
return 1;
}
/*
* 5) Start with "source files".. for each one (hereafter called a
* "header file"):
* a) Parse declarations (in .h or .m/.c) using an AGSParser object.
* b) Determine (possibly multiple) dependent .m/.c files corresponding to
* a .h and parse them.
* c) Feed parser results to an AGSOutput instance.
*/
count = [sFiles count];
if (count > 0)
{
AGSParser *parser;
AGSOutput *output;
NSString *up;
up = [defs stringForKey: @"Up"];
#if GS_WITH_GC == 0
pool = [NSAutoreleasePool new];
#endif
parser = [AGSParser new];
[parser setWordMap: [defs dictionaryForKey: @"WordMap"]];
output = [AGSOutput new];
if ([defs boolForKey: @"Standards"] == YES)
{
[parser setGenerateStandards: YES];
}
if ([defs boolForKey: @"DocumentAllInstanceVariables"] == YES)
{
[parser setDocumentAllInstanceVariables: YES];
}
if ([defs objectForKey: @"DocumentInstanceVariables"] != nil
&& [defs boolForKey: @"DocumentInstanceVariables"] == NO)
{
[parser setDocumentInstanceVariables: NO];
}
for (i = 0; i < count; i++)
{
NSString *hfile = [sFiles objectAtIndex: i];
NSString *gsdocfile;
NSString *file;
NSString *sourceName = nil;
NSMutableArray *a;
NSDictionary *attrs;
NSDate *sDate = nil;
NSDate *gDate = nil;
unsigned j;
#if GS_WITH_GC == 0
if (pool != nil)
{
RELEASE(pool);
pool = [NSAutoreleasePool new];
}
#endif
/*
* Note the name of the header file without path or extension.
* This will be used to generate the output file.
*/
file = [hfile stringByDeletingPathExtension];
file = [file lastPathComponent];
/*
* Ensure that header file name is set up using the
* header directory specified unless it is absolute.
*/
if ([hfile isAbsolutePath] == NO)
{
if ([headerDirectory length] > 0
&& [[hfile pathExtension] isEqual: @"h"] == YES)
{
hfile = [headerDirectory stringByAppendingPathComponent:
hfile];
}
}
gsdocfile = [documentationDirectory
stringByAppendingPathComponent: file];
gsdocfile = [gsdocfile stringByAppendingPathExtension: @"gsdoc"];
if (ignoreDependencies == NO)
{
NSDate *d;
/*
* Ask existing project info (.gsdoc file) for dependency
* information. Then check the dates on the source files
* and the header file.
*/
a = [projectRefs sourcesForHeader: hfile];
[a insertObject: hfile atIndex: 0];
[projectRefs setSources: a forHeader: hfile];
for (j = 0; j < [a count]; j++)
{
NSString *sfile = [a objectAtIndex: j];
attrs = [mgr fileAttributesAtPath: sfile
traverseLink: YES];
d = [attrs fileModificationDate];
if (sDate == nil || [d earlierDate: sDate] == sDate)
{
sDate = d;
IF_NO_GC([[sDate retain] autorelease];)
}
}
if (verbose == YES)
{
NSLog(@"Saved sources for %@ are %@ ... %@", hfile, a, sDate);
}
/*
* Ask existing project info (.gsdoc file) for dependency
* information. Then check the dates on the output files.
* If none are set, assume the default.
*/
a = [projectRefs outputsForHeader: hfile];
if ([a count] == 0)
{
[a insertObject: gsdocfile atIndex: 0];
[projectRefs setOutputs: a forHeader: hfile];
}
for (j = 0; j < [a count]; j++)
{
NSString *ofile = [a objectAtIndex: j];
attrs = [mgr fileAttributesAtPath: ofile traverseLink: YES];
d = [attrs fileModificationDate];
if (gDate == nil || [d laterDate: gDate] == gDate)
{
gDate = d;
IF_NO_GC([[gDate retain] autorelease];)
}
}
if (verbose == YES)
{
NSLog(@"Saved outputs for %@ are %@ ... %@", hfile, a, gDate);
}
}
if (gDate == nil || [sDate earlierDate: gDate] == gDate)
{
NSArray *modified;
if (showDependencies == YES)
{
NSLog(@"%@: source %@, gsdoc %@ ==> regenerate",
file, sDate, gDate);
}
[parser reset];
/*
* Try to parse header to see what needs documenting.
* If the header given was actually a .m/.c file, this will
* parse that file for declarations rather than definitions.
*/
if ([mgr isReadableFileAtPath: hfile] == NO)
{
NSLog(@"No readable header at '%@' ... skipping", hfile);
continue;
}
if (declared != nil)
{
[parser setDeclared:
[declared stringByAppendingPathComponent:
[hfile lastPathComponent]]];
}
[parser parseFile: hfile isSource: NO];
/*
* Record dependency information.
*/
a = [parser outputs];
if ([a count] > 0)
{
/*
* Adjust the location of the output files to be in the
* documentation directory.
*/
for (j = 0; j < [a count]; j++)
{
NSString *s = [a objectAtIndex: j];
if ([s isAbsolutePath] == NO)
{
s = [documentationDirectory
stringByAppendingPathComponent: s];
[a replaceObjectAtIndex: j withObject: s];
}
}
if (verbose == YES)
{
NSLog(@"Computed outputs for %@ are %@", hfile, a);
}
[projectRefs setOutputs: a forHeader: hfile];
}
a = [parser sources];
/*
* Collect any matching .m files provided as autogsdoc arguments
* for the current header (hfile).
*/
sourceName = [[hfile lastPathComponent]
stringByDeletingPathExtension];
sourceName = [sourceName stringByAppendingPathExtension: @"m"];
for (j = 0; j < [sFiles count]; j++)
{
NSString *sourcePath = [sFiles objectAtIndex: j];
if ([sourcePath hasSuffix: sourceName]
&& [mgr isReadableFileAtPath: sourcePath])
{
[a addObject: sourcePath];
}
}
if ([a count] > 0)
{
[projectRefs setSources: a forHeader: hfile];
}
if (verbose == YES)
{
NSLog(@"Computed sources for %@ are %@", hfile, a);
}
for (j = 0; j < [a count]; j++)
{
NSString *sfile = [a objectAtIndex: j];
/*
* If we can read a source file, parse it for any
* additional information on items found in the header.
*/
if ([mgr isReadableFileAtPath: sfile] == YES)
{
[parser parseFile: sfile isSource: YES];
}
else
{
NSLog(@"No readable source at '%@' ... ignored", sfile);
}
}
/*
* Set up linkage for this file.
*/
[[parser info] setObject: file forKey: @"base"];
[[parser info] setObject: documentationDirectory
forKey: @"directory"];
/*
* Only produce linkage if the up link is not empty.
* Do not add an up link if this *is* the up link document.
*/
if ([up length] > 0 && [up isEqual: file] == NO)
{
[[parser info] setObject: up forKey: @"up"];
}
modified = [output output: [parser info]];
if (modified == nil)
{
NSLog(@"Sorry unable to write %@", gsdocfile);
}
else
{
unsigned c = [modified count];
while (c-- > 0)
{
NSString *f;
f = [[modified objectAtIndex: c] lastPathComponent];
if ([gFiles containsObject: f] == NO)
{
[gFiles addObject: f];
}
}
}
}
else
{
/*
* Add the .h file to the list of those to process.
*/
[gFiles addObject: [hfile lastPathComponent]];
}
}
/*
* Ask the parser for the OrderedSymbolDeclarations plist, merge with
* the previously output plist and save it
*/
[symbolDecls addEntriesFromDictionary:
[parser orderedSymbolDeclarationsByUnit]];
[symbolDecls writeToFile: symbolDeclsFile atomically: YES];
informalProtocols = RETAIN([output informalProtocols]);
#if GS_WITH_GC == 0
DESTROY(pool);
#endif
DESTROY(parser);
DESTROY(output);
}
/*
* 6) Now move to "gsdoc files" (including both command-line given ones and
* just-generated ones).. and generate the index.
*
*/
count = [gFiles count];
if (count > 0)
{
NSDictionary *projectIndex;
CREATE_AUTORELEASE_POOL(arp);
for (i = 0; i < count; i++)
{
NSString *arg = [gFiles objectAtIndex: i];
NSString *gsdocfile;
NSString *file;
NSDictionary *attrs;
NSDate *gDate = nil;
#if GS_WITH_GC == 0
if (arp != nil)
{
RELEASE(arp);
arp = [NSAutoreleasePool new];
}
#endif
/*
* 6a) Chop off any path specification that might be there (for files
* given on the command line) and search for the file only in
* 'DocumentationDirectory' or the CWD (which is assumed to be
* the directory with the source files, though this will not be
* true if path information was given for them on the command
* line).
*/
file = [[arg lastPathComponent] stringByDeletingPathExtension];
gsdocfile = [documentationDirectory
stringByAppendingPathComponent: file];
gsdocfile = [gsdocfile stringByAppendingPathExtension: @"gsdoc"];
/*
* If our source file is a gsdoc file ... it may be located
* in the current (input) directory rather than the documentation
* (output) directory.
*/
if ([mgr isReadableFileAtPath: gsdocfile] == NO)
{
gsdocfile = [file stringByAppendingPathExtension: @"gsdoc"];
}
if (ignoreDependencies == NO)
{
attrs = [mgr fileAttributesAtPath: gsdocfile traverseLink: YES];
gDate = [attrs fileModificationDate];
IF_NO_GC([[gDate retain] autorelease];)
}
/*
* 6b) Now we try to process the gsdoc data to make index info
* unless the project index is already more up to date than
* this file (or the gsdoc file does not exist of course).
*/
if (gDate != nil && [gDate earlierDate: rDate] == rDate)
{
if (showDependencies == YES)
{
NSLog(@"%@: gsdoc %@, index %@ ==> regenerate",
file, gDate, rDate);
}
if ([mgr isReadableFileAtPath: gsdocfile] == YES)
{
GSXMLNode *root;
GSXMLParser *parser;
AGSIndex *localRefs;
// This parses the file for index info
parser = [GSXMLParser parserWithContentsOfFile: gsdocfile];
[parser doValidityChecking: YES];
[parser keepBlanks: NO];
[parser substituteEntities: NO];
if ([parser parse] == NO)
{
NSLog(@"WARNING %@ is not a valid document", gsdocfile);
}
root = [[parser document] root];
if (![[root name] isEqualToString: @"gsdoc"])
{
NSLog(@"not a gsdoc document - because name node is %@",
[root name]);
return 1;
}
localRefs = AUTORELEASE([AGSIndex new]);
// This is the main call that computes index information
[localRefs makeRefs: root];
/*
* accumulate index info in project references
*/
[projectRefs mergeRefs: [localRefs refs] override: NO];
}
else
{
NSLog(@"File '%@' not found in $DocumentationDirectory or '.' ... skipping indexing",
gsdocfile);
}
}
}
if (informalProtocols != nil) {
[projectRefs addInformalProtocols: informalProtocols];
DESTROY(informalProtocols);
}
#if GS_WITH_GC == 0
DESTROY(arp);
#endif
/*
* 7) Save project references if they have been modified
* (into an .igsdoc file named for the project).
*/
projectIndex = [projectRefs refs];
if (projectIndex != nil && [originalIndex isEqual: projectIndex] == NO)
{
if ([projectIndex writeToFile: refsFile atomically: YES] == NO)
{
NSLog(@"Sorry unable to write %@", refsFile);
}
}
DESTROY(originalIndex);
}
globalRefs = [AGSIndex new];
/*
* 8) If we are either generating html output, or relocating existing
* html documents, we must build up the indexing information needed
* for any cross-referencing etc.. This comes from the "xxxProjects"
* defaults. Each of these is used to find a project directory, in
* which an .igsdoc index cache file is searched for. If found, its
* contents are read in and merged with the current project (but NOT
* merged into its index file).
*/
if (generateHtml == YES || [hFiles count] > 0)
{
NSMutableDictionary *projects;
NSString *systemProjects;
NSString *localProjects;
CREATE_AUTORELEASE_POOL (pool);
localProjects = [defs stringForKey: @"LocalProjects"];
if (localProjects == nil)
{
localProjects = @"";
}
systemProjects = [defs stringForKey: @"SystemProjects"];
if (systemProjects == nil)
{
systemProjects = @"";
}
projects = [[defs dictionaryForKey: @"Projects"] mutableCopy];
IF_NO_GC([projects autorelease];)
/*
* Merge any system project references.
*/
if ([systemProjects caseInsensitiveCompare: @"None"] != NSOrderedSame)
{
NSString *base = [NSSearchPathForDirectoriesInDomains(
NSDocumentationDirectory, NSSystemDomainMask, NO) lastObject];
base = [base stringByStandardizingPath];
if (base != nil)
{
NSDirectoryEnumerator *enumerator = [mgr enumeratorAtPath: base];
NSString *file;
if ([systemProjects isEqual: @""] == YES)
{
systemProjects = base; // Absolute path
}
while ((file = [enumerator nextObject]) != nil)
{
NSString *ext = [file pathExtension];
if ([ext isEqualToString: @"igsdoc"] == YES
&& [[file lastPathComponent] isEqual: refsName] == NO)
{
NSString *key;
NSString *val;
if (projects == nil)
{
projects = [NSMutableDictionary dictionary];
}
key = [base stringByAppendingPathComponent: file];
val = [file stringByDeletingLastPathComponent];
val
= [systemProjects stringByAppendingPathComponent: val];
[projects setObject: val forKey: key];
}
}
}
}
/*
* Merge any local project references.
*/
if ([localProjects caseInsensitiveCompare: @"None"] != NSOrderedSame)
{
NSString *base = [NSSearchPathForDirectoriesInDomains(
NSDocumentationDirectory, NSLocalDomainMask, NO) lastObject];
base = [base stringByStandardizingPath];
if (base != nil)
{
NSDirectoryEnumerator *enumerator;
NSString *file;
enumerator = [mgr enumeratorAtPath: base];
if ([localProjects isEqual: @""] == YES)
{
localProjects = base; // Absolute path
}
while ((file = [enumerator nextObject]) != nil)
{
NSString *ext = [file pathExtension];
if ([ext isEqualToString: @"igsdoc"] == YES
&& [[file lastPathComponent] isEqual: refsName] == NO)
{
NSString *key;
NSString *val;
if (projects == nil)
{
projects = [NSMutableDictionary dictionary];
}
key = [base stringByAppendingPathComponent: file];
val = [file stringByDeletingLastPathComponent];
val = [localProjects stringByAppendingPathComponent: val];
[projects setObject: val forKey: key];
}
}
}
}
/*
* Merge any "plain project" references.
*/
if (projects != nil)
{
NSEnumerator *e = [projects keyEnumerator];
NSString *k;
while ((k = [e nextObject]) != nil)
{
if ([mgr isReadableFileAtPath: k] == NO)
{
NSLog(@"Unable to read project file '%@'", k);
}
else
{
NSDictionary *dict;
dict = [[NSDictionary alloc] initWithContentsOfFile: k];
if (dict == nil)
{
NSLog(@"Unable to read project file '%@'", k);
}
else
{
AGSIndex *tmp;
NSString *p;
tmp = [AGSIndex new];
[tmp mergeRefs: dict override: NO];
RELEASE(dict);
/*
* Adjust path to external project files ...
*/
p = [projects objectForKey: k];
if ([p isEqual: @""] == YES)
{
p = [k stringByDeletingLastPathComponent];
}
[tmp setDirectory: p];
[globalRefs mergeRefs: [tmp refs] override: YES];
RELEASE(tmp);
}
}
}
}
/*
* Accumulate project index info into global index
*/
[globalRefs mergeRefs: [projectRefs refs] override: YES];
#if GS_WITH_GC == 0
RELEASE(pool);
#endif
}
/*
* 9) If we are generating HTML frames, create the gsdoc files specifying
* indices that we will use.
*/
if ([defs boolForKey: @"MakeFrames"] == YES)
{
int i;
int cap = 1360;
NSArray *idxTypes = [NSArray arrayWithObjects:
@"class",
@"protocol",
@"constant",
@"function",
@"macro",
@"type",
@"variable",
@"tool",
nil];
NSString *idxIndexFile;
NSMutableString *idxIndex= [NSMutableString stringWithCapacity: 5*cap];
NSString *framesetFile;
NSMutableString *frameset = [NSMutableString stringWithCapacity: cap];
NSMutableString *tocSkel = [NSMutableString stringWithCapacity: cap];
NSString *prjFile =
[NSString stringWithFormat: @"%@.gsdoc",project];
// skeleton for table of contents files
[tocSkel setString: @"\n"
@"\n"
@"
The index below lists the major components of the [prjName] \n"
@" documentation.