changeset 457:33a4cb255fcc

Cached images, small fixes, reorganizing.
author Jari-Matti M?kel? <jmjm@iki.fi>
date Tue, 30 Oct 2007 15:41:30 +0200
parents de2675bc9afa
children bac1d75f87f8
files trunk/src/docgen/config/configurator.d trunk/src/docgen/config/default.cfg trunk/src/docgen/config/reader.d trunk/src/docgen/docgen.d trunk/src/docgen/document/generator.d trunk/src/docgen/graphutils/dotwriter.d trunk/src/docgen/graphutils/modulenamewriter.d trunk/src/docgen/graphutils/modulepathwriter.d trunk/src/docgen/graphutils/writer.d trunk/src/docgen/graphutils/writers.d trunk/src/docgen/misc/misc.d trunk/src/docgen/page/htmlwriter.d trunk/src/docgen/page/latexwriter.d trunk/src/docgen/page/plaintextwriter.d trunk/src/docgen/page/writer.d trunk/src/docgen/page/writers.d trunk/src/docgen/page/xmlwriter.d trunk/src/docgen/sourcelisting/htmlwriter.d trunk/src/docgen/sourcelisting/latexwriter.d trunk/src/docgen/sourcelisting/plaintextwriter.d trunk/src/docgen/sourcelisting/writer.d trunk/src/docgen/sourcelisting/writers.d trunk/src/docgen/sourcelisting/xmlwriter.d trunk/src/docgen/tests/doctemplate.d trunk/src/docgen/tests/graphs.d trunk/src/docgen/tests/listing.d
diffstat 26 files changed, 757 insertions(+), 145 deletions(-) [+]
line wrap: on
line diff
--- a/trunk/src/docgen/config/configurator.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/config/configurator.d	Tue Oct 30 15:41:30 2007 +0200
@@ -1,3 +1,7 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
 module docgen.config.configurator;
 
 import docgen.config.reader;
@@ -5,10 +9,20 @@
 
 import Integer = tango.text.convert.Integer;
 import tango.io.stream.FileStream;
+import tango.io.Stdout;
 
+/**
+ * Class for handling and merging doc generator options.
+ */
 interface Configurator {
+  /**
+   * Merges configuration options from the given file.
+   */
   void mergeConfiguration(char[] cfgFile);
   
+  /**
+   * Returns a hierarchical structure of configuration options.
+   */
   DocGeneratorOptions *getConfiguration();
 }
 
@@ -56,19 +70,23 @@
   const char[] _parseEnum = `case "` ~ key ~
     `":` ~ _wrong(key) ~ `switch(val[0]) {` ~
     _parseEnum_!(false, key, V) ~
-      `default: err(); }`;
+      `default: err(); } continue;`;
 }
 
 template _parseEnumList(char[] key, V...) {
   const char[] _parseEnumList = `case "` ~ key ~
-    `":` ~ _wrong(key) ~ `switch(val[0]) {` ~
+    `":` ~ `foreach (item; val) switch(item) {` ~
     _parseEnum_!(true, key, V) ~
-      `default: err(); }`;
+      `default: err(); } continue;`;
 }
 
 class DefaultConfigurator : Configurator {
+  private:
+    
   DocGeneratorOptions options;
 
+  public:
+
   const defaultProfileLocation = "docgen/config/default.cfg";
 
   this() {
@@ -129,7 +147,7 @@
         _parseEnum!("options.parser.commentFormat",
             "Doxygen", "CommentFormat.Doxygen",
             "Ddoc", "CommentFormat.Ddoc"
-        ) ~ 
+        ) ~
         _parseEnumList!("options.outputFormats",
             "LaTeX", "DocFormat.LaTeX",
             "HTML", "DocFormat.HTML",
--- a/trunk/src/docgen/config/default.cfg	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/config/default.cfg	Tue Oct 30 15:41:30 2007 +0200
@@ -30,6 +30,6 @@
     (strRegexps)
     (commentFormat Doxygen)
   )
-  (outputFormats LaTeX)
+  (outputFormats LaTeX LaTeX)
   (outputDir tmp/)
 )
--- a/trunk/src/docgen/config/reader.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/config/reader.d	Tue Oct 30 15:41:30 2007 +0200
@@ -1,7 +1,14 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
 module docgen.config.reader;
 
 debug import tango.io.Stdout;
 
+/**
+ * Lexes a s-exp like input
+ */
 char[][] lex(char[] input) {
   char[][] tokens;
 
@@ -91,6 +98,9 @@
   return tokens;
 }
 
+/**
+ * Parser a s-exp like input used by the config files.
+ */
 char[][][char[]] parse(char[][] tokens) {
   char[][][char[]] values;
   size_t i = 1;
--- a/trunk/src/docgen/docgen.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/docgen.d	Tue Oct 30 15:41:30 2007 +0200
@@ -4,8 +4,10 @@
  */
 module docgen.docgen;
 
+import docgen.document.generator;
+
 import docgen.sourcelisting.writers;
-import docgen.document.writers;
+import docgen.page.writers;
 import docgen.graphutils.writers;
 import docgen.misc.misc;
 import docgen.misc.parser;
@@ -18,79 +20,6 @@
 
 import tango.io.Stdout;
 
-template DefaultDocGenerator(char[] genDir) {
-  abstract class DefaultDocGenerator : DocGenerator {
-    DocGeneratorOptions m_options;
-    DocumentWriter docWriter;
-    GraphWriterFactory graphFactory;
-    
-    Module[] modules;
-    Edge[] edges;
-    Vertex[char[]] vertices;
-
-    this(DocGeneratorOptions options) {
-      m_options = options;
-      graphFactory = new DefaultGraphWriterFactory(this);
-
-      // create output dir
-      (new FilePath(options.outputDir ~ "/" ~ genDir)).create();
-    }
-
-    // TODO: constructor for situations where parsing has happened elsewhere
-
-    protected char[] outPath(char[] file) {
-      return options.outputDir ~ "/" ~ genDir ~ "/" ~ file;
-    }
-
-    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
-      );
-    }
-
-    void createDepGraph(char[] depGraphFile) {
-      auto imgFile = new FileOutput(outPath(depGraphFile));
-
-      auto writer = graphFactory.createGraphWriter( docWriter, GraphFormat.Dot );
-
-      writer.generateDepGraph(vertices.values, edges, imgFile);
-
-      imgFile.close();
-    }
-
-    public DocGeneratorOptions *options() {
-      return &m_options;
-    }
-}
-}
 
 class HTMLDocGenerator : DefaultDocGenerator!("html") {
   this(DocGeneratorOptions options) {
@@ -114,7 +43,7 @@
 /**
  * Main routine for LaTeX doc generation.
  */
-class LaTeXDocGenerator : DefaultDocGenerator!("latex") {
+class LaTeXDocGenerator : DefaultCachingDocGenerator!("latex") {
   this(DocGeneratorOptions options) {
     super(options);
   }
@@ -123,10 +52,8 @@
    * Generates document skeleton.
    */
   void generateDoc(char[] docFileName) {
-    auto ddf = new DefaultDocumentWriterFactory(this);
-
     auto docFile = new FileOutput(outPath(docFileName));
-    docWriter = ddf.createDocumentWriter( [ docFile ], DocFormat.LaTeX );
+    docWriter = pageFactory.createPageWriter( [ docFile ], DocFormat.LaTeX );
 
     docWriter.generateFirstPage();
     docWriter.generateTOC(modules);
@@ -180,9 +107,6 @@
     docWriter.setOutput([docFile]);
     auto writer = dlwf.createListingWriter(docWriter, DocFormat.LaTeX);
 
-    /*modules.sort(
-      (Module a, Module b){ return icompare(a.moduleFQN, b.moduleFQN); }
-    );*/
 
     foreach(mod; modules) {
       auto dstFname = replace(mod.moduleFQN.dup, '.', '_') ~ ".d";
@@ -256,31 +180,29 @@
   options.outputDir = args[$-1];
 
   foreach(format; options.outputFormats) {
+    DocGenerator generator;
+
     switch(format) {
       case DocFormat.LaTeX:
-        auto generator = new LaTeXDocGenerator(*options);
+        generator = new LaTeXDocGenerator(*options);
         Stdout("Generating LaTeX docs..");
-        generator.generate();
-        Stdout("done.").newline;
         break;
       case DocFormat.HTML:
-        auto generator = new HTMLDocGenerator(*options);
+        generator = new HTMLDocGenerator(*options);
         Stdout("Generating HTML docs..");
-        generator.generate();
-        Stdout("done.").newline;
         break;
       case DocFormat.XML:
-        auto generator = new XMLDocGenerator(*options);
+        generator = new XMLDocGenerator(*options);
         Stdout("Generating XML docs..");
-        generator.generate();
-        Stdout("done.").newline;
         break;
       case DocFormat.PlainText:
-        auto generator = new PlainTextDocGenerator(*options);
+        generator = new PlainTextDocGenerator(*options);
         Stdout("Generating plain text docs..");
-        generator.generate();
-        Stdout("done.").newline;
         break;
+      default: throw new Exception("Format not supported");
     }
+
+    generator.generate();
+    Stdout("done.").newline;
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/document/generator.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,131 @@
+module docgen.document.generator;
+
+import docgen.sourcelisting.writers;
+import docgen.page.writers;
+import docgen.graphutils.writers;
+import docgen.misc.misc;
+import docgen.misc.parser;
+import docgen.config.configurator;
+import tango.core.Array;
+import tango.io.stream.FileStream;
+import tango.text.Ascii;
+import tango.text.Util : replace;
+import tango.io.FilePath;
+debug import tango.io.Stdout;
+
+template DefaultDocGenerator(char[] genDir) {
+  abstract class DefaultDocGenerator : DocGenerator {
+    DocGeneratorOptions m_options;
+    PageWriter docWriter;
+
+    GraphWriterFactory graphFactory;
+    PageWriterFactory pageFactory;
+    
+    Module[] modules;
+    Edge[] edges;
+    Vertex[char[]] vertices;
+
+    this(DocGeneratorOptions options) {
+      m_options = options;
+
+      createGraphWriterFactory();
+      createPageWriterFactory();
+
+      // create output dir
+      (new FilePath(options.outputDir ~ "/" ~ genDir)).create();
+    }
+
+    // TODO: constructor for situations where parsing has happened elsewhere
+
+    protected void createGraphWriterFactory() {
+      graphFactory = new DefaultGraphWriterFactory(this);
+    }
+
+    protected void createPageWriterFactory() {
+      pageFactory = new DefaultPageWriterFactory(this);
+    }
+
+    protected char[] outPath(char[] file) {
+      return options.outputDir ~ "/" ~ genDir ~ "/" ~ file;
+    }
+
+    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); }
+      );
+    }
+
+    void createDepGraph(char[] depGraphFile) {
+      auto imgFile = new FileOutput(outPath(depGraphFile));
+
+      auto writer = graphFactory.createGraphWriter( docWriter, GraphFormat.Dot );
+
+      writer.generateDepGraph(vertices.values, edges, imgFile);
+
+      imgFile.close();
+    }
+
+    public DocGeneratorOptions *options() {
+      return &m_options;
+    }
+  }
+}
+
+template DefaultCachingDocGenerator(char[] genDir) {
+  abstract class DefaultCachingDocGenerator : DefaultDocGenerator!(genDir), CachingDocGenerator {
+    this(DocGeneratorOptions options) {
+      super(options);
+    }
+    
+    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;
+    }
+
+    protected void createGraphWriterFactory() {
+      graphFactory = new DefaultCachingGraphWriterFactory(this);
+    }
+  }
+}
--- a/trunk/src/docgen/graphutils/dotwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/graphutils/dotwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -9,16 +9,17 @@
 import tango.text.convert.Layout : Layout;
 import tango.io.FilePath;
 import tango.text.Util;
+debug import tango.io.Stdout;
 
 /**
  * Creates a graph rule file for the dot utility.
  */
 class DotWriter : AbstractGraphWriter {
-  this(GraphWriterFactory factory, DocumentWriter writer) {
+  this(GraphWriterFactory factory, PageWriter writer) {
     super(factory, writer);
   }
 
-  void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
+  void generateDepImageFile(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
     auto image = new Print!(char)(new Layout!(char), imageFile);
 
     Vertex[][char[]] verticesByPckgName;
@@ -38,14 +39,6 @@
         factory.options.graph.highlightCyclicEdges)
       findCycles(vertices, edges);
 
-    // name of the .dot file
-    char[] fn = (cast(Object)imageFile.conduit).toUtf8();
-    fn = FilePath(fn).file;
-
-    fn = fn[0..$-3] ~ imageFormatExts[factory.options.graph.imageFormat];
-    
-    writer.addGraphics(fn);
-    
     image("Digraph ModuleDependencies {\n");
 
     foreach (module_; vertices) {
@@ -113,4 +106,44 @@
 
     image("}");
   }
+        
+  void generateImageTag(OutputStream imageFile) {
+    // name of the .dot file
+    char[] fn = (cast(Object)imageFile.conduit).toUtf8();
+    fn = FilePath(fn).file;
+
+    fn = fn[0..$-3] ~ imageFormatExts[factory.options.graph.imageFormat];
+    
+    writer.addGraphics(fn);
+  }
+
+  protected void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
+    generateImageTag(imageFile);
+    generateDepImageFile(vertices, edges, imageFile);
+  }
 }
+
+class CachingDotWriter : DotWriter {
+  CachingGraphWriterFactory factory;
+
+  this(CachingGraphWriterFactory factory, PageWriter writer) {
+    super(factory, writer);
+  }
+
+  protected void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile) {
+    generateImageTag(imageFile);
+
+    auto cached = factory.getCachedGraph(vertices, edges, GraphFormat.Dot);
+
+    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);
+    }
+  }
+}
+
--- a/trunk/src/docgen/graphutils/modulenamewriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/graphutils/modulenamewriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -9,7 +9,7 @@
 import tango.text.convert.Layout : Layout;
 
 class ModuleNameWriter : AbstractGraphWriter {
-  this(GraphWriterFactory factory, DocumentWriter writer) {
+  this(GraphWriterFactory factory, PageWriter writer) {
     super(factory, writer);
   }
 
--- a/trunk/src/docgen/graphutils/modulepathwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/graphutils/modulepathwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -9,7 +9,7 @@
 import tango.text.convert.Layout : Layout;
 
 class ModulePathWriter : AbstractGraphWriter {
-  this(GraphWriterFactory factory, DocumentWriter writer) {
+  this(GraphWriterFactory factory, PageWriter writer) {
     super(factory, writer);
   }
 
--- a/trunk/src/docgen/graphutils/writer.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/graphutils/writer.d	Tue Oct 30 15:41:30 2007 +0200
@@ -6,20 +6,28 @@
 
 public import docgen.misc.misc;
 public import docgen.graphutils.primitives;
-public import docgen.document.writer;
+public import docgen.page.writer;
 debug import tango.io.Stdout;
 
 interface GraphWriter {
   void generateDepGraph(Vertex[] vertices, Edge[] edges, OutputStream imageFile);
 }
 
+interface GraphWriterFactory : WriterFactory {
+  GraphWriter createGraphWriter(PageWriter writer, GraphFormat outputFormat);
+}
+
+interface CachingGraphWriterFactory : GraphWriterFactory {
+  char[] getCachedGraph(Vertex[] vertices, Edge[] edges, GraphFormat format);
+}
+
 /**
  * Marks all cycles in the graph.
  *
  * May have bugs, but is a bit simpler than the previous version.
  */
 void findCycles(Vertex[] vertices, Edge[] edges) {
-  debug void p() {
+  void p() {
     foreach(e; edges) Stderr(e.type)(" "c);
     Stderr.newline;
   }
@@ -27,20 +35,20 @@
   bool visit(Edge edge) {
     if (edge.type == EdgeType.Reserved) {
       edge.type = EdgeType.CyclicDependency;
-      debug p();
+      version(VerboseDebug) p();
       return true;
     }
 
     bool wasCyclic = edge.isCyclic();
     edge.type = EdgeType.Reserved;
-    debug p();
+    version(VerboseDebug) p();
 
     foreach(edge2; edge.incoming.outgoingEdges)
       if (visit(edge2)) {
         if (edge.isCyclic()) {
           edge.type = EdgeType.Reserved;
           wasCyclic = true;
-          debug p();
+          version(VerboseDebug) p();
           continue;
         }
         edge.type = EdgeType.CyclicDependency;
@@ -48,7 +56,7 @@
       }
 
     edge.type = wasCyclic ? EdgeType.CyclicDependency : EdgeType.Dependency;
-    debug p();
+    version(VerboseDebug) p();
     return false;
   }
 
@@ -60,14 +68,10 @@
       }
 }
 
-interface GraphWriterFactory : WriterFactory {
-  GraphWriter createGraphWriter(DocumentWriter writer, GraphFormat outputFormat);
-}
-
 abstract class AbstractGraphWriter : AbstractWriter!(GraphWriterFactory), GraphWriter {
-  DocumentWriter writer;
+  PageWriter writer;
   
-  this(GraphWriterFactory factory, DocumentWriter writer) {
+  this(GraphWriterFactory factory, PageWriter writer) {
     super(factory);
     this.writer = writer;
   }
--- a/trunk/src/docgen/graphutils/writers.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/graphutils/writers.d	Tue Oct 30 15:41:30 2007 +0200
@@ -14,7 +14,7 @@
     super(generator);
   }
 
-  GraphWriter createGraphWriter(DocumentWriter writer, GraphFormat outputFormat) {
+  GraphWriter createGraphWriter(PageWriter writer, GraphFormat outputFormat) {
     switch (outputFormat) {
       case GraphFormat.Dot:
         return new DotWriter(this, writer);
@@ -27,3 +27,28 @@
     }
   }
 }
+
+class DefaultCachingGraphWriterFactory : AbstractWriterFactory, GraphWriterFactory {
+  CachingDocGenerator generator;
+
+  this(CachingDocGenerator generator) {
+    super(generator);
+  }
+
+  char[] getCachedGraph(Vertex[] vertices, Edge[] edges, GraphFormat format) {
+    return generator.getCachedGraph(vertices, edges, format);
+  }
+
+  GraphWriter createGraphWriter(PageWriter writer, GraphFormat outputFormat) {
+    switch (outputFormat) {
+      case GraphFormat.Dot:
+        return new DotWriter(this, writer);
+      case GraphFormat.ModuleNames:
+        return new ModuleNameWriter(this, writer);
+      case GraphFormat.ModulePaths:
+        return new ModulePathWriter(this, writer);
+      default:
+        throw new Exception("Graph writer type does not exist!");
+    }
+  }
+}
--- a/trunk/src/docgen/misc/misc.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/misc/misc.d	Tue Oct 30 15:41:30 2007 +0200
@@ -122,6 +122,10 @@
   void generate();
 }
 
+interface CachingDocGenerator : DocGenerator {
+  char[] getCachedGraph(Object[] vertices, Object[] edges, GraphFormat format);
+}
+
 interface WriterFactory {
   DocGeneratorOptions *options();
 }
@@ -138,6 +142,7 @@
   }
 }
 
+
 template AbstractWriter(T, int n = 0) {
   abstract class AbstractWriter {
     protected T factory;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/htmlwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,64 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.htmlwriter;
+
+import docgen.page.writer;
+import docgen.misc.textutils;
+import tango.io.FileConduit : FileConduit;
+
+// TODO: this is mostly broken now
+
+/**
+ * Writes a HTML document skeleton.
+ */
+class HTMLWriter : AbstractPageWriter!(2, "html") {
+  this(PageWriterFactory 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>");
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/latexwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,39 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.latexwriter;
+
+import docgen.page.writer;
+import tango.io.FileConduit : FileConduit;
+
+/**
+ * Writes a LaTeX document skeleton.
+ */
+class LaTeXWriter : AbstractPageWriter!(1, "latex") {
+  this(PageWriterFactory 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);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/plaintextwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,76 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.plaintextwriter;
+
+import docgen.page.writer;
+import docgen.misc.textutils;
+import tango.io.FileConduit : FileConduit;
+
+//TODO: this is mostly broken now
+
+/**
+ * Writes a plain text document skeleton.
+ */
+class PlainTextWriter : AbstractPageWriter!(1, "plaintext") {
+  this(PageWriterFactory 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);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/writer.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,197 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.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 PageWriter {
+  /**
+   * 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 PageWriterFactory : WriterFactory {
+  PageWriter createPageWriter(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 AbstractPageWriter(int n, char[] format) {
+  abstract class AbstractPageWriter : AbstractWriter!(PageWriterFactory, n), PageWriter {
+    protected char[][char[]] templates;
+    protected Print!(char) print;
+         
+    this(PageWriterFactory 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);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/writers.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,32 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.writers;
+
+public import docgen.page.writer;
+import docgen.page.htmlwriter;
+import docgen.page.xmlwriter;
+import docgen.page.plaintextwriter;
+import docgen.page.latexwriter;
+
+class DefaultPageWriterFactory : AbstractWriterFactory, PageWriterFactory {
+  this(DocGenerator generator) {
+    super(generator);
+  }
+
+  PageWriter createPageWriter(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!");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/trunk/src/docgen/page/xmlwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -0,0 +1,56 @@
+/**
+ * Author: Jari-Matti Mäkelä
+ * License: GPL3
+ */
+module docgen.page.xmlwriter;
+
+import docgen.page.writer;
+import docgen.misc.textutils;
+import tango.io.FileConduit : FileConduit;
+
+//TODO: this is mostly broken now
+
+/**
+ * TODO
+ */
+class XMLWriter : AbstractPageWriter!(2, "xml") {
+  this(PageWriterFactory 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/sourcelisting/htmlwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/htmlwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -14,9 +14,9 @@
  * TODO
  */
 class HTMLWriter : AbstractWriter!(ListingWriterFactory), ListingWriter {
-  DocumentWriter writer;
+  PageWriter writer;
   
-  this(ListingWriterFactory factory, DocumentWriter writer) {
+  this(ListingWriterFactory factory, PageWriter writer) {
     super(factory);
     this.writer = writer;
   }
--- a/trunk/src/docgen/sourcelisting/latexwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/latexwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -12,9 +12,9 @@
  * Adds a code listing section for the given file. 
  */
 class LaTeXWriter : AbstractWriter!(ListingWriterFactory), ListingWriter {
-  DocumentWriter writer;
+  PageWriter writer;
   
-  this(ListingWriterFactory factory, DocumentWriter writer) {
+  this(ListingWriterFactory factory, PageWriter writer) {
     super(factory);
     this.writer = writer;
   }
--- a/trunk/src/docgen/sourcelisting/plaintextwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/plaintextwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -13,9 +13,9 @@
  * TODO
  */
 class PlainTextWriter : AbstractWriter!(ListingWriterFactory), ListingWriter {
-  DocumentWriter writer;
+  PageWriter writer;
   
-  this(ListingWriterFactory factory, DocumentWriter writer) {
+  this(ListingWriterFactory factory, PageWriter writer) {
     super(factory);
     this.writer = writer;
   }
--- a/trunk/src/docgen/sourcelisting/writer.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/writer.d	Tue Oct 30 15:41:30 2007 +0200
@@ -5,7 +5,7 @@
 module docgen.sourcelisting.writer;
 
 public import docgen.misc.misc;
-public import docgen.document.writer;
+public import docgen.page.writer;
 //import dil.Parser;
 import tango.io.model.IConduit : OutputStream, InputStream;
 
@@ -15,5 +15,5 @@
 }
 
 interface ListingWriterFactory : WriterFactory {
-  ListingWriter createListingWriter(DocumentWriter writer, DocFormat outputFormat);
+  ListingWriter createListingWriter(PageWriter writer, DocFormat outputFormat);
 }
--- a/trunk/src/docgen/sourcelisting/writers.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/writers.d	Tue Oct 30 15:41:30 2007 +0200
@@ -15,7 +15,7 @@
     super(generator);
   }
 
-  ListingWriter createListingWriter(DocumentWriter writer, DocFormat outputFormat) {
+  ListingWriter createListingWriter(PageWriter writer, DocFormat outputFormat) {
     switch (outputFormat) {
       case DocFormat.LaTeX:
         return new LaTeXWriter(this, writer);
--- a/trunk/src/docgen/sourcelisting/xmlwriter.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/sourcelisting/xmlwriter.d	Tue Oct 30 15:41:30 2007 +0200
@@ -11,9 +11,9 @@
  * TODO
  */
 class XMLWriter : AbstractWriter!(ListingWriterFactory), ListingWriter {
-  DocumentWriter writer;
+  PageWriter writer;
   
-  this(ListingWriterFactory factory, DocumentWriter writer) {
+  this(ListingWriterFactory factory, PageWriter writer) {
     super(factory);
     this.writer = writer;
   }
--- a/trunk/src/docgen/tests/doctemplate.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/tests/doctemplate.d	Tue Oct 30 15:41:30 2007 +0200
@@ -5,7 +5,7 @@
 module docgen.tests.doctemplate;
 
 import docgen.tests.common;
-import docgen.document.writers;
+import docgen.page.writers;
 import tango.io.FileConduit;
 
 // doc template
@@ -14,9 +14,9 @@
   auto gen = new TestDocGenerator;
   auto fname = "doctemplate.tex";
   
-  auto gwf = new DefaultDocumentWriterFactory(gen);
+  auto gwf = new DefaultPageWriterFactory(gen);
   auto file = new FileConduit("docgen/teststuff/" ~ fname, FileConduit.WriteCreate);
-  auto writer = gwf.createDocumentWriter( [ file ], DocFormat.LaTeX );
+  auto writer = gwf.createPageWriter( [ file ], DocFormat.LaTeX );
   
   writer.generateFirstPage();
   writer.generateTOC(null);
--- a/trunk/src/docgen/tests/graphs.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/tests/graphs.d	Tue Oct 30 15:41:30 2007 +0200
@@ -7,7 +7,7 @@
 import docgen.tests.common;
 import docgen.misc.parser;
 import docgen.graphutils.writers;
-import docgen.document.writers;
+import docgen.page.writers;
 import tango.io.FileConduit;
 import dil.Module;
 
@@ -18,12 +18,12 @@
   //gen.options.graph.graphFormat = GraphFormat.ModuleNames;
   //gen.options.graph.graphFormat = GraphFormat.ModulePaths;
   gen.options.graph.depth = 5;
-  auto ddf = new DefaultDocumentWriterFactory(gen);
+  auto ddf = new DefaultPageWriterFactory(gen);
   auto gwf = new DefaultGraphWriterFactory(gen);
   auto file = new FileConduit("docgen/teststuff/" ~ fname, FileConduit.WriteCreate);
   auto file2 = new FileConduit("docgen/teststuff/" ~ fname ~ "-2", FileConduit.WriteCreate);
   auto writer = gwf.createGraphWriter(
-    ddf.createDocumentWriter( [ file2 ], DocFormat.LaTeX),
+    ddf.createPageWriter( [ file2 ], DocFormat.LaTeX),
     GraphFormat.Dot
   );
   
@@ -114,7 +114,7 @@
   auto fname = "dependencies.tex";
   auto imgFname = "depgraph.dot";
   
-  auto ddf = new DefaultDocumentWriterFactory(gen);
+  auto ddf = new DefaultPageWriterFactory(gen);
   auto gwf = new DefaultGraphWriterFactory(gen);
   auto file = new FileConduit("docgen/teststuff/" ~ fname, FileConduit.WriteCreate);
   auto imgFile = new FileConduit("docgen/teststuff/" ~ imgFname, FileConduit.WriteCreate);
@@ -137,7 +137,7 @@
   );
 
   auto writer = gwf.createGraphWriter(
-    ddf.createDocumentWriter( [ file ], DocFormat.LaTeX ),
+    ddf.createPageWriter( [ file ], DocFormat.LaTeX ),
     GraphFormat.Dot
   );
   
--- a/trunk/src/docgen/tests/listing.d	Tue Oct 30 02:35:56 2007 +0200
+++ b/trunk/src/docgen/tests/listing.d	Tue Oct 30 15:41:30 2007 +0200
@@ -7,7 +7,7 @@
 import docgen.misc.parser;
 import docgen.tests.common;
 import docgen.sourcelisting.writers;
-import docgen.document.writers;
+import docgen.page.writers;
 import tango.io.FileConduit;
 import tango.text.Util;
 
@@ -18,7 +18,7 @@
   gen.options.outputFormats = [ DocFormat.LaTeX ];
   auto fname = "files.tex";
   
-  auto ddf = new DefaultDocumentWriterFactory(gen);
+  auto ddf = new DefaultPageWriterFactory(gen);
   auto dlwf = new DefaultListingWriterFactory(gen);
   auto file = new FileConduit("docgen/teststuff/" ~ fname, FileConduit.WriteCreate);
   
@@ -38,7 +38,7 @@
     
     auto srcFile = new FileConduit(mod.filePath);
     auto dstFile = new FileConduit("docgen/teststuff/_" ~ dstFname ~ ".d", FileConduit.WriteCreate);
-    auto writer = dlwf.createListingWriter( ddf.createDocumentWriter( [ file ],
+    auto writer = dlwf.createListingWriter( ddf.createPageWriter( [ file ],
           DocFormat.LaTeX ), DocFormat.LaTeX );
     
     writer.generateListing(srcFile, dstFile, mod.moduleFQN);