From 3d0e4741eb5d1c0ac219eeaea85397e6f8ee1f24 Mon Sep 17 00:00:00 2001
From: Russ Cox <rsc@golang.org>
Date: Mon, 13 Apr 2009 15:25:50 -0700
Subject: [PATCH] godoc: switch to go library template system

R=gri
DELTA=272  (38 added, 139 deleted, 95 changed)
OCL=27372
CL=27390
---
 usr/gri/pretty/Makefile    |   4 +-
 usr/gri/pretty/godoc.go    | 211 +++++++++++++++++++++----------------
 usr/gri/pretty/template.go | 123 ---------------------
 3 files changed, 120 insertions(+), 218 deletions(-)
 delete mode 100644 usr/gri/pretty/template.go

diff --git a/usr/gri/pretty/Makefile b/usr/gri/pretty/Makefile
index 0636289efd..836f0890ff 100644
--- a/usr/gri/pretty/Makefile
+++ b/usr/gri/pretty/Makefile
@@ -36,11 +36,11 @@ pretty.6:	 platform.6 astprinter.6 compilation.6
 
 compilation.6:	 platform.6 typechecker.6
 
-symboltable.6:	
+symboltable.6:
 
 platform.6:	 utils.6
 
-astprinter.6:	 utils.6 symboltable.6 template.6
+astprinter.6:	 utils.6 symboltable.6
 
 docprinter.6:	astprinter.6
 
diff --git a/usr/gri/pretty/godoc.go b/usr/gri/pretty/godoc.go
index f3903e8a9b..0a1ebd49b9 100644
--- a/usr/gri/pretty/godoc.go
+++ b/usr/gri/pretty/godoc.go
@@ -35,10 +35,12 @@ import (
 	"io";
 	"log";
 	"net";
+	"once";
 	"os";
 	"parser";
 	pathutil "path";
 	"sort";
+	"strings";
 	"tabwriter";
 	"template";
 	"time";
@@ -94,24 +96,13 @@ func init() {
 // ----------------------------------------------------------------------------
 // Support
 
-func hasPrefix(s, prefix string) bool {
-	return len(prefix) <= len(s) && s[0 : len(prefix)] == prefix;
-}
-
-
-func hasSuffix(s, suffix string) bool {
-	pos := len(s) - len(suffix);
-	return pos >= 0 && s[pos : len(s)] == suffix;
-}
-
-
 func isGoFile(dir *os.Dir) bool {
-	return dir.IsRegular() && hasSuffix(dir.Name, ".go");
+	return dir.IsRegular() && strings.HasSuffix(dir.Name, ".go");
 }
 
 
 func isHTMLFile(dir *os.Dir) bool {
-	return dir.IsRegular() && hasSuffix(dir.Name, ".html");
+	return dir.IsRegular() && strings.HasSuffix(dir.Name, ".html");
 }
 
 
@@ -127,7 +118,7 @@ func isFile(name string) bool {
 }
 
 
-func printLink(c *http.Conn, dir, name string) {
+func printLink(c io.Write, dir, name string) {
 	fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", pathutil.Clean(filePrefix + dir + "/" + name), name);
 }
 
@@ -208,26 +199,55 @@ func parse(path string, mode uint) (*ast.Program, errorList) {
 // Templates
 
 // html template
-// TODO initialize only if needed (i.e. if run as a server)
-var godoc_html = template.NewTemplateOrDie("godoc.html");
+var godoc_html string
+
+func readTemplate() {
+	name := "usr/gri/pretty/godoc.html";
+	f, err := os.Open(name, os.O_RDONLY, 0);
+	if err != nil {
+		log.Exitf("open %s: %v", name, err);
+	}
+	var b io.ByteBuffer;
+	if n, err := io.Copy(f, &b); err != nil {
+		log.Exitf("copy %s: %v", name, err);
+	}
+	f.Close();
+	godoc_html = string(b.Data());
+}
+
+
+func servePage(c *http.Conn, title, content interface{}) {
+	once.Do(readTemplate);
 
-func servePage(c *http.Conn, title string, contents func()) {
 	c.SetHeader("content-type", "text/html; charset=utf-8");
 
-	// TODO handle Apply errors
-	godoc_html.Apply(c, "<!--", template.Substitution {
-		"TITLE-->" : func() { fmt.Fprint(c, title); },
-		"HEADER-->" : func() { fmt.Fprint(c, title); },
-		"TIMESTAMP-->" : func() { fmt.Fprint(c, time.UTC().String()); },
-		"CONTENTS-->" : contents
-	});
+	type Data struct {
+		title string;
+		header string;
+		timestamp string;
+		content string;
+	}
+	
+	// TODO(rsc): Once template system can handle []byte,
+	// remove this conversion.
+	if x, ok := title.([]byte); ok {
+		title = string(x);
+	}
+	if x, ok := content.([]byte); ok {
+		content = string(x);
+	}
+
+	var d Data;
+	d.title = title.(string);
+	d.header = title.(string);
+	d.timestamp = time.UTC().String();
+	d.content = content.(string);
+	template.Execute(godoc_html, &d, nil, c);
 }
 
 
 func serveError(c *http.Conn, err, arg string) {
-	servePage(c, "Error", func () {
-		fmt.Fprintf(c, "%v (%s)\n", err, arg);
-	});
+	servePage(c, "Error", fmt.Sprintf("%v (%s)\n", err, arg));
 }
 
 
@@ -260,28 +280,29 @@ func serveDir(c *http.Conn, dirname string) {
 	path := dirname + "/";
 
 	// Print contents in 3 sections: directories, go files, everything else
-	servePage(c, dirname + " - Contents", func () {
-		fmt.Fprintln(c, "<h2>Directories</h2>");
-		for i, entry := range list {
-			if entry.IsDirectory() {
-				printLink(c, path, entry.Name);
-			}
+	var b io.ByteBuffer;
+	fmt.Fprintln(&b, "<h2>Directories</h2>");
+	for i, entry := range list {
+		if entry.IsDirectory() {
+			printLink(&b, path, entry.Name);
 		}
+	}
 
-		fmt.Fprintln(c, "<h2>Go files</h2>");
-		for i, entry := range list {
-			if isGoFile(&entry) {
-				printLink(c, path, entry.Name);
-			}
+	fmt.Fprintln(&b, "<h2>Go files</h2>");
+	for i, entry := range list {
+		if isGoFile(&entry) {
+			printLink(&b, path, entry.Name);
 		}
+	}
 
-		fmt.Fprintln(c, "<h2>Other files</h2>");
-		for i, entry := range list {
-			if !entry.IsDirectory() && !isGoFile(&entry) {
-				fmt.Fprintf(c, "%s<br />\n", entry.Name);
-			}
+	fmt.Fprintln(&b, "<h2>Other files</h2>");
+	for i, entry := range list {
+		if !entry.IsDirectory() && !isGoFile(&entry) {
+			fmt.Fprintf(&b, "%s<br />\n", entry.Name);
 		}
-	});
+	}
+
+	servePage(c, dirname + " - Contents", b.Data());
 }
 
 
@@ -307,35 +328,36 @@ func serveParseErrors(c *http.Conn, filename string, errors errorList) {
 	}
 	src := buf.Data();
 
-	// TODO handle Apply errors
-	servePage(c, filename, func () {
-		// section title
-		fmt.Fprintf(c, "<h1>Parse errors in %s</h1>\n", filename);
+	// generate body
+	var b io.ByteBuffer;
+	// section title
+	fmt.Fprintf(&b, "<h1>Parse errors in %s</h1>\n", filename);
 
-		// handle read errors
-		if err1 != nil || err2 != nil {
-			fmt.Fprintf(c, "could not read file %s\n", filename);
-			return;
-		}
+	// handle read errors
+	if err1 != nil || err2 != nil {
+		fmt.Fprintf(&b, "could not read file %s\n", filename);
+		return;
+	}
 
-		// write source with error messages interspersed
-		fmt.Fprintln(c, "<pre>");
-		offs := 0;
-		for i, e := range errors {
-			if 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
-				// TODO handle Write errors
-				c.Write(src[offs : e.pos.Offset]);
-				// TODO this should be done using a .css file
-				fmt.Fprintf(c, "<b><font color=red>%s >>></font></b>", e.msg);
-				offs = e.pos.Offset;
-			} else {
-				log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
-			}
+	// write source with error messages interspersed
+	fmt.Fprintln(&b, "<pre>");
+	offs := 0;
+	for i, e := range errors {
+		if 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
+			// TODO handle Write errors
+			b.Write(src[offs : e.pos.Offset]);
+			// TODO this should be done using a .css file
+			fmt.Fprintf(&b, "<b><font color=red>%s >>></font></b>", e.msg);
+			offs = e.pos.Offset;
+		} else {
+			log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
 		}
-		// TODO handle Write errors
-		c.Write(src[offs : len(src)]);
-		fmt.Fprintln(c, "</pre>");
-	});
+	}
+	// TODO handle Write errors
+	b.Write(src[offs : len(src)]);
+	fmt.Fprintln(&b, "</pre>");
+
+	servePage(c, filename, b.Data());
 }
 
 
@@ -347,15 +369,16 @@ func serveGoSource(c *http.Conn, dirname string, filename string) {
 		return;
 	}
 
-	servePage(c, path + " - Go source", func () {
-		fmt.Fprintln(c, "<pre>");
-		var p astPrinter.Printer;
-		writer := makeTabwriter(c);  // for nicely formatted output
-		p.Init(writer, nil, nil, true);
-		p.DoProgram(prog);
-		writer.Flush();  // ignore errors
-		fmt.Fprintln(c, "</pre>");
-	});
+	var b io.ByteBuffer;
+	fmt.Fprintln(&b, "<pre>");
+	var p astPrinter.Printer;
+	writer := makeTabwriter(&b);  // for nicely formatted output
+	p.Init(writer, nil, nil, true);
+	p.DoProgram(prog);
+	writer.Flush();  // ignore errors
+	fmt.Fprintln(&b, "</pre>");
+
+	servePage(c, path + " - Go source", b.Data());
 }
 
 
@@ -410,7 +433,7 @@ func (p pakArray) Swap(i, j int)       { p[i], p[j] = p[j], p[i]; }
 
 
 func addFile(pmap map[string]*pakDesc, dirname string, filename string) {
-	if hasSuffix(filename, "_test.go") {
+	if strings.HasSuffix(filename, "_test.go") {
 		// ignore package tests
 		return;
 	}
@@ -507,23 +530,25 @@ func servePackage(c *http.Conn, p *pakDesc) {
 		doc.AddProgram(prog);
 	}
 
-	servePage(c, doc.PackageName() + " - Go package documentation", func () {
-		writer := makeTabwriter(c);  // for nicely formatted output
-		doc.Print(writer);
-		writer.Flush();  // ignore errors
-	});
+	var b io.ByteBuffer;
+	writer := makeTabwriter(&b);  // for nicely formatted output
+	doc.Print(writer);
+	writer.Flush();	// ignore errors
+
+	servePage(c, doc.PackageName() + " - Go package documentation", b.Data());
 }
 
 
 func servePackageList(c *http.Conn, list pakArray) {
-	servePage(c, "Packages", func () {
-		for i := 0; i < len(list); i++ {
-			p := list[i];
-			link := pathutil.Clean(p.dirname + "/" + p.pakname);
-			fmt.Fprintf(c, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n",
-				p.pakname, p.pakname, link);
-		}
-	});
+	var b io.ByteBuffer;
+	for i := 0; i < len(list); i++ {
+		p := list[i];
+		link := pathutil.Clean(p.dirname + "/" + p.pakname);
+		fmt.Fprintf(&b, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n",
+			p.pakname, p.pakname, link);
+	}
+
+	servePage(c, "Packages", b.Data());
 
 	// TODO: show subdirectories
 }
diff --git a/usr/gri/pretty/template.go b/usr/gri/pretty/template.go
deleted file mode 100644
index 9aa83d4495..0000000000
--- a/usr/gri/pretty/template.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2009 The Go Authors.  All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package template
-
-import (
-	"os";
-	"io";
-)
-
-
-type Template struct {
-	template []byte;
-}
-
-
-func (T *Template) Init(filename string) *os.Error {
-	f, err0 := os.Open(filename, os.O_RDONLY, 0);
-	defer f.Close();
-	if err0 != nil {
-		return err0;
-	}
-
-	var buf io.ByteBuffer;
-	len, err1 := io.Copy(f, &buf);
-	if err1 == io.ErrEOF {
-		err1 = nil;
-	}
-	if err1 != nil {
-		return err1;
-	}
-
-	T.template = buf.Data();
-
-	return nil;
-}
-
-
-// Returns true if buf starts with s, returns false otherwise.
-//
-func match(buf []byte, s string) bool {
-	if len(buf) < len(s) {
-		return false;
-	}
-	for i := 0; i < len(s); i++ {
-		if buf[i] != s[i] {
-			return false;
-		}
-	}
-	return true;
-}
-
-
-// Find the position of string s in buf, starting at i.
-// Returns a value < 0 if not found.
-//
-func find(buf []byte, s string, i int) int {
-    if s == "" {
-        return i;
-    }
-L:	for ; i + len(s) <= len(buf); i++ {
-		for k := 0; k < len(s); k++ {
-			if buf[i+k] != s[k] {
-				continue L;
-			}
-		}
-		return i;
-    }
-    return -1
-}
-
-
-type Substitution map [string] func()
-
-func (T *Template) Apply(w io.Write, prefix string, subs Substitution) *os.Error {
-	i0 := 0;  // position from which to write from the template
-	i1 := 0;  // position from which to look for the next prefix
-
-	for {
-		// look for a prefix
-		i2 := find(T.template, prefix, i1);  // position of prefix, if any
-		if i2 < 0 {
-			// no prefix found, we are done
-			break;
-		}
-
-		// we have a prefix, look for a matching key
-		i1 = i2 + len(prefix);
-		for key, action := range subs {
-			if match(T.template[i1 : len(T.template)], key) {
-				// found a match
-				i1 += len(key);  // next search starting pos
-				len, err := w.Write(T.template[i0 : i2]);  // TODO handle errors
-				i0 = i1;  // skip placeholder
-				action();
-				break;
-			}
-		}
-	}
-
-	// write the rest of the template
-	len, err := w.Write(T.template[i0 : len(T.template)]);  // TODO handle errors
-	return err;
-}
-
-
-func NewTemplate(filename string) *Template {
-	t := new(Template);
-	if t.Init(filename) != nil {
-		return nil;
-	}
-	return t;
-}
-
-
-func NewTemplateOrDie(filename string) *Template {
-	t := NewTemplate(filename);
-	if t == nil {
-		panic("could not read template: " + filename);
-	}
-	return t;
-}
-- 
2.30.9