blob: e4a2b5293375cec9630d0a498625dff762e17c1f [file] [log] [blame]
// 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.
// Process plain text into HTML.
// - h2's are made from lines followed by a line "----\n"
// - tab-indented blocks become <pre> blocks
// - blank lines become <p> marks
// - "quoted strings" become <code>quoted strings</code>
package main
import (
"bufio";
"bytes";
"log";
"os";
)
var (
lines = make([][]byte, 0, 10000); // assume big enough
linebuf = make([]byte, 10000); // assume big enough
empty = []byte("");
newline = []byte("\n");
tab = []byte("\t");
quote = []byte(`"`);
sectionMarker = []byte("----\n");
preStart = []byte("<pre>");
preEnd = []byte("</pre>\n");
pp = []byte("<p>\n");
);
func main() {
read();
headings();
paragraphs();
coalesce(preStart, foldPre);
coalesce(tab, foldTabs);
quotes();
write();
}
func read() {
b := bufio.NewReader(os.Stdin);
for {
line, err := b.ReadBytes('\n');
if err == os.EOF {
break;
}
if err != nil {
log.Exit(err)
}
n := len(lines);
lines = lines[0:n+1];
lines[n] = line;
}
}
func write() {
b := bufio.NewWriter(os.Stdout);
for _, line := range lines {
b.Write(expandTabs(line));
}
b.Flush();
}
// each time prefix is found on a line, call fold and replace
// 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); {
if bytes.HasPrefix(lines[i], prefix) {
nlines, block := fold(i);
lines[j] = block;
i += nlines;
} else {
lines[j] = lines[i];
i++;
}
j++;
}
lines = lines[0:j];
}
// return the <pre> block as a single slice
func foldPre(i int) (n int, line []byte) {
buf := new(bytes.Buffer);
for i < len(lines) {
buf.Write(lines[i]);
n++;
if bytes.Equal(lines[i], preEnd) {
break
}
i++;
}
return n, buf.Bytes();
}
// return 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");
for i < len(lines) {
if !bytes.HasPrefix(lines[i], tab) {
break;
}
buf.Write(lines[i]);
n++;
i++;
}
buf.WriteString("</pre>\n");
return n, buf.Bytes();
}
func headings() {
b := bufio.NewWriter(os.Stdout);
for i, l := range lines {
if i > 0 && bytes.Equal(l, sectionMarker) {
lines[i-1] = []byte("<h2>" + string(trim(lines[i-1])) + "</h2>\n");
lines[i] = empty;
}
}
b.Flush();
}
func paragraphs() {
for i, l := range lines {
if bytes.Equal(l, newline) {
lines[i] = pp;
}
}
}
func quotes() {
for i, l := range lines {
lines[i] = codeQuotes(l);
}
}
func codeQuotes(l []byte) []byte {
if bytes.HasPrefix(l, preStart) {
return l
}
n := bytes.Index(l, quote);
if n < 0 {
return l
}
buf := new(bytes.Buffer);
inQuote := false;
for _, c := range l {
if c == '"' {
if inQuote {
buf.WriteString("</code>")
} else {
buf.WriteString("<code>")
}
inQuote = !inQuote
} else {
buf.WriteByte(c)
}
}
return buf.Bytes();
}
// drop trailing newline
func trim(l []byte) []byte {
n := len(l);
if n > 0 && l[n-1] == '\n' {
return l[0:n-1]
}
return l
}
// expand tabs to 4 spaces. don't worry about columns.
func expandTabs(l []byte) []byte {
j := 0; // position in linebuf.
for _, c := range l {
if c == '\t' {
for k := 0; k < 4; k++ {
linebuf[j] = ' ';
j++;
}
} else {
linebuf[j] = c;
j++;
}
}
return linebuf[0:j];
}