diff options
author | Ondřej Surý <ondrej@sury.org> | 2012-03-26 16:50:58 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2012-03-26 16:50:58 +0200 |
commit | 519725bb3c075ee2462c929f5997cb068e18466a (patch) | |
tree | 5b162e8488ad147a645048c073577821b4a2bee9 /src/cmd/godoc/template.go | |
parent | 842623c5dd2819d980ca9c58048d6bc6ed82475f (diff) | |
download | golang-upstream-weekly/2012.03.22.tar.gz |
Imported Upstream version 2012.03.22upstream-weekly/2012.03.22
Diffstat (limited to 'src/cmd/godoc/template.go')
-rw-r--r-- | src/cmd/godoc/template.go | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/cmd/godoc/template.go b/src/cmd/godoc/template.go new file mode 100644 index 000000000..d709baef4 --- /dev/null +++ b/src/cmd/godoc/template.go @@ -0,0 +1,182 @@ +// 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. + +// Template support for writing HTML documents. +// Documents that include Template: true in their +// metadata are executed as input to text/template. +// +// This file defines functions for those templates to invoke. + +// 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. In multi-line matches, +// lines that end with the four characters +// OMIT +// are omitted from the output, making it easy to provide marker +// lines in the input that will not appear in the output but are easy +// to identify by pattern. + +package main + +import ( + "bytes" + "fmt" + "log" + "regexp" + "strings" + "text/template" +) + +// Functions in this file panic on error, but the panic is recovered +// to an error by 'code'. + +var templateFuncs = template.FuncMap{ + "code": code, +} + +// contents reads and returns the content of the named file +// (from the virtual file system, so for example /doc refers to $GOROOT/doc). +func contents(name string) string { + file, err := ReadFile(fs, name) + if err != nil { + log.Panic(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.Panicf("unrecognized argument: %v type %T", arg, arg) + } + return "" +} + +func code(file string, arg ...interface{}) (s string, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%v", r) + } + }() + + 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) + } + // Trim spaces from output. + text = strings.Trim(text, "\n") + // Replace tabs by spaces, which work better in HTML. + text = strings.Replace(text, "\t", " ", -1) + var buf bytes.Buffer + // HTML-escape text and syntax-color comments like elsewhere. + FormatText(&buf, []byte(text), -1, true, "", nil) + // Include the command as a comment. + text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes()) + 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.Panicf("%q:%d is out of range", file, n) + } + return n, "", true + case string: + return 0, n, false + } + log.Panicf("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.Panicf("lines out of order for %q: %d %d", text, line1, line2) + } + for k := line1 - 1; k < line2; k++ { + if strings.HasSuffix(lines[k], "OMIT\n") { + lines[k] = "" + } + } + 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.Panicf("%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.Panic(err) + } + for i := start; i < len(lines); i++ { + if re.MatchString(lines[i]) { + return i + 1 + } + } + log.Panicf("%s: no match for %#q", file, pattern) + } + log.Panicf("unrecognized pattern: %q", pattern) + return 0 +} |