github.com/xiaq/elvish@v0.12.0/website/highlighter.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "html" 8 "log" 9 "os" 10 "strings" 11 12 "github.com/elves/elvish/edit/highlight" 13 "github.com/elves/elvish/parse" 14 ) 15 16 var ( 17 scanner = bufio.NewScanner(os.Stdin) 18 lineno = 0 19 ) 20 21 func scan() bool { 22 lineno++ 23 return scanner.Scan() 24 } 25 26 func main() { 27 for scan() { 28 line := scanner.Text() 29 trimmed := strings.TrimLeft(line, " ") 30 indent := line[:len(line)-len(trimmed)] 31 if trimmed == "```elvish" || trimmed == "```elvish-bad" { 32 bad := trimmed == "```elvish-bad" 33 highlighted := convert(collectFenced(indent), bad) 34 fmt.Printf("%s<pre><code>%s</code></pre>\n", indent, highlighted) 35 } else if trimmed == "```elvish-transcript" { 36 highlighted := convertTranscript(collectFenced(indent)) 37 fmt.Printf("%s<pre><code>%s</code></pre>\n", indent, highlighted) 38 } else { 39 fmt.Println(line) 40 } 41 } 42 } 43 44 func collectFenced(indent string) string { 45 var buf bytes.Buffer 46 for scan() { 47 line := scanner.Text() 48 if !strings.HasPrefix(line, indent) { 49 log.Fatalf("bad indent of line %d: %q", lineno, line) 50 } 51 unindented := line[len(indent):] 52 if unindented == "```" { 53 break 54 } 55 if buf.Len() > 0 { 56 buf.WriteRune('\n') 57 } 58 buf.WriteString(unindented) 59 } 60 return buf.String() 61 } 62 63 const ( 64 ps1 = "~> " 65 ps2 = " " 66 ) 67 68 func convertTranscript(transcript string) string { 69 scanner := bufio.NewScanner(bytes.NewBufferString(transcript)) 70 var buf bytes.Buffer 71 72 overread := false 73 var line string 74 for overread || scanner.Scan() { 75 if overread { 76 overread = false 77 } else { 78 line = scanner.Text() 79 } 80 if strings.HasPrefix(line, ps1) { 81 elvishBuf := bytes.NewBufferString(line[len(ps1):] + "\n") 82 for scanner.Scan() { 83 line = scanner.Text() 84 if strings.HasPrefix(line, ps2) { 85 elvishBuf.WriteString(line + "\n") 86 } else { 87 overread = true 88 break 89 } 90 } 91 buf.WriteString(html.EscapeString(ps1)) 92 buf.WriteString(convert(elvishBuf.String(), false)) 93 } else { 94 buf.WriteString(html.EscapeString(line)) 95 buf.WriteString("<br>") 96 } 97 } 98 return buf.String() 99 } 100 101 func convert(text string, bad bool) string { 102 n, err := parse.Parse("highlight", text) 103 if err != nil && !bad { 104 log.Printf("parsing %q: %v", text, err) 105 } 106 107 styling := highlight.Styling{} 108 109 e := highlight.Emitter{ 110 func(s string) bool { 111 return true 112 }, 113 styling.Add, 114 } 115 e.EmitAll(n) 116 117 var buf bytes.Buffer 118 119 openedSpan := false 120 currentStyle := "" 121 122 applier := styling.Apply() 123 for i, r := range text { 124 applier.At(i) 125 style := applier.Get() 126 if style != currentStyle { 127 if openedSpan { 128 buf.WriteString("</span>") 129 } 130 var classes []string 131 for _, c := range strings.Split(style, ";") { 132 if c != "" { 133 classes = append(classes, "sgr-"+c) 134 } 135 } 136 fmt.Fprintf(&buf, `<span class="%s">`, strings.Join(classes, " ")) 137 openedSpan = true 138 currentStyle = style 139 } 140 if r == '\n' { 141 buf.WriteString("<br>") 142 } else { 143 buf.WriteString(html.EscapeString(string(r))) 144 } 145 } 146 if openedSpan { 147 buf.WriteString("</span>") 148 } 149 return buf.String() 150 }