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  }