/**
This is a really great class ... but it's not really reusable since it's * far too special purpose.
*Here is the afterword for the class.
*And here is some automated cross referencing ... * A method in a protocol: [(NSCopying)-copyWithZone:], a class: * [NSString], a protocol: [(NSCopying)], and a * category: [NSRunLoop(GNUstepExtensions)]. *
*AGSOutput
, including some
* sample uses of GSDoc, such as cross-references (see [NSString]).
* Functions, like escapeType(), are automatically referenced (if they are
* found).
*/
@implementation AGSOutput
- (NSString*) checkComment: (NSString*)comment
unit: (NSString*)unit
info: (NSDictionary*)d
{
if ([comment length] == 0)
{
comment = @"Description forthcoming.";
if (warn == YES)
{
NSString *name = [d objectForKey: @"Name"];
NSString *type = [d objectForKey: @"Type"];
if (unit == nil)
{
if (type == nil)
{
NSLog(@"Warning - No comments for %@", name);
}
else
{
NSLog(@"Warning - No comments for %@ %@", type, name);
}
}
else
{
if ([d objectForKey: @"ReturnType"] != nil)
{
NSLog(@"Warning - No comments for [%@ %@]", unit, name);
}
else
{
NSLog(@"Warning - No comments for instance variable %@ in %@",
name, unit);
}
}
}
}
return comment;
}
- (void) dealloc
{
DESTROY(identifier);
DESTROY(identStart);
DESTROY(spaces);
DESTROY(spacenl);
DESTROY(informalProtocols);
[super dealloc];
}
- (unsigned) fitWords: (NSArray*)a
from: (unsigned)start
to: (unsigned)end
maxSize: (unsigned)limit
output: (NSMutableString*)buf
{
unsigned size = 0;
unsigned nest = 0;
unsigned i;
int lastOk = -1;
BOOL addSpace = NO;
for (i = start; size < limit && i < end; i++)
{
NSString *t = [a objectAtIndex: i];
if (nest == 0 && [t hasPrefix: @""] == YES)
{
break; // End of element reached.
}
/*
* Check sizing and output this word if necessary.
*/
if (addSpace == YES && snuggleEnd(t) == NO)
{
size++;
if (buf != nil)
{
[buf appendString: @" "];
}
}
size += [t length];
if (buf != nil)
{
[buf appendString: t];
}
/*
* Determine nesting level changes produced by this word, and
* whether we need a space before the next word.
*/
if ([t hasPrefix: @""] == YES)
{
nest--;
addSpace = YES;
}
else if ([t hasPrefix: @"<"] == YES)
{
if ([t hasSuffix: @"/>"] == YES)
{
addSpace = YES;
}
else
{
nest++;
addSpace = NO;
}
}
else
{
if (snuggleStart(t) == NO)
{
addSpace = YES;
}
else
{
addSpace = NO;
}
}
/*
* Record whether the word we just checked was at nesting level 0
* and had not exceeded the line length limit.
*/
if (nest == 0 && size <= limit)
{
lastOk = i;
}
}
return lastOk + 1;
}
- (id) init
{
NSMutableCharacterSet *m;
m = [[NSCharacterSet controlCharacterSet] mutableCopy];
[m addCharactersInString: @" "];
spacenl = [m copy];
[m removeCharactersInString: @"\n"];
spaces = [m copy];
RELEASE(m);
identifier = RETAIN([NSCharacterSet characterSetWithCharactersInString:
@"_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]);
identStart = RETAIN([NSCharacterSet characterSetWithCharactersInString:
@"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]);
informalProtocols = [NSMutableArray new];
verbose = [[NSUserDefaults standardUserDefaults] boolForKey: @"Verbose"];
warn = [[NSUserDefaults standardUserDefaults] boolForKey: @"Warn"];
return self;
}
/**
* Return an array containing the names of any files modified as
* a result of outputing the specified data structure.
*/
- (NSArray*) output: (NSDictionary*)d
{
NSMutableString *str = [NSMutableString stringWithCapacity: 10240];
NSDictionary *classes;
NSDictionary *categories;
NSDictionary *protocols;
NSDictionary *functions;
NSDictionary *types;
NSDictionary *variables;
NSDictionary *constants;
NSDictionary *macros;
NSMutableArray *files;
NSArray *authors;
NSString *base;
NSString *tmp;
NSString *file;
NSString *dest;
unsigned chapters = 0;
files = [NSMutableArray arrayWithCapacity: 5];
info = d;
base = [info objectForKey: @"base"];
file = base;
if ([[file pathExtension] isEqualToString: @"gsdoc"] == NO)
{
file = [file stringByAppendingPathExtension: @"gsdoc"];
}
dest = [info objectForKey: @"directory"];
if ([dest length] > 0 && [file isAbsolutePath] == NO)
{
file = [dest stringByAppendingPathComponent: file];
}
classes = [info objectForKey: @"Classes"];
categories = [info objectForKey: @"Categories"];
protocols = [info objectForKey: @"Protocols"];
functions = [info objectForKey: @"Functions"];
types = [info objectForKey: @"Types"];
variables = [info objectForKey: @"Variables"];
constants = [info objectForKey: @"Constants"];
macros = [info objectForKey: @"Macros"];
[str appendString: @"\n"];
[str appendString: @"\n"];
[str appendFormat: @""] == NO)
{
/*
* Ensure that well known constants are rendered as 'code'
*/
count = [constants count];
for (pos = 0; pos < count; pos++)
{
NSString *c = [constants objectAtIndex: pos];
r = [tmp rangeOfString: c];
if (r.length > 0)
{
NSString *start;
NSString *end;
if (r.location > 0)
{
start = [tmp substringToIndex: r.location];
}
else
{
start = nil;
}
if (NSMaxRange(r) < [tmp length])
{
end = [tmp substringFromIndex: NSMaxRange(r)];
}
else
{
end = nil;
}
if ((start == nil || snuggleStart(start) == YES)
&& (end == nil || snuggleEnd(end) == YES))
{
NSString *sub;
if (start != nil || end != nil)
{
sub = [tmp substringWithRange: r];
}
else
{
sub = nil;
}
if (start != nil)
{
[a insertObject: start atIndex: l++];
}
[a insertObject: @"" atIndex: l++];
if (sub != nil)
{
[a replaceObjectAtIndex: l withObject: sub];
}
l++;
[a insertObject: @"
" atIndex: l];
if (end != nil)
{
[a insertObject: end atIndex: ++l];
}
}
}
}
}
/*
* Ensure that method arguments are rendered as 'var'
*/
if (l == 0 || [[a objectAtIndex: l-1] isEqual: @""] == NO)
{
count = [args count];
for (pos = 0; pos < count; pos++)
{
NSString *c = [args objectAtIndex: pos];
r = [tmp rangeOfString: c];
if (r.length > 0)
{
NSString *start;
NSString *end;
if (r.location > 0)
{
start = [tmp substringToIndex: r.location];
}
else
{
start = nil;
}
if (NSMaxRange(r) < [tmp length])
{
end = [tmp substringFromIndex: NSMaxRange(r)];
}
else
{
end = nil;
}
if ((start == nil || snuggleStart(start) == YES)
&& (end == nil || snuggleEnd(end) == YES))
{
NSString *sub;
if (start != nil || end != nil)
{
sub = [tmp substringWithRange: r];
}
else
{
sub = nil;
}
if (start != nil)
{
[a insertObject: start atIndex: l++];
}
[a insertObject: @"" atIndex: l++];
if (sub != nil)
{
[a replaceObjectAtIndex: l withObject: sub];
}
l++;
[a insertObject: @"" atIndex: l];
if (end != nil)
{
[a insertObject: end atIndex: ++l];
}
}
}
}
}
/*
* Ensure that methods are rendered as references.
* First look for format with class name in square brackets.
* If that's all there is, we make a class reference.
*/
r = [tmp rangeOfString: @"["];
if (r.length > 0)
{
unsigned sPos = NSMaxRange(r);
pos = sPos;
r = NSMakeRange(pos, [tmp length] - pos);
r = [tmp rangeOfString: @"]" options: NSLiteralSearch range: r];
if (r.length > 0)
{
unsigned ePos = r.location;
NSString *cName = nil;
NSString *mName = nil;
unichar c = 0;
BOOL isProtocol = NO;
if (pos < ePos
&& [identStart characterIsMember:
(c = [tmp characterAtIndex: pos])] == YES)
{
/*
* Look for class or category name.
*/
pos++;
while (pos < ePos)
{
c = [tmp characterAtIndex: pos];
if ([identifier characterIsMember: c] == NO)
{
break;
}
pos++;
}
if (c == '(')
{
pos++;
if (pos < ePos
&& [identStart characterIsMember:
(c = [tmp characterAtIndex: pos])] == YES)
{
while (pos < ePos)
{
c = [tmp characterAtIndex: pos];
if ([identifier characterIsMember: c] == NO)
{
break;
}
pos++;
}
if (c == ')')
{
pos++;
r = NSMakeRange(sPos, pos - sPos);
cName = [tmp substringWithRange: r];
if (pos < ePos)
{
c = [tmp characterAtIndex: pos];
}
}
}
if (cName == nil)
{
pos = ePos; // Bad class name!
}
}
else
{
r = NSMakeRange(sPos, pos - sPos);
cName = [tmp substringWithRange: r];
}
}
else if (pos < ePos && (c = [tmp characterAtIndex: pos]) == '(')
{
/*
* Look for protocol name.
*/
pos++;
while (pos < ePos)
{
c = [tmp characterAtIndex: pos];
if (c == ')')
{
pos++;
r = NSMakeRange(sPos, pos - sPos);
cName = [tmp substringWithRange: r];
if (pos < ePos)
{
c = [tmp characterAtIndex: pos];
}
break;
}
pos++;
}
isProtocol = YES;
}
if (pos < ePos && (c == '+' || c == '-'))
{
unsigned mStart = pos;
pos++;
if (pos < ePos
&& [identStart characterIsMember:
(c = [tmp characterAtIndex: pos])] == YES)
{
while (pos < ePos)
{
c = [tmp characterAtIndex: pos];
if (c != ':'
&& [identifier characterIsMember: c] == NO)
{
break;
}
pos++;
}
/*
* Varags methods end with ',...'
*/
if (ePos - pos >= 4
&& [[tmp substringWithRange: NSMakeRange(pos, 4)]
isEqual: @",..."])
{
pos += 4;
}
/*
* The end of the method name should be immediately
* before the closing square bracket at 'ePos'
*/
if (pos == ePos && pos - mStart > 1)
{
r = NSMakeRange(mStart, pos - mStart);
mName = [tmp substringWithRange: r];
}
}
}
if (mName != nil)
{
NSString *start;
NSString *end;
NSString *sub;
NSString *ref;
if (sPos > 0)
{
start = [tmp substringToIndex: sPos];
if ([start isEqualToString: @"["] == YES)
{
start = nil;
}
else if ([start hasSuffix: @"["] == YES)
{
start = [start substringToIndex: [start length] - 1];
}
}
else
{
start = nil;
}
if (ePos < [tmp length])
{
end = [tmp substringFromIndex: ePos];
if ([end isEqualToString: @"]"] == YES)
{
end = nil;
}
else if ([end hasPrefix: @"]"] == YES)
{
end = [end substringFromIndex: 1];
}
}
else
{
end = nil;
}
if (start != nil || end != nil)
{
sub = [tmp substringWithRange:
NSMakeRange(sPos, ePos - sPos)];
}
else
{
sub = nil;
}
if (start != nil)
{
[a insertObject: start atIndex: l++];
}
if (cName == nil)
{
ref = [NSString stringWithFormat:
@"", mName];
}
else if (isProtocol == YES)
{
ref = [NSString stringWithFormat:
@"",
mName, cName];
if (isProtocol == YES)
{
if (sub == nil)
{
sub = tmp;
}
sub = [sub stringByReplacingString: @"("
withString: @"<"];
sub = [sub stringByReplacingString: @")"
withString: @">"];
}
}
else
{
ref = [NSString stringWithFormat:
@"",
mName, cName];
sub = [NSString stringWithFormat: @"[%@ %@]",
cName, mName];
}
[a insertObject: ref atIndex: l++];
if (sub != nil)
{
[a replaceObjectAtIndex: l withObject: sub];
}
l++;
[a insertObject: @"" atIndex: l];
if (end != nil)
{
[a insertObject: end atIndex: ++l];
}
}
else if (pos == ePos && cName != nil)
{
NSString *ref;
if (isProtocol == YES)
{
NSRange r;
r = NSMakeRange(1, [cName length] - 2);
cName = [cName substringWithRange: r];
ref = [NSString stringWithFormat:
@"<%@>",
cName, cName];
}
else if ([cName hasSuffix: @")"] == YES)
{
ref = [NSString stringWithFormat:
@"%@",
cName, cName];
}
else
{
ref = [NSString stringWithFormat:
@"%@",
cName, cName];
}
[a replaceObjectAtIndex: l withObject: ref];
if (ePos < [tmp length])
{
NSString *end = [tmp substringFromIndex: ePos];
if ([end isEqualToString: @"]"] == YES)
{
end = nil;
}
if ([end hasPrefix: @"]"] == YES)
{
end = [end substringFromIndex: 1];
}
if ([end length] > 0)
{
[a insertObject: end atIndex: ++l];
}
}
}
}
continue;
}
/*
* Now handle bare method names for current class ... outside brackets.
*/
if ([tmp hasPrefix: @"-"] || [tmp hasPrefix: @"+"])
{
unsigned ePos = [tmp length];
NSString *mName = nil;
unsigned c;
pos = 1;
if (pos < ePos
&& [identStart characterIsMember:
(c = [tmp characterAtIndex: pos])] == YES)
{
while (pos < ePos)
{
c = [tmp characterAtIndex: pos];
if (c != ':'
&& [identifier characterIsMember: c] == NO)
{
break;
}
pos++;
}
/*
* Varags methods end with ',...'
*/
if (ePos - pos >= 4
&& [[tmp substringWithRange: NSMakeRange(pos, 4)]
isEqual: @",..."])
{
pos += 4;
if (pos < ePos)
{
c = [tmp characterAtIndex: pos];
}
}
if (pos > 1 && (pos == ePos || c == ',' || c == '.' || c == ';'))
{
NSString *end;
NSString *sub;
NSString *ref;
mName = [tmp substringWithRange: NSMakeRange(0, pos)];
if (pos < [tmp length])
{
end = [tmp substringFromIndex: pos];
sub = [tmp substringToIndex: pos];
}
else
{
end = nil;
sub = nil;
}
ref = [NSString stringWithFormat:
@"", mName];
[a insertObject: ref atIndex: l++];
if (sub != nil)
{
[a replaceObjectAtIndex: l withObject: sub];
}
l++;
[a insertObject: @"" atIndex: l];
if (end != nil)
{
[a insertObject: end atIndex: ++l];
}
}
}
continue;
}
/*
* Now handle function names. Anything ending in '()' is assumed to
* be a referencable function name except 'main()' ... which is special.
* NB. A comma, fullstop, or semicolon following '()' is counted as if
* the text ended in '()'
*/
r = [tmp rangeOfString: @"()"];
if (r.length > 0)
{
unsigned c = [tmp characterAtIndex: 0];
unsigned len = [tmp length];
NSString *str = [tmp substringToIndex: r.location];
BOOL ok = NO;
if ([identStart characterIsMember: c] == YES
&& [str isEqual: @"main"] == NO)
{
ok = YES;
if (len > NSMaxRange(r))
{
NSString *end;
end = [tmp substringFromIndex: NSMaxRange(r)];
c = [end characterAtIndex: 0];
if (c == ',' || c == '.' || c == ';')
{
[a insertObject: end atIndex: l + 1];
tmp = [tmp substringToIndex: NSMaxRange(r)];
[a replaceObjectAtIndex: l withObject: tmp];
}
else
{
ok = NO;
}
}
}
if (ok == YES)
{
str = [NSString stringWithFormat:
@"", str];
[a insertObject: str atIndex: l++];
l++; // Point past the function name in the array.
[a insertObject: @"" atIndex: l++];
continue;
}
}
}
return a;
}
- (NSArray*) informalProtocols
{
return informalProtocols;
}
@end
@implementation AGSOutput (Private)
- (NSString*) mergeMarkup: (NSString*)markup
ofKind: (NSString*)kind
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSString *key = [kind stringByAppendingString: @"Template"];
NSString *name = [ud stringForKey: key];
NSData *d;
NSString *file;
NSFileManager *mgr;
NSString *base;
NSString *tmp;
NSMutableString *str;
NSRange range;
NSRange start;
NSRange end;
NSString *dest;
dest = [info objectForKey: @"directory"];
if ([name length] == 0)
{
return nil; // No common document.
}
file = [name stringByAppendingPathExtension: @"gsdoc"];
if ([dest length] > 0 && [file isAbsolutePath] == NO)
{
file = [dest stringByAppendingPathComponent: file];
}
mgr = [NSFileManager defaultManager];
base = [info objectForKey: @"base"];
/*
* Load the current file that info should be merged into.
*/
if ([mgr isReadableFileAtPath: file] == YES)
{
str = [NSMutableString stringWithContentsOfFile: file];
}
else
{
tmp = [name stringByAppendingPathExtension: @"template"];
if ([mgr isReadableFileAtPath: tmp] == YES)
{
str = [NSMutableString stringWithContentsOfFile: tmp];
}
else
{
NSString *up = [ud stringForKey: @"Up"];
/*
* No pre-existing file, and no blank template available ...
* Generate a standard template.
*/
str = [[NSMutableString alloc] initWithCapacity: 1024];
[str appendString: @"\n"];
[str appendString: @"\n"];
[str appendString: @"\n"];
[str appendString: @" \n"];
[str appendString: @" "];
[str appendString: kind];
[str appendString: @" \n"];
tmp = [NSString stringWithFormat: @"Generated by %@", NSUserName()];
[str appendString: @" \n"];
[str appendString: @" \n"];
[str appendString: @" \n"];
[str appendString: @" \n"];
[str appendString: @" \n"];
[str appendString: @" \n"];
}
}
/*
* Locate start and end points for all markup of this 'kind'.
*/
tmp = [NSString stringWithFormat: @"\n", kind];
start = [str rangeOfString: tmp];
if (start.length == 0)
{
start = [str rangeOfString: @""];
if (start.length == 0)
{
start = [str rangeOfString: @"