diff options
Diffstat (limited to 'tools/sw-convert')
-rw-r--r-- | tools/sw-convert/main.go | 222 |
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) +} |