diff --git a/Tools/HTMLLinker.m b/Tools/HTMLLinker.m
index 81ff41d2e..7a0f48c85 100644
--- a/Tools/HTMLLinker.m
+++ b/Tools/HTMLLinker.m
@@ -60,13 +60,15 @@
* right. It would probably be impossible.
*
* The HTML linker will only fixup links which have the attribute
- * 'rel' set to 'dynamic', as in the following example -
+ * 'rel' set to 'dynamical', as in the following example -
*
*
*
- * All other links will be ignored and not fixed up. This is so that you
- * can clearly mark the links you want to be dynamically fixed up by the
- * linker; other links will not be touched.
+ * All other links will be ignored and not fixed up. This is so that
+ * you can clearly mark the links you want to be dynamically fixed up
+ * by the linker; other links will not be touched. If you want the
+ * linker to attempt to fixup all links, pass the -FixupAllLinks YES
+ * option to the linker.
*
* The linker might perform 'link checking' if run with the
* '-CheckLinks YES' option. link checking means that when a link is
@@ -96,6 +98,65 @@
#include
+/* For convenience, cached for the whole tool. */
+
+/* [NSFileManager defaultManager] */
+static NSFileManager *fileManager = nil;
+
+/* [[NSFileManager defaulManager] currentDirectoryPath] */
+static NSString *currentPath = nil;
+
+/* Enumerate all .html files in a directory and subdirectories. */
+@interface HTMLDirectoryEnumerator : NSEnumerator
+{
+ NSDirectoryEnumerator *e;
+ NSString *basePath;
+}
+
+- (id)initWithBasePath: (NSString *)path;
+
+@end
+
+@implementation HTMLDirectoryEnumerator : NSEnumerator
+
+- (id)initWithBasePath: (NSString *)path
+{
+ ASSIGN (e, [fileManager enumeratorAtPath: path]);
+ ASSIGN (basePath, path);
+ return [super init];
+}
+
+- (void)dealloc
+{
+ RELEASE (e);
+ RELEASE (basePath);
+ [super dealloc];
+}
+
+- (id)nextObject
+{
+ NSString *s;
+
+ while ((s = [e nextObject]) != nil)
+ {
+ NSString *extension = [s pathExtension];
+
+ if ([extension isEqualToString: @"html"]
+ || [extension isEqualToString: @"HTML"]
+ || [extension isEqualToString: @"htm"]
+ || [extension isEqualToString: @"HTM"])
+ {
+ /* NSDirectoryEnumerator returns the relative path, we
+ return the absolute. */
+ return [basePath stringByAppendingPathComponent: s];
+ }
+ }
+
+ return nil;
+}
+
+@end
+
/*
* An object representing a file which can be a destination of links.
*/
@@ -137,13 +198,17 @@
BOOL verbose;
BOOL checkLinks;
NSMutableDictionary *files;
+ NSMutableDictionary *pathMappings;
}
- (id)initWithVerboseFlag: (BOOL)v
checkLinksFlag: (BOOL)f;
/* Register the file as available for resolving references. */
-- (void)registerFile: (DestinationFile *)file;
+- (void)registerFile: (NSString *)pathOnDisk;
+
+/* Register a new path mapping. */
+- (void)registerPathMappings: (NSDictionary *)dict;
/* Resolve the link 'link' by fixing it up using the registered
destination files. Return the resolved link. 'logFile' is only
@@ -181,12 +246,16 @@
- (NSArray *)names;
/* Fix up all the links in the HTML code by feeding each of them to
- the provided HTMLLinker; return the fixed up HTML code. logFile is
- the file we are fixing up; it's only used when a warning is issued
- because there is problem in the linking - the warning message is
- displayed as being about links in the file logFile. */
+ the provided HTMLLinker; return the fixed up HTML code. If
+ fixupAllLinks is 'NO', only fixup links with rel="dynamical"; if
+ fixupAllLinks is 'YES', attempt to fixup all links in the HTML
+ code. logFile is the file we are fixing up; it's only used when a
+ warning is issued because there is problem in the linking - the
+ warning message is displayed as being about links in the file
+ logFile. */
- (NSString *)resolveLinksUsingHTMLLinker: (HTMLLinker *)linker
- logFile: (NSString *)logFile;
+ logFile: (NSString *)logFile
+ fixupAllLinks: (BOOL)fixupAllLinks;
@end
@@ -384,6 +453,7 @@
- (NSString *)resolveLinksUsingHTMLLinker: (HTMLLinker *)linker
logFile: (NSString *)logFile
+ fixupAllLinks: (BOOL)fixupAllLinks
{
/* We represent the output as a linked list. Each element in the
linked list represents a string; concatenating all the strings in
@@ -643,7 +713,8 @@
}
}
}
- if (href != nil && [rel isEqualToString: @"dynamical"])
+ if (href != nil && (fixupAllLinks
+ || [rel isEqualToString: @"dynamical"]))
{
/* Ok - fixup the link. */
NSString *link;
@@ -788,18 +859,100 @@
verbose = v;
checkLinks = f;
files = [NSMutableDictionary new];
+ pathMappings = [NSMutableDictionary new];
return [super init];
}
- (void)dealloc
{
RELEASE (files);
+ RELEASE (pathMappings);
[super dealloc];
}
-- (void)registerFile: (DestinationFile *)file
+- (void)registerFile: (NSString *)pathOnDisk
{
- [files setObject: file forKey: [[file fullName] lastPathComponent]];
+ NSString *fullPath = pathOnDisk;
+ DestinationFile *file;
+
+ /* We only accept absolute paths. */
+ if (![pathOnDisk isAbsolutePath])
+ {
+ pathOnDisk = [currentPath stringByAppendingPathComponent: pathOnDisk];
+ }
+
+ /* Check if it's a directory; if it is, enumerate all HTML files
+ inside it, and add all of them. */
+ {
+ BOOL isDir;
+
+ if (![fileManager fileExistsAtPath: pathOnDisk isDirectory: &isDir])
+ {
+ if (verbose)
+ {
+ /* FIXME - Perhaps we should not actually ignore it but
+ act as if it were there. */
+ NSLog (@"Warning - destination file '%@' not found - ignored",
+ pathOnDisk);
+ }
+ return;
+ }
+ else
+ {
+ if (isDir)
+ {
+ HTMLDirectoryEnumerator *e;
+ NSString *filename;
+
+ e = [HTMLDirectoryEnumerator alloc];
+ e = [e initWithBasePath: pathOnDisk];
+
+ while ((filename = [e nextObject]) != nil)
+ {
+ [self registerFile: filename];
+ }
+ return;
+ }
+ }
+ }
+
+ /* Manage pathMappings: try to match any of the pathMappings against
+ pathOnDisk, and perform the path mapping if we can match. */
+ {
+ NSEnumerator *e = [pathMappings keyEnumerator];
+ NSString *key;
+
+ while ((key = [e nextObject]))
+ {
+ if ([pathOnDisk hasPrefix: key])
+ {
+ NSString *value = [pathMappings objectForKey: key];
+
+ fullPath = [pathOnDisk substringFromIndex: [key length]];
+ fullPath = [value stringByAppendingPathComponent: fullPath];
+ break;
+ }
+ }
+ }
+
+ /* Save the file properly prepared into our dictionary of
+ destination files. */
+ file = [[DestinationFile alloc] initWithFullName: fullPath
+ pathOnDisk: pathOnDisk];
+ [files setObject: file forKey: [pathOnDisk lastPathComponent]];
+ RELEASE (file);
+}
+
+- (void)registerPathMappings: (NSDictionary *)dict
+{
+ NSEnumerator *e = [dict keyEnumerator];
+ NSString *key;
+
+ while ((key = [e nextObject]))
+ {
+ NSString *value = [dict objectForKey: key];
+ [pathMappings setObject: value forKey: key];
+ }
}
- (NSString *)resolveLink: (NSString *)link
@@ -809,6 +962,12 @@
NSString *nameLink;
NSString *relocatedFileLink;
DestinationFile *file;
+
+ /* Do nothing if this is evidently *not* a dynamical link to fixup. */
+ if ([link hasPrefix: @"mailto:"] || [link hasPrefix: @"news:"])
+ {
+ return link;
+ }
{
/* Break the link string into fileLink (everything which is before
@@ -840,44 +999,61 @@
}
}
- /* Now lookup fileLink. First, extract the path-less filename,
- because it might have already been fixed up by a previous run of
- the linker. */
- fileLink = [fileLink lastPathComponent];
+ /* Now lookup fileLink. */
- /* Now simply look it up in our list of files. */
- file = [files objectForKey: fileLink];
-
- /* Not found - leave it unfixed. */
- if (file == nil)
+ /* If it's "", it means a reference to something inside the same
+ file. Leave it untouched - no fixup needed. Normally these
+ should not be marked as need linker fixup anyway. */
+ if ([fileLink isEqualToString: @""])
{
- if (verbose || checkLinks)
+ if (checkLinks)
{
- NSString *m;
-
- m = [NSString stringWithFormat:
- @"%@: Unresolved reference to file '%@'\n",
- logFile, fileLink];
- fprintf (stderr, [m lossyCString]);
+ /* FIXME - not really the linker's task, but we might want
+ to add checking of intra-document links. */
}
relocatedFileLink = fileLink;
}
else
{
- relocatedFileLink = [file fullName];
-
- if (checkLinks)
+ /* First, extract the path-less filename, because it might have
+ already been fixed up by a previous run of the linker. */
+ fileLink = [fileLink lastPathComponent];
+
+ /* Now simply look it up in our list of files. */
+ file = [files objectForKey: fileLink];
+
+ /* Not found - leave it unfixed. */
+ if (file == nil)
{
- if (![file checkAnchorName: nameLink])
+ if (verbose || checkLinks)
{
NSString *m;
m = [NSString stringWithFormat:
- @"%@: Unresolved reference to '%@' in file '%@'\n",
- logFile, nameLink, fileLink];
+ @"%@: Unresolved reference to file '%@'\n",
+ logFile, fileLink];
fprintf (stderr, [m lossyCString]);
}
+
+ relocatedFileLink = fileLink;
+ }
+ else
+ {
+ relocatedFileLink = [file fullName];
+
+ if (checkLinks)
+ {
+ if (![file checkAnchorName: nameLink])
+ {
+ NSString *m;
+
+ m = [NSString stringWithFormat:
+ @"%@: Unresolved reference to '%@' in file '%@'\n",
+ logFile, nameLink, fileLink];
+ fprintf (stderr, [m lossyCString]);
+ }
+ }
}
}
@@ -906,7 +1082,10 @@ static void print_help_and_exit ()
printf (" --help: print this message;\n");
printf (" --version: print version information;\n");
printf (" -Verbose YES: print verbose messages;\n");
- printf (" -CheckLinks YES: check links as they are fixed up;\n");
+ printf (" -CheckLinks NO: do not check links as they are fixed up;\n");
+ printf (" -FixupAllLinks YES: attempt to fixup all links (not only dynamical ones);\n");
+ printf (" -PathMappingsFile file: read path mappings from file (a dictionary);\n");
+ printf (" -PathMappings '{\"/usr/doc\"=\"/Doc\";}': use the supplied path mappings;\n");
exit (0);
}
@@ -926,7 +1105,7 @@ int main (int argc, char** argv, char** env)
NSArray *args;
NSMutableArray *inputFiles;
unsigned i, count;
- BOOL verbose, checkLinks;
+ BOOL verbose, checkLinks, fixupAllLinks;
HTMLLinker *linker;
BOOL destinations;
@@ -934,20 +1113,93 @@ int main (int argc, char** argv, char** env)
[NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
+ /* Set up the cache. */
+ fileManager = [NSFileManager defaultManager];
+ currentPath = [fileManager currentDirectoryPath];
+
+ /* Read basic defaults. */
userDefs = [NSUserDefaults standardUserDefaults];
+ /* defaults are -
+ -Verbose NO
+ -CheckLinks YES
+ -FixupAllLinks NO
+ */
+ [userDefs registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys:
+ @"YES", @"CheckLinks", nil]];
+
verbose = [userDefs boolForKey: @"Verbose"];
checkLinks = [userDefs boolForKey: @"CheckLinks"];
+ fixupAllLinks = [userDefs boolForKey: @"FixupAllLinks"];
+ /* Create the linker object. */
linker = [[HTMLLinker alloc] initWithVerboseFlag: verbose
checkLinksFlag: checkLinks];
+ /*
+ To specify that a destination directory on disk is to be referred to
+ with a different path, you can use -PathMapping, as in
+
+ -PathMapping '{
+ "/opt/gnustep/System/Documentation/Base"="/Documentation/Base";
+ "/opt/gnustep/System/Documentation/Gui"="/Documentation/Gui";
+ }'
+
+ which causes all links to destination files which have the path
+ beginnig with /opt/gnustep/System/Documentation/Base to be resolved as
+ being to files with a path beginning with /Documentation/Base.
+
+ This is only useful if you are serving HTML files off from a web server,
+ where the actual path on disk is not the same as the path seen by the
+ web browser.
+
+ -PathMappingFile filename
+
+ causes path mappings to be read from filename, which should contain
+ them in dictionary format.
+ */
+
+ /* Read path mappings from PathMappingsFile if specified. */
+ {
+ NSString *pathMapFile = [userDefs stringForKey: @"PathMappingsFile"];
+
+ if (pathMapFile != nil)
+ {
+ NSDictionary *mappings;
+
+ mappings = [NSDictionary dictionaryWithContentsOfFile: pathMapFile];
+
+ if (mappings == nil)
+ {
+ NSLog (@"Warning - %@ does not contain a dictionary - ignored",
+ pathMapFile);
+ }
+ else
+ {
+ [linker registerPathMappings: mappings];
+ }
+ }
+ }
+
+ /* Add PathMappings specified on the command line if any. */
+ {
+ NSDictionary *paths = [userDefs dictionaryForKey: @"PathMappings"];
+
+ if (paths != nil)
+ {
+ [linker registerPathMappings: paths];
+ }
+ }
+
/* All non-options on the command line are:
-
+
input files if they come before --Destinations
-
+
destination files if they come after --Destinations
-
+
+ Directories as input files or destination files means 'all .html, .htm,
+ .HTML, .HTM files in the directory and subdirectories'.
+
*/
args = [[NSProcessInfo processInfo] arguments];
@@ -995,23 +1247,38 @@ int main (int argc, char** argv, char** env)
{
if (destinations)
{
- DestinationFile *d;
-
- if (![arg isAbsolutePath])
- {
- /* Not sure what to do here ... will think about it
- tomorrow. */
- NSLog (@"Warning - %@ is not an absolute filename!", arg);
- }
-
- d = [[DestinationFile alloc] initWithFullName: arg
- pathOnDisk: arg];
- [linker registerFile: d];
- RELEASE (d);
+ [linker registerFile: arg];
}
else
{
- [inputFiles addObject: arg];
+ BOOL isDir;
+
+ if (![fileManager fileExistsAtPath: arg
+ isDirectory: &isDir])
+ {
+ NSLog (@"Warning - input file '%@' not found - ignored",
+ arg);
+ }
+ else
+ {
+ if (isDir)
+ {
+ HTMLDirectoryEnumerator *e;
+ NSString *filename;
+
+ e = [HTMLDirectoryEnumerator alloc];
+ e = [e initWithBasePath: arg];
+
+ while ((filename = [e nextObject]) != nil)
+ {
+ [inputFiles addObject: filename];
+ }
+ }
+ else
+ {
+ [inputFiles addObject: arg];
+ }
+ }
}
}
}
@@ -1035,7 +1302,8 @@ int main (int argc, char** argv, char** env)
parser = [[HTMLParser alloc] initWithCode: inputFileContents];
inputFileContents = [parser resolveLinksUsingHTMLLinker: linker
- logFile: inputFile];
+ logFile: inputFile
+ fixupAllLinks: fixupAllLinks];
[inputFileContents writeToFile: inputFile
atomically: YES];
RELEASE (parser);