go/doc/tmpltohtml.go
Rob Pike c1c8d46d20 go_tutorial: change the way it's generated.
No longer do we generate HTML from it; instead the input
file is already in HTML but has template invocations to
extract programs from other files.
Delete htmlgen, which is no longer needed.
Add tmpltohtml, which runs the templating code.

R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/4699041
2011-07-13 13:20:05 +10:00

177 lines
4.9 KiB
Go

// 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
}