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")
+ }
+ out.WriteString("- \n")
+ out.WriteString(l)
+ out.WriteString("\n
\n")
+ continue
+ } else if inConvertList {
+ inConvertList = false
+ 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\t\n\t\t%s\n\t\n
\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()
}
}