diff --git a/gemini b/gemini index 07f4750..1dae086 100755 Binary files a/gemini and b/gemini differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2a6f761 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module tildegit.org/sloum/gemini + +go 1.16 diff --git a/main.go b/main.go index 81c4d08..1b71954 100644 --- a/main.go +++ b/main.go @@ -11,8 +11,8 @@ import ( ) const ( - SPEC_URL string = "gemini://gemini.circumlunar.space:1965/docs/spec-spec.txt" - VERSION string = "0.1" + SPEC_URL string = "gemini://gemini.circumlunar.space:1965/docs/specification.gmi" + VERSION string = "0.2.0" WARNING int = iota ERROR ) @@ -194,6 +194,162 @@ func LoadCertificate(cert, key string) (tls.Certificate, error) { return certificate, nil } +func WriteToFile(path, content string) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + f.WriteString(content) + return nil +} + +func convertToHTML(lines []string, lang string, blanks bool) string { + var out strings.Builder + header := ` + + + + + + + + + +
+ ` + out.WriteString(fmt.Sprintf(header, lang)) + + inConvertPre := false + inConvertList := false + art := false + code := false + + for _, l := range lines { + // Handle lists + if strings.HasPrefix(l, "* ") { + if !inConvertList { + inConvertList = true + out.WriteString("\n") + } + + // Handle preformatted blocks + if strings.HasPrefix(l, "```") && !inConvertPre { + inConvertPre = true + alt := "" + if len(l) > 3 { + alt = l[3:] + } + art = strings.Contains(strings.ToLower(alt), "art") + code = strings.Contains(strings.ToLower(alt), "code") + if art { + out.WriteString(fmt.Sprintf(`
`, alt)) + out.WriteString("\n
\n")
+			} else if code {
+				out.WriteString("
\n\n")
+			} else {
+				out.WriteString("
\n")
+			}
+			continue
+		} else if strings.HasPrefix(l, "```") && inConvertPre {
+			inConvertPre = false
+			if art {
+				out.WriteString("
\n
\n") + } else if code { + out.WriteString("\n\n") + } else { + out.WriteString("\n") + } + continue + } else if inConvertPre { + out.WriteString(l) + out.WriteRune('\n') + continue + } + + // Handle block quote + if strings.HasPrefix(l, ">") { + out.WriteString(fmt.Sprintf("
\n\t%s\n\n", l)) + continue + } + + // Handle headings + if strings.HasPrefix(l, "####") { + out.WriteString(l) + continue + } else if strings.HasPrefix(l, "###") { + out.WriteString(fmt.Sprintf("

\n\t%s\n

\n", l)) + continue + } else if strings.HasPrefix(l, "##") { + out.WriteString(fmt.Sprintf("

\n\t%s\n

\n", l)) + continue + } else if strings.HasPrefix(l, "#") { + out.WriteString(fmt.Sprintf("

\n\t%s\n

\n", l)) + continue + } + + // Handle links + if strings.HasPrefix(l, "=>") { + if len(l) < 3 { + continue + } + split := strings.SplitN(strings.TrimSpace(l[2:]), " ", 2) + u := strings.TrimSpace(split[0]) + txt := u + if len(split) > 1 { + txt = strings.TrimSpace(split[1]) + } + out.WriteString(fmt.Sprintf("\n", u, txt)) + continue + } + + // Regular text + if len(l) > 0 { + out.WriteString("

\n\t") + out.WriteString(l) + out.WriteString("\n

\n") + } else { + if blanks { + out.WriteString("
\n") + } + } + + } + + out.WriteString("\t\t
\n\t\n\n") + + return out.String() +} + +func convertTo(file, format, lang string, blanks bool, outPath string) error { + var output string + lines := strings.Split(file, "\n") + + switch format { + case "html": + output = convertToHTML(lines, lang, blanks) + default: + return fmt.Errorf("Unknown conversion format %q", format) + } + if outPath == "" { + fmt.Print(output) + } else { + err := WriteToFile(outPath, output) + if err != nil { + return err + } + } + return nil +} + func get(resource, cert, key string, headerOnly, currentSpec bool) (string, error) { hasScheme := strings.Contains(resource, "://") if !hasScheme { @@ -259,7 +415,7 @@ func get(resource, cert, key string, headerOnly, currentSpec bool) (string, erro func main() { fmtCmd := flag.NewFlagSet("fmt", flag.ExitOnError) fmtOut := fmtCmd.String("o", "", "Path to output file") - fmtSupress := fmtCmd.Bool("s", false, "Supress error messaging") + fmtSupress := fmtCmd.Bool("quiet", false, "Supress error messaging") getCmd := flag.NewFlagSet("get", flag.ExitOnError) getOut := getCmd.String("o", "", "Path to output file") @@ -270,12 +426,37 @@ func main() { specCmd := flag.NewFlagSet("spec", flag.ExitOnError) specOut := specCmd.String("o", "", "Path to output file") + convertCmd := flag.NewFlagSet("convert", flag.ExitOnError) + convertFormat := convertCmd.String("to", "html", "Valid: html|text|markdown") + convertLang := convertCmd.String("lang", "en", "The document's language abbreviation for the html lang attribute") + convertBlankLines := convertCmd.Bool("blanks", false, "Preserve empty lines in the converted document") + convertOut := convertCmd.String("o", "", "Path to output file") + if len(os.Args) < 2 { - fmt.Println("expected command: fmt, get, spec, or version") + fmt.Fprintf(os.Stderr, "Gemini Tool v%s\n", VERSION) + fmt.Fprintf(os.Stderr, "Expected command: convert, fmt, get, spec, or version\n") + flag.Usage() os.Exit(1) } switch os.Args[1] { + case "convert": + convertCmd.Parse(os.Args[2:]) + if len(convertCmd.Args()) != 1 { + fmt.Printf("Incorrect number of positional arguments expected 1, got %d\n\ngemini convert [flags] [filepath]\n\n", len(fmtCmd.Args())) + convertCmd.Usage() + os.Exit(1) + } + text, err := fmtFile(convertCmd.Args()[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } + err = convertTo(text, *convertFormat, *convertLang, *convertBlankLines, *convertOut) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } case "fmt": fmtCmd.Parse(os.Args[2:]) if len(fmtCmd.Args()) != 1 { @@ -291,7 +472,11 @@ func main() { fmt.Print(text) fmt.Fprint(os.Stderr, "\n") // So that the terminal input is always on a new line, but this LF doesnt get piped into a document } else { - // TODO handle file writing + err = WriteToFile(*fmtOut, text) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } } if !*fmtSupress { @@ -331,6 +516,11 @@ func main() { // TODO handle file writing } case "version": - fmt.Printf("Gemini Tool v%s\n", VERSION) + fmt.Printf("v%s\n", VERSION) + default: + fmt.Fprintf(os.Stderr, "Gemini Tool v%s\n", VERSION) + fmt.Fprintf(os.Stderr, "Expected command: convert, fmt, get, spec, or version\n") + flag.Parse() + flag.PrintDefaults() } }