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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
|
package main
import (
htemplate "html/template"
"net/url"
"os"
"text/template"
"tildegit.org/tjp/sliderule/gemini/gemtext"
"tildegit.org/tjp/sliderule/gemini/gemtext/atomconv"
gem_htmlconv "tildegit.org/tjp/sliderule/gemini/gemtext/htmlconv"
gem_mdconv "tildegit.org/tjp/sliderule/gemini/gemtext/mdconv"
"tildegit.org/tjp/sliderule/gopher/gophermap"
goph_htmlconv "tildegit.org/tjp/sliderule/gopher/gophermap/htmlconv"
goph_mdconv "tildegit.org/tjp/sliderule/gopher/gophermap/mdconv"
)
const usage = `Conversions for small web formats.
Usage:
sw-convert (-h | --help)
sw-convert --list-formats
sw-convert (-i INPUT | --input INPUT) (-o OUTPUT | --output OUTPUT) [-t PATH | --template PATH]
Options:
-h --help Show this screen.
--list-formats List supported input and output formats.
-i --input INPUT Format with which to parse standard input.
-o --output OUTPUT Which format to write to standard output.
-t --template PATH Path to a template file. May be specified more than once.
-l --location URL URL the source came from. Required only for gemtext to atom conversion.
Templates:
Template files provided by the -t/--template option should be in the golang template formats.
When converting to markdown they will be parsed by text/template (https://pkg.go.dev/text/template),
and for html conversions they will be parsed by html/template (https://pkg.go.dev/html/template).
The template names available for override depend on the type of the source document.
Gemtext:
"header" is rendered at the beginning of the document and is passed the full Document.
"footer" is rendered at the end of the document and is passed the full Document.
"textline" is rendered once for each plain text line, and is passed the Line.
"linkline" is rendered for each link line and is passed the Line.
"preformattedtextlines" is rendered for each block of pre-formatted text, and is passed a slice of Lines.
"heading1line", "heading2line", and "heading3line" are rendered for heading lines and are passed the Line.
"listitemlines" is rendered for any contiguous group of list item lines, and is passed a slice of the Lines.
"quoteline" is rendered for each block-quote line and is passed the Line.
The default gemtext->html templates define an HTML5 document with all HTML nodes given a class of "gemtext".
Document:
The header and footer templates are given the full document, which can be iterated over to produce all the lines.
Line:
Line-specific or line-group-specific templates are passed Line objects. These all have Type, Raw, and String methods, and some have type-specific additional methods.
- link lines also have URL() and Label() methods.
- heading, list item, and quote lines have a Body() method which omit the leading prefixes.
Gophermap:
"header" is rendered at the beginning of the document and is passed the full Document.
"footer" is rendered at the end of the document and is passed the full Document.
"message" is rendered for any contiguous group of info-message lines, and is passed a string of the newline-separated lines.
"image" is rendered for any gif file, bitmap file, png file, or image line types, and is passed the Map Item.
"link" is rendered for all other line types and is passed the Map Item.
The default gophermap->html templates define an HTML5 document with all HTML nodes given a class of "gophermap".
Document:
The full gophermap document object is a list of map items, additionally with a String() method which serializes the full document back into the gophermap format.
Map Item:
An individual gophermap item has attributes Type, Display, Selector, Hostname, and Port, and can re-serialize itself into a gophermap CRLF-terminated line with the String() method.
`
const formats = `Inputs:
gemtext
gophermap
Outputs:
markdown
html
atom (with gemtext input only)
`
func main() {
conf := configure()
tover, hover, err := overrides(conf)
if err != nil {
fail("template loading failed")
}
switch conf.input {
case "gemtext":
doc, err := gemtext.Parse(os.Stdin)
if err != nil {
fail("failure reading gemtext")
}
switch conf.output {
case "markdown":
if err := gem_mdconv.Convert(os.Stdout, doc, tover); err != nil {
fail("failure writing markdown")
}
case "html":
if err := gem_htmlconv.Convert(os.Stdout, doc, hover); err != nil {
fail("failure writing html")
}
case "atom":
u, err := url.Parse(conf.location)
if conf.location == "" || err != nil {
fail("-l|--location must be a valid url for gemtext->atom conversion")
}
if err := atomconv.Convert(os.Stdout, doc, u); err != nil {
fail("failure writing atom XML")
}
default:
goto unknown
}
case "gophermap":
doc, err := gophermap.Parse(os.Stdin)
if err != nil {
fail("failure reading gophermap")
}
switch conf.output {
case "markdown":
if err := goph_mdconv.Convert(os.Stdout, doc, tover); err != nil {
fail("failure writing markdown")
}
case "html":
if err := goph_htmlconv.Convert(os.Stdout, doc, hover); err != nil {
fail("failure writing html")
}
default:
goto unknown
}
}
return
unknown:
os.Stderr.WriteString("unsupported input/output combination\n")
fail(formats)
}
type config struct {
input string
output string
location string
template []string
}
func configure() config {
conf := config{}
for i := 1; i < len(os.Args); i += 1 {
switch os.Args[i] {
case "-h", "--help":
os.Stdout.WriteString(usage)
os.Exit(0)
case "--list-formats":
os.Stdout.WriteString(formats)
os.Exit(0)
case "-i", "--input":
if i == len(os.Args) {
fail(usage)
}
i += 1
conf.input = os.Args[i]
case "-o", "--output":
if i == len(os.Args) {
fail(usage)
}
i += 1
conf.output = os.Args[i]
case "-l", "--location":
if i == len(os.Args) {
fail(usage)
}
i += 1
conf.location = os.Args[i]
case "-t", "--template":
if i == len(os.Args) {
fail(usage)
}
i += 1
conf.template = append(conf.template, os.Args[i])
}
}
if conf.input == "" || conf.output == "" {
fail("both -i|--input and -o|--output are required\n")
}
return conf
}
func overrides(conf config) (*template.Template, *htemplate.Template, error) {
if len(conf.template) == 0 {
return nil, nil, nil
}
switch conf.output {
case "markdown":
tmpl, err := template.New("mdconv").ParseFiles(conf.template...)
if err != nil {
return nil, nil, err
}
return tmpl, nil, nil
case "html":
tmpl, err := htemplate.New("htmlconv").ParseFiles(conf.template...)
if err != nil {
return nil, nil, err
}
return nil, tmpl, err
}
return nil, nil, nil
}
func fail(msg string) {
os.Stderr.WriteString(msg)
os.Exit(1)
}
|