changeset 459:1b5f1ce09f38

Fix to the image cache, cleaned stuff.
author Jari-Matti M?kel? <jmjm@iki.fi>
date Tue, 30 Oct 2007 16:58:17 +0200
parents bac1d75f87f8
children 77c17bbe9d20
files trunk/src/docgen/docgen.d trunk/src/docgen/document/generator.d trunk/src/docgen/document/htmlwriter.d trunk/src/docgen/document/latexwriter.d trunk/src/docgen/document/plaintextwriter.d trunk/src/docgen/document/writer.d trunk/src/docgen/document/writers.d trunk/src/docgen/document/xmlwriter.d trunk/src/docgen/graphutils/dotwriter.d trunk/src/docgen/graphutils/writer.d trunk/src/docgen/graphutils/writers.d trunk/src/docgen/misc/misc.d
diffstat 12 files changed, 148 insertions(+), 555 deletions(-) [+]
line wrap: on
line diff
--- a/trunk/src/docgen/docgen.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/docgen.d	Tue Oct 30 16:58:17 2007 +0200
@@ -22,20 +22,20 @@
 
 
 class HTMLDocGenerator : DefaultDocGenerator!("html") {
-  this(DocGeneratorOptions options) {
-    super(options);
+  this(DocGeneratorOptions options, ParserDg parser) {
+    super(options, parser);
   }
   public void generate() { /* TODO */ }
 }
 class XMLDocGenerator : DefaultDocGenerator!("xml") {
-  this(DocGeneratorOptions options) {
-    super(options);
+  this(DocGeneratorOptions options, ParserDg parser) {
+    super(options, parser);
   }
   public void generate() { /* TODO */ }
 }
 class PlainTextDocGenerator : DefaultDocGenerator!("txt") {
-  this(DocGeneratorOptions options) {
-    super(options);
+  this(DocGeneratorOptions options, ParserDg parser) {
+    super(options, parser);
   }
   public void generate() { /* TODO */ }
 }
@@ -44,8 +44,8 @@
  * Main routine for LaTeX doc generation.
  */
 class LaTeXDocGenerator : DefaultCachingDocGenerator!("latex") {
-  this(DocGeneratorOptions options) {
-    super(options);
+  this(DocGeneratorOptions options, ParserDg parser, GraphCache graphcache) {
+    super(options, parser, graphcache);
   }
 
   /**
@@ -179,24 +179,79 @@
   options.parser.importPaths = args[2..$-1];
   options.outputDir = args[$-1];
 
+  Module[] cachedModules;
+  Edge[] cachedEdges;
+  Vertex[char[]] cachedVertices;
+
+  void parser(ref Module[] modules, ref Edge[] edges, ref Vertex[char[]] vertices) {
+    if (cachedModules != null) {
+      modules = cachedModules;
+      edges = cachedEdges;
+      vertices = cachedVertices;
+      return;
+    }
+
+    int id = 1;
+
+    Parser.loadModules(
+      options.parser.rootPaths,
+      options.parser.importPaths,
+      options.parser.strRegexps,
+      options.graph.includeUnlocatableModules,
+      options.graph.depth,
+      (char[] fqn, char[] path, Module m) {
+        if (m is null) {
+          if (fqn in vertices) {
+            debug Stdout.format("{} already set.\n", fqn);
+            return;
+
+          }
+          auto vertex = new Vertex(fqn, path, id++);
+          vertex.type = VertexType.UnlocatableModule;
+          vertices[fqn] = vertex;
+          debug Stdout.format("Setting {} = {}.\n", fqn, path);
+
+        } else {
+          vertices[m.moduleFQN] = new Vertex(m.moduleFQN, m.filePath, id++);
+          debug Stdout.format("Setting {} = {}.\n", m.moduleFQN, m.filePath);
+        }
+      },
+      (Module imported, Module importer) {
+        debug Stdout.format("Connecting {} - {}.\n", imported.moduleFQN, importer.moduleFQN);
+        edges ~= vertices[imported.moduleFQN].addChild(vertices[importer.moduleFQN]);
+      },
+      modules
+    );
+
+    modules.sort(
+      (Module a, Module b){ return icompare(a.moduleFQN, b.moduleFQN); }
+    );
+
+    cachedVertices = vertices;
+    cachedModules = modules;
+    cachedEdges = edges;
+  }
+  
+  GraphCache graphcache = new DefaultGraphCache();
+
   foreach(format; options.outputFormats) {
     DocGenerator generator;
 
     switch(format) {
       case DocFormat.LaTeX:
-        generator = new LaTeXDocGenerator(*options);
+        generator = new LaTeXDocGenerator(*options, &parser, graphcache);
         Stdout("Generating LaTeX docs..");
         break;
       case DocFormat.HTML:
-        generator = new HTMLDocGenerator(*options);
+        generator = new HTMLDocGenerator(*options, &parser);
         Stdout("Generating HTML docs..");
         break;
       case DocFormat.XML:
-        generator = new XMLDocGenerator(*options);
+        generator = new XMLDocGenerator(*options, &parser);
         Stdout("Generating XML docs..");
         break;
       case DocFormat.PlainText:
-        generator = new PlainTextDocGenerator(*options);
+        generator = new PlainTextDocGenerator(*options, &parser);
         Stdout("Generating plain text docs..");
         break;
       default: throw new Exception("Format not supported");
--- a/trunk/src/docgen/document/generator.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/document/generator.d	Tue Oct 30 16:58:17 2007 +0200
@@ -13,9 +13,12 @@
 import tango.io.FilePath;
 debug import tango.io.Stdout;
 
+alias void delegate(ref Module[], ref Edge[], ref Vertex[char[]]) ParserDg;
+
 template DefaultDocGenerator(char[] genDir) {
   abstract class DefaultDocGenerator : DocGenerator {
     DocGeneratorOptions m_options;
+    ParserDg m_parser;
     PageWriter docWriter;
 
     GraphWriterFactory graphFactory;
@@ -25,8 +28,9 @@
     Edge[] edges;
     Vertex[char[]] vertices;
 
-    this(DocGeneratorOptions options) {
+    this(DocGeneratorOptions options, ParserDg parser) {
       m_options = options;
+      m_parser = parser;
 
       createGraphWriterFactory();
       createPageWriterFactory();
@@ -35,8 +39,6 @@
       (new FilePath(options.outputDir ~ "/" ~ genDir)).create();
     }
 
-    // TODO: constructor for situations where parsing has happened elsewhere
-
     protected void createGraphWriterFactory() {
       graphFactory = new DefaultGraphWriterFactory(this);
     }
@@ -50,41 +52,7 @@
     }
 
     protected void parseSources() {
-      int id = 1;
-
-      Parser.loadModules(
-        options.parser.rootPaths,
-        options.parser.importPaths,
-        options.parser.strRegexps,
-        options.graph.includeUnlocatableModules,
-        options.graph.depth,
-        (char[] fqn, char[] path, Module m) {
-          if (m is null) {
-            if (fqn in vertices) {
-              debug Stdout.format("{} already set.\n", fqn);
-              return;
-
-            }
-            auto vertex = new Vertex(fqn, path, id++);
-            vertex.type = VertexType.UnlocatableModule;
-            vertices[fqn] = vertex;
-            debug Stdout.format("Setting {} = {}.\n", fqn, path);
-
-          } else {
-            vertices[m.moduleFQN] = new Vertex(m.moduleFQN, m.filePath, id++);
-            debug Stdout.format("Setting {} = {}.\n", m.moduleFQN, m.filePath);
-          }
-        },
-        (Module imported, Module importer) {
-          debug Stdout.format("Connecting {} - {}.\n", imported.moduleFQN, importer.moduleFQN);
-          edges ~= vertices[imported.moduleFQN].addChild(vertices[importer.moduleFQN]);
-        },
-        modules
-      );
-
-      modules.sort(
-        (Module a, Module b){ return icompare(a.moduleFQN, b.moduleFQN); }
-      );
+      m_parser(modules, edges, vertices);
     }
 
     void createDepGraph(char[] depGraphFile) {
@@ -105,23 +73,15 @@
 
 template DefaultCachingDocGenerator(char[] genDir) {
   abstract class DefaultCachingDocGenerator : DefaultDocGenerator!(genDir), CachingDocGenerator {
-    this(DocGeneratorOptions options) {
-      super(options);
+    GraphCache m_graphCache;
+
+    this(DocGeneratorOptions options, ParserDg parser, GraphCache graphCache) {
+      super(options, parser);
+      m_graphCache = graphCache;
     }
     
-    private char[][Object[]][Object[]][GraphFormat] m_graphCache;
-
-    char[] getCachedGraph(Object[] vertices, Object[] edges, GraphFormat format) {
-      auto lookup1 = format in m_graphCache;
-      if (lookup1) {
-        auto lookup2 = edges in *lookup1;
-        if (lookup2) {
-          auto lookup3 = vertices in *lookup2;
-          if (lookup3)
-            return *lookup3;
-        }
-      }
-      return null;
+    GraphCache graphCache() {
+      return m_graphCache;
     }
 
     protected void createGraphWriterFactory() {
--- a/trunk/src/docgen/document/htmlwriter.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.htmlwriter;
-
-import docgen.document.writer;
-import docgen.misc.textutils;
-import tango.io.FileConduit : FileConduit;
-
-// TODO: this is mostly broken now
-
-/**
- * Writes a HTML document skeleton.
- */
-class HTMLWriter : AbstractDocumentWriter!(2, "html") {
-  this(DocumentWriterFactory factory, OutputStream[] outputs) {
-    super(factory, outputs);
-  }
-
-  void generateTOC(Module[] modules) {
-    // TODO
-    print.format(templates["toc"]);
-  }
-
-  void generateModuleSection() {
-    // TODO
-    print.format(templates["modules"]);
-  }
-
-  void generateListingSection() {
-    // TODO
-    print.format(templates["listings"]);
-  }
-
-  void generateDepGraphSection() {
-    // TODO
-    print.format(templates["dependencies"]);
-  }
-
-  void generateIndexSection() { }
-
-  void generateLastPage() { }
-
-  void generateFirstPage() {
-    print.format(
-      templates["firstpage"],
-      factory.options.templates.title,
-      factory.options.templates.copyright,
-      factory.options.templates.versionString,
-      docgen_version
-    );
-  }
-
-  void addList(char[][] contents, bool ordered) {
-    foreach(item; contents) {
-      switch(item) {
-        case "(": print(ordered ? "<ol>" : "<ul>"); continue;
-        case ")": print(ordered ? "</ol>" : "</ul>"); continue;
-        default: print("<li>")(xml_escape(item))("</li>");
-      }
-    }
-  }
-}
--- a/trunk/src/docgen/document/latexwriter.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.latexwriter;
-
-import docgen.document.writer;
-import tango.io.FileConduit : FileConduit;
-
-/**
- * Writes a LaTeX document skeleton.
- */
-class LaTeXWriter : AbstractDocumentWriter!(1, "latex") {
-  this(DocumentWriterFactory factory, OutputStream[] outputs) {
-    super(factory, outputs);
-  }
-
-  void generateFirstPage() {
-    print.format(
-      templates["firstpage"],
-      factory.options.templates.paperSize,
-      factory.options.templates.title,
-      factory.options.templates.versionString,
-      docgen_version,
-      timeNow(),
-      factory.options.listing.literateStyle ? "" : "%"
-    );
-  }
-
-  void addList(char[][] contents, bool ordered) {
-    foreach(item; contents) {
-      switch(item) {
-        case "(": print(ordered ? "\\begin{enumerate}" : "\\begin{itemize}"); continue;
-        case ")": print(ordered ? "\\end{enumerate}" : "\\end{itemize}"); continue;
-        default: print("\\item")(item)(\n);
-      }
-    }
-  }
-}
--- a/trunk/src/docgen/document/plaintextwriter.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.plaintextwriter;
-
-import docgen.document.writer;
-import docgen.misc.textutils;
-import tango.io.FileConduit : FileConduit;
-
-//TODO: this is mostly broken now
-
-/**
- * Writes a plain text document skeleton.
- */
-class PlainTextWriter : AbstractDocumentWriter!(1, "plaintext") {
-  this(DocumentWriterFactory factory, OutputStream[] outputs) {
-    super(factory, outputs);
-  }
-
-  void generateTOC(Module[] modules) {
-    // TODO
-    print.format(templates["toc"]);
-  }
-
-  void generateModuleSection() {
-    // TODO
-    print.format(templates["modules"]);
-  }
-
-  void generateListingSection() {
-    // TODO
-    print.format(templates["listings"]);
-  }
-
-  void generateDepGraphSection() {
-    // TODO
-    print.format(templates["dependencies"]);
-  }
-
-  void generateIndexSection() { }
-
-  void generateLastPage() { }
-
-  void generateFirstPage() {
-    print(
-      plainTextHeading(factory.options.templates.title ~ " Reference Manual") ~
-      factory.options.templates.versionString ~ \n ~
-      "Generated by " ~ docgen_version ~ \n ~
-      timeNow() ~ \n \n \n ~
-      plainTextHorizLine() ~ \n \n ~
-      plainTextHeading("Table of Contents") ~ \n ~
-      plainTextHeading("Module documentation") ~ \n ~
-      plainTextHeading("File listings") ~ \n ~
-      plainTextHeading("Dependency diagram") ~ \n
-    );
-  }
-
-  void addList(char[][] contents, bool ordered) {
-    uint[] counters;
-    foreach(item; contents) {
-      switch(item) {
-        case "(": counters ~= 1; continue;
-        case ")": counters.length = counters.length - 1; continue;
-        default:
-        if (counters.length>0)
-          for (int i=0; i <= counters.length; i++)
-            print(" ");
-        if (ordered)
-          print(++counters[$-1])(". ")(item)(\n);
-        else
-          print("* ")(item)(\n);
-      }
-    }
-  }
-}
--- a/trunk/src/docgen/document/writer.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,197 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.writer;
-
-public import docgen.misc.misc;
-import tango.io.model.IConduit : OutputStream;
-import tango.util.time.Date;
-import tango.util.time.Clock;
-import tango.text.convert.Sprint;
-import tango.io.stream.FileStream;
-import tango.io.Stdout;
-import tango.io.Print: Print;
-import tango.text.convert.Layout : Layout;
-public import docgen.misc.parser;
-
-const templateDir = "docgen/templates/";
-
-// template file names
-const templateNames = [
-  "firstpage"[], "toc"[], "modules"[],
-  "listings"[], "dependencies"[], "index"[],
-  "lastpage"[], "langdef"[], "makefile"[],
-  "graphics"[], "listing"[]
-];
-
-/**
- * Writes the logical subcomponents of a document,
- * e.g. sections, embedded graphics, lists
- */
-interface DocumentWriter {
-  /**
-   * Updates the outputstreams.
-   */
-  void setOutput(OutputStream[] outputs);
-
-  /**
-   * Generates the first page(s).
-   */
-  void generateFirstPage();
-
-  /**
-   * Generates table of contents.
-   */
-  void generateTOC(Module[] modules);
-
-  /**
-   * Generates module documentation section.
-   */
-  void generateModuleSection();
-
-  /**
-   * Generates source code listing section.
-   */
-  void generateListingSection();
-
-  /**
-   * Generates dependency graph section.
-   */
-  void generateDepGraphSection();
-
-  /**
-   * Generates an index section.
-   */
-  void generateIndexSection();
-
-  /**
-   * Generates the last page(s).
-   */
-  void generateLastPage();
-
-  /**
-   * Generates a language definition file [LaTeX].
-   * Could be used for DTD too, I suppose.
-   */
-  void generateLangDef();
-
-  /**
-   * Generates a makefile used for document post-processing.
-   */
-  void generateMakeFile();
-  
-  // --- page components
-  //
-  /*
-   * Adds an external graphics file. 
-   */
-  void addGraphics(char[] imageFile);
-  
-  /**
-   * Adds a source code listing.
-   */
-  void addListing(char[] moduleName, char[] contents, bool inline = true);
-
-  /**
-   * Adds a list of items.
-   */
-  void addList(char[][] contents, bool ordered);
-}
-
-interface DocumentWriterFactory : WriterFactory {
-  DocumentWriter createDocumentWriter(OutputStream[] outputs, DocFormat outputFormat);
-}
-
-
-char[] timeNow() {
-  auto date = Clock.toDate;
-  auto sprint = new Sprint!(char);
-  return sprint.format("{0} {1} {2} {3}",
-    date.asDay(),
-    date.asMonth(),
-    date.day,
-    date.year);
-}
-
-char[] loadTemplate(char[] style, char[] format, char[] templateName) {
-  char[] fn = templateDir~style~"/"~format~"/"~templateName~".tpl";
-  
-  scope(failure) {
-    Stderr("Warning: error opening template "~fn~".");
-    return null;
-  }
-
-  auto file = new FileInput(fn);
-  auto content = new char[file.length];
-  auto bytesRead = file.read(content);
-  
-  assert(bytesRead == file.length, "Error reading template");
-  
-  file.close();
-  
-  return content;
-}
-
-template AbstractDocumentWriter(int n, char[] format) {
-  abstract class AbstractDocumentWriter : AbstractWriter!(DocumentWriterFactory, n), DocumentWriter {
-    protected char[][char[]] templates;
-    protected Print!(char) print;
-         
-    this(DocumentWriterFactory factory, OutputStream[] outputs) {
-      super(factory, outputs);
-      setOutput(outputs);
-    
-      foreach(tpl; templateNames) {
-        templates[tpl] = loadTemplate(factory.options.templates.templateStyle, format, tpl);
-      }
-    }
-
-    void setOutput(OutputStream[] outputs) {
-      this.outputs = outputs;
-
-      print = new Print!(char)(new Layout!(char), outputs[0]);
-    }
-
-    void generateTOC(Module[] modules) {
-      print.format(templates["toc"]);
-    }
-
-    void generateModuleSection() {
-      print.format(templates["modules"]);
-    }
-
-    void generateListingSection() {
-      print.format(templates["listings"]);
-    }
-
-    void generateDepGraphSection() {
-      print.format(templates["dependencies"]);
-    }
-
-    void generateIndexSection() {
-      print.format(templates["index"]);
-    }
-
-    void generateLastPage() {
-      print.format(templates["lastpage"]);
-    }
-
-    void generateLangDef() {
-      print(templates["langdef"]);
-    }
-
-    void generateMakeFile() {
-      print(templates["makefile"]);
-    }
-
-
-    void addGraphics(char[] imageFile) {
-      print.format(templates["graphics"], imageFile);
-    }
-    
-    void addListing(char[] moduleName, char[] contents, bool inline) {
-      print.format(templates["listing"], moduleName, contents);
-    }
-  }
-}
--- a/trunk/src/docgen/document/writers.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.writers;
-
-public import docgen.document.writer;
-import docgen.document.htmlwriter;
-import docgen.document.xmlwriter;
-import docgen.document.plaintextwriter;
-import docgen.document.latexwriter;
-
-class DefaultDocumentWriterFactory : AbstractWriterFactory, DocumentWriterFactory {
-  this(DocGenerator generator) {
-    super(generator);
-  }
-
-  DocumentWriter createDocumentWriter(OutputStream[] outputs, DocFormat outputFormat) {
-    switch (outputFormat) {
-      case DocFormat.LaTeX:
-        return new LaTeXWriter(this, outputs);
-      case DocFormat.XML:
-        return new XMLWriter(this, outputs);
-      case DocFormat.HTML:
-        return new HTMLWriter(this, outputs);
-      case DocFormat.PlainText:
-        return new PlainTextWriter(this, outputs);
-      default:
-        throw new Exception("Document writer type does not exist!");
-    }
-  }
-}
--- a/trunk/src/docgen/document/xmlwriter.d	Tue Oct 30 15:44:05 2007 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/**
- * Author: Jari-Matti Mäkelä
- * License: GPL3
- */
-module docgen.document.xmlwriter;
-
-import docgen.document.writer;
-import docgen.misc.textutils;
-import tango.io.FileConduit : FileConduit;
-
-//TODO: this is mostly broken now
-
-/**
- * TODO
- */
-class XMLWriter : AbstractDocumentWriter!(2, "xml") {
-  this(DocumentWriterFactory factory, OutputStream[] outputs) {
-    super(factory, outputs);
-  }
-
-  void generateTOC(Module[] modules) {
-    // TODO
-    print.format(templates["toc"]);
-  }
-
-  void generateModuleSection() {
-    // TODO
-    print.format(templates["modules"]);
-  }
-
-  void generateListingSection() {
-    // TODO
-    print.format(templates["listings"]);
-  }
-
-  void generateDepGraphSection() {
-    // TODO
-    print.format(templates["dependencies"]);
-  }
-
-  void generateIndexSection() { }
-
-  void generateLastPage() { }
-
-  void generateFirstPage() { }
-
-  void addList(char[][] contents, bool ordered) {
-    foreach(item; contents) {
-      switch(item) {
-        case "(": print(ordered ? "<ol>" : "<ul>"); continue;
-        case ")": print(ordered ? "</ol>" : "</ul>"); continue;
-        default: print("<li>")(xml_escape(item))("</li>");
-      }
-    }
-  }
-}
--- a/trunk/src/docgen/graphutils/dotwriter.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/graphutils/dotwriter.d	Tue Oct 30 16:58:17 2007 +0200
@@ -9,6 +9,7 @@
 import tango.text.convert.Layout : Layout;
 import tango.io.FilePath;
 import tango.text.Util;
+import tango.text.convert.Sprint;
 debug import tango.io.Stdout;
 
 /**
@@ -19,8 +20,9 @@
     super(factory, writer);
   }
 
-  void generateDepImageFile(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
-    auto image = new Print!(char)(new Layout!(char), imageFile);
+  char[] generateDepImageFile(Vertex[] vertices, Edge[] edges) {
+    char[] image;
+    auto sprint = new Sprint!(char);
 
     Vertex[][char[]] verticesByPckgName;
     if (factory.options.graph.groupByFullPackageName ||
@@ -39,7 +41,7 @@
         factory.options.graph.highlightCyclicEdges)
       findCycles(vertices, edges);
 
-    image("Digraph ModuleDependencies {\n");
+    image ~= "Digraph ModuleDependencies {\n";
 
     foreach (module_; vertices) {
       auto nodeName = 
@@ -47,7 +49,7 @@
         module_.name.split(".")[$-1] :
         module_.name;
 
-      image.format(
+      image ~= sprint.format(
         `  n{0} [label="{1}"{2}];`\n,
         module_.id,
         nodeName,
@@ -62,7 +64,7 @@
     }
 
     foreach (edge; edges)
-      image.format(
+      image ~= sprint.format(
         `  n{0} -> n{1}{2};`\n,
         edge.outgoing.id,
         edge.incoming.id,
@@ -79,20 +81,20 @@
             char[] pkg;
             foreach(part; name) {
               pkg ~= part ~ ".";
-              image.format(
+              image ~= sprint.format(
                 `subgraph "cluster_{0}" {{`\n`  label="{0}"`\n,
                 pkg[0..$-1],
                 pkg[0..$-1]
               );
             }
             for (int i=0; i< name.length; i++) {
-              image("}\n");
+              image ~= "}\n";
             }
           }
         }
       }
       foreach (packageName, vertices; verticesByPckgName) {
-        image.format(
+        image ~= sprint.format(
           `  subgraph "cluster_{0}" {{`\n`  label="{0}";color=`
           ~ factory.options.graph.clusterColor ~ `;`\n`  `,
           packageName,
@@ -100,11 +102,13 @@
         );
 
         foreach (module_; vertices)
-          image.format(`n{0};`, module_.id);
-        image("\n  }\n");
+          image ~= sprint.format(`n{0};`, module_.id);
+        image ~= "\n  }\n";
       }
 
-    image("}");
+    image ~= "}";
+
+    return image;
   }
         
   void generateImageTag(OutputStream imageFile) {
@@ -119,7 +123,10 @@
 
   protected void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
     generateImageTag(imageFile);
-    generateDepImageFile(vertices, edges, imageFile);
+    
+    auto image = generateDepImageFile(vertices, edges);
+    auto printer = new Print!(char)(new Layout!(char), imageFile);
+    printer(image);
   }
 }
 
@@ -128,21 +135,22 @@
 
   this(CachingGraphWriterFactory factory, PageWriter writer) {
     super(factory, writer);
+    this.factory = factory;
   }
 
   protected void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
     generateImageTag(imageFile);
 
-    auto cached = factory.getCachedGraph(vertices, edges, GraphFormat.Dot);
+    auto cached = factory.graphCache.getCachedGraph(vertices, edges, GraphFormat.Dot);
 
+    auto printer = new Print!(char)(new Layout!(char), imageFile);
+    
     if (cached) {
-      auto image = new Print!(char)(new Layout!(char), imageFile);
-      
-      if (cached) {
-        debug Stdout("Image cache hit.\n");
-        image(cached);
-      } else
-        generateDepImageFile(vertices, edges, imageFile);
+      printer(cached);
+    } else {
+      auto image = generateDepImageFile(vertices, edges);
+      factory.graphCache.setCachedGraph(vertices, edges, GraphFormat.Dot, image);
+      printer(image);
     }
   }
 }
--- a/trunk/src/docgen/graphutils/writer.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/graphutils/writer.d	Tue Oct 30 16:58:17 2007 +0200
@@ -18,7 +18,7 @@
 }
 
 interface CachingGraphWriterFactory : GraphWriterFactory {
-  char[] getCachedGraph(Vertex[] vertices, Edge[] edges, GraphFormat format);
+  GraphCache graphCache();
 }
 
 /**
@@ -76,3 +76,31 @@
     this.writer = writer;
   }
 }
+
+class DefaultGraphCache : GraphCache {
+  private char[][Object[]][Object[]][GraphFormat] m_graphCache;
+
+  char[] getCachedGraph(Object[] vertices, Object[] edges, GraphFormat format) {
+    debug Stdout("Starting graph lookup\n");
+    debug Stdout(&vertices, &edges, format).newline;
+    debug Stdout(&m_graphCache).newline;
+    
+    auto lookup1 = format in m_graphCache;
+    if (lookup1) {
+      auto lookup2 = edges in *lookup1;
+      if (lookup2) {
+        auto lookup3 = vertices in *lookup2;
+        if (lookup3)
+          return *lookup3;
+      }
+    }
+    debug Stdout("Graph cache miss!\n");
+    return null;
+  }
+
+  void setCachedGraph(Object[] vertices, Object[] edges, GraphFormat format, char[]
+      contents) {
+    m_graphCache[format][edges][vertices] = contents;
+      debug Stdout("Graph cache updated!\n");
+    }
+}
--- a/trunk/src/docgen/graphutils/writers.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/graphutils/writers.d	Tue Oct 30 16:58:17 2007 +0200
@@ -28,21 +28,22 @@
   }
 }
 
-class DefaultCachingGraphWriterFactory : AbstractWriterFactory, GraphWriterFactory {
+class DefaultCachingGraphWriterFactory : AbstractWriterFactory, CachingGraphWriterFactory {
   CachingDocGenerator generator;
 
   this(CachingDocGenerator generator) {
     super(generator);
+    this.generator = generator;
   }
 
-  char[] getCachedGraph(Vertex[] vertices, Edge[] edges, GraphFormat format) {
-    return generator.getCachedGraph(vertices, edges, format);
+  GraphCache graphCache() {
+    return generator.graphCache;
   }
 
   GraphWriter createGraphWriter(PageWriter writer, GraphFormat outputFormat) {
     switch (outputFormat) {
       case GraphFormat.Dot:
-        return new DotWriter(this, writer);
+        return new CachingDotWriter(this, writer);
       case GraphFormat.ModuleNames:
         return new ModuleNameWriter(this, writer);
       case GraphFormat.ModulePaths:
--- a/trunk/src/docgen/misc/misc.d	Tue Oct 30 15:44:05 2007 +0200
+++ b/trunk/src/docgen/misc/misc.d	Tue Oct 30 16:58:17 2007 +0200
@@ -122,8 +122,13 @@
   void generate();
 }
 
+interface GraphCache {  
+  char[] getCachedGraph(Object[] vertices, Object[] edges, GraphFormat format);
+  void setCachedGraph(Object[] vertices, Object[] edges, GraphFormat format, char[] contents);
+}
+
 interface CachingDocGenerator : DocGenerator {
-  char[] getCachedGraph(Object[] vertices, Object[] edges, GraphFormat format);
+  GraphCache graphCache();
 }
 
 interface WriterFactory {