diff --git a/Makefile b/Makefile
index 478fefd..026341f 100644
--- a/Makefile
+++ b/Makefile
@@ -36,10 +36,10 @@ lintfix: fmt
build: clean
mkdir ./dist
- go build -o ./dist/gmi2html ./bin/gmi2html/gmi2html.go
+ go build -o ./dist/gmi2html ./cmd/gmi2html
build-gccgo: clean
- go build -compiler=gccgo -o ./dist/gmi2html ./bin/gmi2html/gmi2html.go
+ go build -compiler=gccgo -o ./dist/gmi2html ./cmd/gmi2html
show-updates:
go list -m -u all
diff --git a/assets/main.html b/assets/main.html
deleted file mode 100644
index 5c1ec79..0000000
--- a/assets/main.html
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
-
-
-
- {{.Title}}
-
-
-
-
-
- {{.Content}}
-
-
-
\ No newline at end of file
diff --git a/bin/gmi2html/gmi2html.go b/bin/gmi2html/gmi2html.go
deleted file mode 100644
index f471cbb..0000000
--- a/bin/gmi2html/gmi2html.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "os"
-
- "github.com/antanst/gmi2html"
-)
-
-func main() {
- err := runApp()
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-}
-
-func runApp() error {
- data, err := io.ReadAll(os.Stdin)
- if err != nil {
- return err
- }
-
- html, err := gmi2html.Gmi2html(string(data), "", false)
- if err != nil {
- return err
- }
- _, err = fmt.Fprintf(os.Stdout, "%s", html)
- if err != nil {
- return err
- }
- return nil
-}
diff --git a/gmi2html.go b/gmi2html.go
deleted file mode 100644
index b6e5dcb..0000000
--- a/gmi2html.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package gmi2html
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "net/url"
- "regexp"
- "strings"
-)
-
-// Based on https://geminiprotocol.net/docs/gemtext-specification.gmi
-
-// Gmi2html converts Gemini text to HTML with proper escaping and wraps it in a container with typography-focused CSS
-func Gmi2html(text string, title string, contentOnly bool) (string, error) {
- content := convertGeminiContent(text)
-
- if contentOnly {
- return content, nil
- }
-
- var buffer bytes.Buffer
- err := containerTmpl.Execute(&buffer, struct {
- Title string
- Content template.HTML
- }{
- Title: title,
- Content: template.HTML(content), // Content already properly escaped in convertGeminiContent
- })
- if err != nil {
- fmt.Printf("Error executing container template: %s\n", err)
- return "", err
- }
-
- return buffer.String(), nil
-}
-
-// convertGeminiContent converts Gemini text to HTML with proper escaping
-func convertGeminiContent(text string) string {
- lines := strings.Split(text, "\n")
- var buffer bytes.Buffer
- normalMode := true
-
- for _, line := range lines {
- switch {
- case strings.HasPrefix(line, "=>"):
- handleLinkLine(&buffer, line)
- case strings.HasPrefix(line, "```"):
- normalMode = !normalMode
- // Don't output the ``` line
- case strings.HasPrefix(line, "###"):
- content := strings.TrimSpace(strings.TrimPrefix(line, "###"))
- err := h3Tmpl.Execute(&buffer, content)
- if err != nil {
- return ""
- }
- case strings.HasPrefix(line, "##"):
- content := strings.TrimSpace(strings.TrimPrefix(line, "##"))
- err := h2Tmpl.Execute(&buffer, content)
- if err != nil {
- return ""
- }
- case strings.HasPrefix(line, "#"):
- content := strings.TrimSpace(strings.TrimPrefix(line, "#"))
- err := h1Tmpl.Execute(&buffer, content)
- if err != nil {
- return ""
- }
- case strings.HasPrefix(line, "*"):
- content := strings.TrimSpace(strings.TrimPrefix(line, "*"))
- err := listItemTmpl.Execute(&buffer, content)
- if err != nil {
- return ""
- }
- case strings.HasPrefix(line, ">"):
- content := strings.TrimSpace(strings.TrimPrefix(line, ">"))
- err := blockquoteTmpl.Execute(&buffer, content)
- if err != nil {
- return ""
- }
- default:
- if normalMode {
- err := textLineTmpl.Execute(&buffer, line)
- if err != nil {
- return ""
- }
- } else {
- err := preformattedTmpl.Execute(&buffer, line)
- if err != nil {
- return ""
- }
- }
- }
- }
-
- return buffer.String()
-}
-
-// handleLinkLine parses and renders a link line
-func handleLinkLine(buffer *bytes.Buffer, linkLine string) {
- url, description, err := parseGeminiLink(linkLine)
- if err != nil {
- fmt.Printf("Error parsing gemini link line: %s\n", err)
- return
- }
-
- err = linkTmpl.Execute(buffer, struct {
- URL, Description string
- }{url, description})
- if err != nil {
- return
- }
-}
-
-// parseGeminiLink extracts URL and description from a link line
-func parseGeminiLink(linkLine string) (string, string, error) {
- re := regexp.MustCompile(`^=>[ \t]+(\S+)([ \t]+.*)?`)
- matches := re.FindStringSubmatch(linkLine)
- if len(matches) == 0 {
- return "", "", fmt.Errorf("error parsing link line: no regexp match for line %s", linkLine)
- }
-
- urlStr := matches[1]
-
- // Check: Unescape the URL if escaped
- _, err := url.QueryUnescape(urlStr)
- if err != nil {
- return "", "", fmt.Errorf("error parsing link line: %w input '%s'", err, linkLine)
- }
-
- // Set description to URL if not provided
- description := urlStr
- if len(matches) > 2 && strings.TrimSpace(matches[2]) != "" {
- description = strings.TrimSpace(matches[2])
- }
-
- return urlStr, description, nil
-}
diff --git a/gmi2html_test.go b/gmi2html_test.go
deleted file mode 100644
index 48e46dc..0000000
--- a/gmi2html_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package gmi2html
-
-import (
- "strings"
- "testing"
-)
-
-func TestConvertGeminiContent(t *testing.T) {
- tests := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "Simple text line",
- input: "This is a simple text line",
- expected: `This is a simple text line
`,
- },
- {
- name: "Heading line level 1",
- input: "# Main heading",
- expected: `Main heading
`,
- },
- {
- name: "Link line with description",
- input: "=> https://example.com Example site",
- expected: ``,
- },
- {
- name: "List item",
- input: "* List item 1",
- expected: `• List item 1
`,
- },
- {
- name: "Quote line",
- input: "> This is a quote",
- expected: `This is a quote
`,
- },
- {
- name: "Preformatted text",
- input: "```\ncode line 1\ncode line 2\n```",
- expected: `code line 1
code line 2
`,
- },
- {
- name: "Mixed content",
- input: "# Title\n\nNormal paragraph\n\n=> https://example.com Link to example",
- expected: `Title
Normal paragraph
`,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := convertGeminiContent(tt.input)
- if result != tt.expected {
- t.Errorf("convertGeminiContent(%q):\ngot: %s\nwant: %s",
- tt.input, result, tt.expected)
- }
- })
- }
-}
-
-func TestGmi2html(t *testing.T) {
- sample := "# Hello Gemini\n\nThis is a test document.\n\n=> https://gemini.circumlunar.space/ Project Gemini"
- result, _ := Gmi2html(sample, "Gemini Test", false)
-
- // Check that it contains the expected elements
- if !strings.Contains(result, "Gemini Test") {
- t.Error("Output HTML missing title")
- }
-
- if !strings.Contains(result, "Hello Gemini
") {
- t.Error("Output HTML missing properly formatted heading")
- }
-
- if !strings.Contains(result, "Project Gemini") {
- t.Error("Output HTML missing properly formatted link")
- }
-
- // Check that CSS is included
- if !strings.Contains(result, "
-
-
-
- {{.Content}}
-
-
-