diff options
| author | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 | 
|---|---|---|
| committer | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 | 
| commit | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (patch) | |
| tree | 32944e18b23f7fe4a0818a694aa2a6dfb1835463 /doc/htmlgen.go | |
| parent | e836bee4716dc0d4d913537ad3ad1925a7ac32d0 (diff) | |
| download | golang-28592ee1ea1f5cdffcf85472f9de0285d928cf12.tar.gz | |
Imported Upstream version 59upstream/59
Diffstat (limited to 'doc/htmlgen.go')
| -rw-r--r-- | doc/htmlgen.go | 167 | 
1 files changed, 151 insertions, 16 deletions
| diff --git a/doc/htmlgen.go b/doc/htmlgen.go index 3a8feb8bc..5318a07dc 100644 --- a/doc/htmlgen.go +++ b/doc/htmlgen.go @@ -2,46 +2,80 @@  // Use of this source code is governed by a BSD-style  // license that can be found in the LICENSE file. -// Process plain text into HTML. +// If --html is set, process plain text into HTML.  //	- h2's are made from lines followed by a line "----\n" -//	- tab-indented blocks become <pre> blocks +//	- tab-indented blocks become <pre> blocks with the first tab deleted  //	- blank lines become <p> marks (except inside <pre> tags)  //	- "quoted strings" become <code>quoted strings</code> +// Lines beginning !src define pieces of program source to be +// extracted from other files and injected as <pre> blocks. +// The syntax is simple: 1, 2, or 3 space-separated arguments: +// +// Whole file: +//	!src foo.go +// One line (here the signature of main): +//	!src foo.go /^func.main/ +// Block of text, determined by start and end (here the body of main): +// !src foo.go /^func.main/ /^}/ +// +// Patterns can be /regular.expression/, a decimal number, or $ +// to signify the end of the file. +// TODO: the regular expression cannot contain spaces; does this matter? +  package main  import (  	"bufio"  	"bytes" +	"flag" +	"fmt" +	"io/ioutil"  	"log"  	"os" +	"regexp" +	"strconv" +	"strings" +	"template"  )  var ( -	lines = make([][]byte, 0, 2000) // probably big enough; grows if not +	html = flag.Bool("html", true, "process text into HTML") +) + +var ( +	// lines holds the input and is reworked in place during processing. +	lines = make([][]byte, 0, 20000)  	empty   = []byte("")  	newline = []byte("\n")  	tab     = []byte("\t")  	quote   = []byte(`"`) -	indent  = []byte{' ', ' ', ' ', ' '} +	indent  = []byte("    ")  	sectionMarker = []byte("----\n")  	preStart      = []byte("<pre>")  	preEnd        = []byte("</pre>\n")  	pp            = []byte("<p>\n") + +	srcPrefix = []byte("!src")  )  func main() { +	flag.Parse()  	read() -	headings() -	coalesce(preStart, foldPre) -	coalesce(tab, foldTabs) -	paragraphs() -	quotes() +	programs() +	if *html { +		headings() +		coalesce(preStart, foldPre) +		coalesce(tab, foldTabs) +		paragraphs() +		quotes() +	}  	write()  } +// read turns standard input into a slice of lines.  func read() {  	b := bufio.NewReader(os.Stdin)  	for { @@ -56,6 +90,7 @@ func read() {  	}  } +// write puts the result on standard output.  func write() {  	b := bufio.NewWriter(os.Stdout)  	for _, line := range lines { @@ -64,8 +99,104 @@ func write() {  	b.Flush()  } -// each time prefix is found on a line, call fold and replace -// line with return value from fold. +// programs injects source code from !src invocations. +func programs() { +	nlines := make([][]byte, 0, len(lines)*3/2) +	for _, line := range lines { +		if bytes.HasPrefix(line, srcPrefix) { +			line = trim(line)[len(srcPrefix):] +			prog := srcCommand(string(line)) +			if *html { +				nlines = append(nlines, []byte(fmt.Sprintf("<pre><!--%s\n-->", line))) +			} +			for _, l := range prog { +				nlines = append(nlines, htmlEscape(l)) +			} +			if *html { +				nlines = append(nlines, preEnd) +			} +		} else { +			nlines = append(nlines, line) +		} +	} +	lines = nlines +} + +// srcCommand processes one !src invocation. +func srcCommand(command string) [][]byte { +	// TODO: quoted args so we can have 'a b'? +	args := strings.Fields(command) +	if len(args) == 0 || len(args) > 3 { +		log.Fatal("bad syntax for src command: %s", command) +	} +	file := args[0] +	lines := bytes.SplitAfter(readFile(file), newline) +	// File plus zero args: whole file: +	//	!src file.go +	if len(args) == 1 { +		return lines +	} +	start := match(file, 0, lines, string(args[1])) +	// File plus one arg: one line: +	//	!src file.go /foo/ +	if len(args) == 2 { +		return [][]byte{lines[start]} +	} +	// File plus two args: range: +	//	!src file.go /foo/ /^}/ +	end := match(file, start, lines, string(args[2])) +	return lines[start : end+1] // +1 to include matched line. +} + +// htmlEscape makes sure input is HTML clean, if necessary. +func htmlEscape(input []byte) []byte { +	if !*html || bytes.IndexAny(input, `&"<>`) < 0 { +		return input +	} +	var b bytes.Buffer +	template.HTMLEscape(&b, input) +	return b.Bytes() +} + +// readFile reads and returns a file as part of !src processing. +func readFile(name string) []byte { +	file, err := ioutil.ReadFile(name) +	if err != nil { +		log.Fatal(err) +	} +	return file +} + +// match identifies the input line that matches the pattern in a !src invocation. +// If start>0, match lines starting there rather than at the beginning. +func match(file string, start int, lines [][]byte, pattern string) int { +	// $ matches the end of the file. +	if pattern == "$" { +		return len(lines) - 1 +	} +	// Number matches the line. +	if i, err := strconv.Atoi(pattern); err == nil { +		return i - 1 // Lines are 1-indexed. +	} +	// /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.Match(lines[i]) { +				return i +			} +		} +		log.Fatalf("%s: no match for %s", file, pattern) +	} +	log.Fatalf("unrecognized pattern: %s", pattern) +	return 0 +} + +// coalesce combines lines. Each time prefix is found on a line, +// it calls fold and replaces the line with return value from fold.  func coalesce(prefix []byte, fold func(i int) (n int, line []byte)) {  	j := 0 // output line number goes up by one each loop  	for i := 0; i < len(lines); { @@ -82,7 +213,7 @@ func coalesce(prefix []byte, fold func(i int) (n int, line []byte)) {  	lines = lines[0:j]  } -// return the <pre> block as a single slice +// foldPre returns the <pre> block as a single slice.  func foldPre(i int) (n int, line []byte) {  	buf := new(bytes.Buffer)  	for i < len(lines) { @@ -96,7 +227,7 @@ func foldPre(i int) (n int, line []byte) {  	return n, buf.Bytes()  } -// return the tab-indented block as a single <pre>-bounded slice +// foldTabs returns the tab-indented block as a single <pre>-bounded slice.  func foldTabs(i int) (n int, line []byte) {  	buf := new(bytes.Buffer)  	buf.WriteString("<pre>\n") @@ -104,7 +235,7 @@ func foldTabs(i int) (n int, line []byte) {  		if !bytes.HasPrefix(lines[i], tab) {  			break  		} -		buf.Write(lines[i]) +		buf.Write(lines[i][1:]) // delete leading tab.  		n++  		i++  	} @@ -112,6 +243,7 @@ func foldTabs(i int) (n int, line []byte) {  	return n, buf.Bytes()  } +// headings turns sections into HTML sections.  func headings() {  	b := bufio.NewWriter(os.Stdout)  	for i, l := range lines { @@ -123,6 +255,7 @@ func headings() {  	b.Flush()  } +// paragraphs turns blank lines into paragraph marks.  func paragraphs() {  	for i, l := range lines {  		if bytes.Equal(l, newline) { @@ -131,12 +264,14 @@ func paragraphs() {  	}  } +// quotes turns "x" in the file into <code>x</code>.  func quotes() {  	for i, l := range lines {  		lines[i] = codeQuotes(l)  	}  } +// quotes turns "x" in the line into <code>x</code>.  func codeQuotes(l []byte) []byte {  	if bytes.HasPrefix(l, preStart) {  		return l @@ -162,7 +297,7 @@ func codeQuotes(l []byte) []byte {  	return buf.Bytes()  } -// drop trailing newline +// trim drops the trailing newline, if present.  func trim(l []byte) []byte {  	n := len(l)  	if n > 0 && l[n-1] == '\n' { @@ -171,7 +306,7 @@ func trim(l []byte) []byte {  	return l  } -// expand tabs to spaces. don't worry about columns. +// expandTabs expands tabs to spaces. It doesn't worry about columns.  func expandTabs(l []byte) []byte {  	return bytes.Replace(l, tab, indent, -1)  } | 
