summaryrefslogtreecommitdiff
path: root/doc/tmpltohtml.go
diff options
context:
space:
mode:
Diffstat (limited to 'doc/tmpltohtml.go')
-rw-r--r--doc/tmpltohtml.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/doc/tmpltohtml.go b/doc/tmpltohtml.go
new file mode 100644
index 000000000..f4d2e2c2c
--- /dev/null
+++ b/doc/tmpltohtml.go
@@ -0,0 +1,176 @@
+// Copyright 2011 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.
+
+
+// The template uses the function "code" to inject program
+// source into the output by extracting code from files and
+// injecting them as HTML-escaped <pre> blocks.
+//
+// The syntax is simple: 1, 2, or 3 space-separated arguments:
+//
+// Whole file:
+// {{code "foo.go"}}
+// One line (here the signature of main):
+// {{code "foo.go" `/^func.main/`}}
+// Block of text, determined by start and end (here the body of main):
+// {{code "foo.go" `/^func.main/` `/^}/`
+//
+// Patterns can be `/regular expression/`, a decimal number, or "$"
+// to signify the end of the file.
+package main
+
+import (
+ "exp/template"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "regexp"
+ "strings"
+)
+
+func Usage() {
+ fmt.Fprintf(os.Stderr, "usage: tmpltohtml file\n")
+ os.Exit(2)
+}
+
+func main() {
+ flag.Usage = Usage
+ flag.Parse()
+ if len(flag.Args()) != 1 {
+ Usage()
+ }
+
+ // Read and parse the input.
+ name := flag.Args()[0]
+ tmpl := template.New(name).Funcs(template.FuncMap{"code": code})
+ if _, err := tmpl.ParseFile(name); err != nil {
+ log.Fatal(err)
+ }
+
+ // Execute the template.
+ if err := tmpl.Execute(os.Stdout, 0); err != nil {
+ log.Fatal(err)
+ }
+}
+
+// contents reads a file by name and returns its contents as a string.
+func contents(name string) string {
+ file, err := ioutil.ReadFile(name)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return string(file)
+}
+
+// format returns a textual representation of the arg, formatted according to its nature.
+func format(arg interface{}) string {
+ switch arg := arg.(type) {
+ case int:
+ return fmt.Sprintf("%d", arg)
+ case string:
+ if len(arg) > 2 && arg[0] == '/' && arg[len(arg)-1] == '/' {
+ return fmt.Sprintf("%#q", arg)
+ }
+ return fmt.Sprintf("%q", arg)
+ default:
+ log.Fatalf("unrecognized argument: %v type %T", arg, arg)
+ }
+ return ""
+}
+
+func code(file string, arg ...interface{}) (string, os.Error) {
+ text := contents(file)
+ var command string
+ switch len(arg) {
+ case 0:
+ // text is already whole file.
+ command = fmt.Sprintf("code %q", file)
+ case 1:
+ command = fmt.Sprintf("code %q %s", file, format(arg[0]))
+ text = oneLine(file, text, arg[0])
+ case 2:
+ command = fmt.Sprintf("code %q %s %s", file, format(arg[0]), format(arg[1]))
+ text = multipleLines(file, text, arg[0], arg[1])
+ default:
+ return "", fmt.Errorf("incorrect code invocation: code %q %q", file, arg)
+ }
+ // Replace tabs by spaces, which work better in HTML.
+ text = strings.Replace(text, "\t", " ", -1)
+ // Escape the program text for HTML.
+ text = template.HTMLEscapeString(text)
+ // Include the command as a comment.
+ text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, text)
+ return text, nil
+}
+
+// parseArg returns the integer or string value of the argument and tells which it is.
+func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
+ switch n := arg.(type) {
+ case int:
+ if n <= 0 || n > max {
+ log.Fatalf("%q:%d is out of range", file, n)
+ }
+ return n, "", true
+ case string:
+ return 0, n, false
+ }
+ log.Fatalf("unrecognized argument %v type %T", arg, arg)
+ return
+}
+
+// oneLine returns the single line generated by a two-argument code invocation.
+func oneLine(file, text string, arg interface{}) string {
+ lines := strings.SplitAfter(contents(file), "\n")
+ line, pattern, isInt := parseArg(arg, file, len(lines))
+ if isInt {
+ return lines[line-1]
+ }
+ return lines[match(file, 0, lines, pattern)-1]
+}
+
+// multipleLines returns the text generated by a three-argument code invocation.
+func multipleLines(file, text string, arg1, arg2 interface{}) string {
+ lines := strings.SplitAfter(contents(file), "\n")
+ line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
+ line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
+ if !isInt1 {
+ line1 = match(file, 0, lines, pattern1)
+ }
+ if !isInt2 {
+ line2 = match(file, line1, lines, pattern2)
+ } else if line2 < line1 {
+ log.Fatal("lines out of order for %q: %d %d", line1, line2)
+ }
+ return strings.Join(lines[line1-1:line2], "")
+}
+
+// match identifies the input line that matches the pattern in a code invocation.
+// If start>0, match lines starting there rather than at the beginning.
+// The return value is 1-indexed.
+func match(file string, start int, lines []string, pattern string) int {
+ // $ matches the end of the file.
+ if pattern == "$" {
+ if len(lines) == 0 {
+ log.Fatal("%q: empty file", file)
+ }
+ return len(lines)
+ }
+ // /regexp/ matches the line that matches the regexp.
+ if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
+ re, err := regexp.Compile(pattern[1 : len(pattern)-1])
+ if err != nil {
+ log.Fatal(err)
+ }
+ for i := start; i < len(lines); i++ {
+ if re.MatchString(lines[i]) {
+ return i + 1
+ }
+ }
+ log.Fatalf("%s: no match for %#q", file, pattern)
+ }
+ log.Fatalf("unrecognized pattern: %q", pattern)
+ return 0
+}