~adnano/gmnitohtml

gmnitohtml/main.go -rw-r--r-- 2.8 KiB
1018ac8dAdnan Maolood Makefile: make POSIX-compliant 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Command gmnitohtml is a Gemini text to HTML converter.
package main

import (
	"flag"
	"fmt"
	"html"
	"html/template"
	"io"
	"log"
	"os"
	"strings"

	"git.sr.ht/~adnano/go-gemini"
)

func main() {
	var templatePath string
	flag.StringVar(&templatePath, "t", "", "the template to use")
	flag.Parse()

	if templatePath != "" {
		tmpl, err := template.ParseFiles(templatePath)
		if err != nil {
			log.Fatal(err)
		}

		ctx := new(struct {
			Title   string
			Content template.HTML
		})

		b := new(strings.Builder)
		hw := &HTMLWriter{
			out: b,
		}

		var title bool
		gemini.ParseLines(os.Stdin, func(line gemini.Line) {
			if !title {
				if h1, ok := line.(gemini.LineHeading1); ok {
					ctx.Title = string(h1)
					title = true
				}
			}
			hw.Handle(line)
		})
		hw.Finish()
		ctx.Content = template.HTML(b.String())

		if err := tmpl.Execute(os.Stdout, ctx); err != nil {
			log.Fatal(err)
		}

		return
	}

	hw := &HTMLWriter{
		out: os.Stdout,
	}
	gemini.ParseLines(os.Stdin, hw.Handle)
	hw.Finish()
}

type HTMLWriter struct {
	out  io.Writer
	pre  bool
	list bool
	br   bool
}

func (h *HTMLWriter) Handle(line gemini.Line) {
	if _, ok := line.(gemini.LineListItem); ok {
		if !h.list {
			h.list = true
			fmt.Fprint(h.out, "<ul>\n")
		}
	} else if h.list {
		h.list = false
		fmt.Fprint(h.out, "</ul>\n")
	}
	var blank bool
	switch line := line.(type) {
	case gemini.LineLink:
		url := html.EscapeString(line.URL)
		name := html.EscapeString(line.Name)
		if name == "" {
			name = url
		}
		fmt.Fprintf(h.out, "<p><a href='%s'>%s</a></p>\n", url, name)
	case gemini.LinePreformattingToggle:
		h.pre = !h.pre
		if h.pre {
			alt := strings.TrimSpace(string(line))
			if alt != "" {
				alt = html.EscapeString(alt)
				fmt.Fprintf(h.out, "<pre aria-label='%s'>\n", alt)
			} else {
				fmt.Fprint(h.out, "<pre>\n")
			}
		} else {
			fmt.Fprint(h.out, "</pre>\n")
		}
	case gemini.LinePreformattedText:
		fmt.Fprintf(h.out, "%s\n", html.EscapeString(string(line)))
	case gemini.LineHeading1:
		fmt.Fprintf(h.out, "<h1>%s</h1>\n", html.EscapeString(string(line)))
	case gemini.LineHeading2:
		fmt.Fprintf(h.out, "<h2>%s</h2>\n", html.EscapeString(string(line)))
	case gemini.LineHeading3:
		fmt.Fprintf(h.out, "<h3>%s</h3>\n", html.EscapeString(string(line)))
	case gemini.LineListItem:
		fmt.Fprintf(h.out, "<li>%s</li>\n", html.EscapeString(string(line)))
	case gemini.LineQuote:
		fmt.Fprintf(h.out, "<blockquote>%s</blockquote>\n", html.EscapeString(string(line)))
	case gemini.LineText:
		if line == "" {
			blank = true
			if h.br {
				fmt.Fprint(h.out, "<br/>\n")
			} else {
				h.br = true
			}
		} else {
			fmt.Fprintf(h.out, "<p>%s</p>\n", html.EscapeString(string(line)))
		}
	}
	if h.br && !blank {
		h.br = false
	}
}

func (h *HTMLWriter) Finish() {
	if h.pre {
		fmt.Fprint(h.out, "</pre>\n")
	}
	if h.list {
		fmt.Fprint(h.out, "</ul>\n")
	}
}