From 82a2083422573a333c3cc03dc0687c505eba15e0 Mon Sep 17 00:00:00 2001 From: antanst Date: Fri, 21 Mar 2025 10:47:15 +0200 Subject: [PATCH] . --- Makefile | 4 +- assets/main.html | 127 -------------------- bin/gmi2html/gmi2html.go | 34 ------ gmi2html.go | 138 ---------------------- gmi2html_test.go | 159 ------------------------- go.mod | 2 +- pkg/gmi2html/assets/main.html | 58 ++++------ pkg/gmi2html/gmi2html_test.go | 210 +++++++++++++++++----------------- templates.go | 144 ----------------------- 9 files changed, 132 insertions(+), 744 deletions(-) delete mode 100644 assets/main.html delete mode 100644 bin/gmi2html/gmi2html.go delete mode 100644 gmi2html.go delete mode 100644 gmi2html_test.go delete mode 100644 templates.go 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}} -
- -`)) -)