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