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