summaryrefslogtreecommitdiff
path: root/tools/sw-convert
diff options
context:
space:
mode:
Diffstat (limited to 'tools/sw-convert')
-rw-r--r--tools/sw-convert/main.go222
1 files changed, 222 insertions, 0 deletions
diff --git a/tools/sw-convert/main.go b/tools/sw-convert/main.go
new file mode 100644
index 0000000..77a459c
--- /dev/null
+++ b/tools/sw-convert/main.go
@@ -0,0 +1,222 @@
+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"
+ goph_htmlconv "tildegit.org/tjp/sliderule/gopher/gophermap/htmlconv"
+ "tildegit.org/tjp/sliderule/gopher/gophermap"
+ 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 + "-" + conf.output {
+ case "gemtext-markdown":
+ doc, err := gemtext.Parse(os.Stdin)
+ if err != nil {
+ fail("gemtext reading failed")
+ }
+ if err := gem_mdconv.Convert(os.Stdout, doc, tover); err != nil {
+ fail("markdown writing failed")
+ }
+ case "gemtext-html":
+ doc, err := gemtext.Parse(os.Stdin)
+ if err != nil {
+ fail("gemtext reading failed")
+ }
+ if err := gem_htmlconv.Convert(os.Stdout, doc, hover); err != nil {
+ fail("html writing failed")
+ }
+ case "gemtext-atom":
+ u, err := url.Parse(conf.location)
+ if conf.location == "" || err != nil {
+ fail("-l|--location must be provided and valid for gemtext to atom conversion")
+ }
+ doc, err := gemtext.Parse(os.Stdin)
+ if err != nil {
+ fail("gemtext reading failed")
+ }
+ if err := atomconv.Convert(os.Stdout, doc, u); err != nil {
+ fail("atom writing failed")
+ }
+ case "gophermap-markdown":
+ doc, err := gophermap.Parse(os.Stdin)
+ if err != nil {
+ fail("gophermap reading failed")
+ }
+ if err := goph_mdconv.Convert(os.Stdout, doc, tover); err != nil {
+ fail("markdown wriiting failed")
+ }
+ case "gophermap-html":
+ doc, err := gophermap.Parse(os.Stdin)
+ if err != nil {
+ fail("gophermap reading failed")
+ }
+ if err := goph_htmlconv.Convert(os.Stdout, doc, hover); err != nil {
+ fail("html writing failed")
+ }
+ default:
+ 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)
+}