From 3cc3582d35944bb1926169166d3a066f150af6b1 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Mon, 13 Oct 2025 14:32:56 +0200 Subject: [PATCH 1/5] fix docker build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9f5b0a9..cd07e52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY ./*.go . -COPY home.gmi . +COPY ./*.gmi . COPY internal ./internal From 9b100e88a2a55070e030d9e1443b734a31ffdaed Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Wed, 15 Oct 2025 15:09:02 +0200 Subject: [PATCH 2/5] add dir view to codeview ") => %s here ", httpURL)) ", url, file.Path)) ", url, file.Path)) ----------------------------- => / Back to home ") --- internal/codeview/codeview.go | 98 +++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/internal/codeview/codeview.go b/internal/codeview/codeview.go index e07eba2..e4b64dc 100644 --- a/internal/codeview/codeview.go +++ b/internal/codeview/codeview.go @@ -1,10 +1,12 @@ package codeview import ( + "encoding/json" "fmt" "io" "log/slog" "net/http" + "net/url" "strings" "github.com/kulak/gemini" @@ -16,18 +18,16 @@ import ( * * Features: * - [x] Individual file view with line numbers? - * - [ ] Dir view with links to files + * - [x] Dir view with links to files * - [ ] Project root view with markdown to gemtxt conversion and file tree */ -// https://git.travisshears.com/travisshears/personal-gemini-capsule/raw/branch/main/internal/guestbook/guestbook.go -// /codeview/raw/personal-gemini-capsule/src/branch/main/internal/guestbook/guestbook.go func HandleRequest(w gemini.ResponseWriter, req *gemini.Request) { path := req.URL.Path switch { - // case path == "/gemlog" || path == "/gemlog/": - // g.serveIndex(w, req) + case strings.HasPrefix(path, "/codeview/dir"): + serveDir(w, req, path) case strings.HasPrefix(path, "/codeview/raw/"): serveFile(w, req, path) default: @@ -35,6 +35,94 @@ func HandleRequest(w gemini.ResponseWriter, req *gemini.Request) { } } +func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { + repo := req.URL.Query().Get("repo") + filepath := url.QueryEscape(req.URL.Query().Get("filepath")) + branch := req.URL.Query().Get("branch") + commit := req.URL.Query().Get("commit") + slog.Info("Serving directory", "repo", repo, "filepath", filepath, "branch", branch, "commit", commit) + var gitURL string + ref := "" + if branch != "" { + ref = branch + } else if commit != "" { + ref = commit + } + + if filepath == "." { + gitURL = "https://git.travisshears.com/api/v1/repos/travisshears/" + repo + "/contents?ref=" + ref + } else { + gitURL = "https://git.travisshears.com/api/v1/repos/travisshears/" + repo + "/contents/" + filepath + "?ref=" + ref + } + + slog.Info("Fetching directory contents", "url", gitURL) + httpReq, err := http.NewRequest("GET", gitURL, nil) + if err != nil { + slog.Error("Failed to create request to git server", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + res, err := http.DefaultClient.Do(httpReq) + if err != nil { + slog.Error("Failed to send request to git server", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + slog.Error("Failed to read res body", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + + if res.StatusCode < 200 || res.StatusCode >= 300 { + slog.Error("unexpected status code", "statusCode", res.StatusCode, "body", string(body)) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem connecting to git server") + return + } + + var dirRes []struct { + Name string `json:"name"` + Path string `json:"path"` + URL string `json:"html_url"` + Type string `json:"type"` + } + + if err := json.Unmarshal(body, &dirRes); err != nil { + slog.Error("failed to parse response", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Problem parsing response from git server") + return + } + slog.Info("Successfully parsed response from git server", "res", dirRes) + + var content strings.Builder + w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") + content.WriteString("# Dir CodeView\n") + httpURL := fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/main/%s", filepath) + if branch != "" { + httpURL = fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/%s/%s", branch, filepath) + } else if commit != "" { + httpURL = fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/commit/%s/%s", commit, filepath) + } + content.WriteString(fmt.Sprintf("This is a code view which proxies my personal git server. On the clearnet the following dir is available:\n=> %s here\n\n", httpURL)) + + for _, file := range dirRes { + switch file.Type { + case "dir": + url := fmt.Sprintf("/codeview/dir?repo=%s&filepath=%s&commit=%s&branch=%s", repo, file.Path, commit, branch) + content.WriteString(fmt.Sprintf("=> %s %s/\n", url, file.Path)) + case "file": + p := strings.TrimPrefix(file.URL, "https://git.travisshears.com/travisshears/") + url := fmt.Sprintf("/codeview/raw/%s", p) + content.WriteString(fmt.Sprintf("=> %s %s\n", url, file.Path)) + } + } + content.WriteString("\n\n-----------------------------\n\n=> / Back to home\n") + w.WriteBody([]byte(content.String())) +} + func serveFile(w gemini.ResponseWriter, req *gemini.Request, path string) { path = strings.TrimPrefix(path, "/codeview/raw/") rawURL := strings.Replace(path, "src", "raw", 1) From 63775d57e026dad0fb4630c02278ad99f4088778 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Wed, 15 Oct 2025 15:56:13 +0200 Subject: [PATCH 3/5] convert markdown to gemtext and display README.md files ") => %s here ", httpURL)) ") ----------------------------- ") --- go.mod | 7 ++++ go.sum | 11 ++++++ internal/codeview/codeview.go | 71 ++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 3984733..9a038a6 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,10 @@ require ( github.com/kulak/gemini v1.2.2 github.com/mattn/go-sqlite3 v1.14.32 ) + +require ( + git.sr.ht/~kota/fuckery v0.2.0 // indirect + git.sr.ht/~kota/goldmark-gemtext v0.3.3 // indirect + git.sr.ht/~kota/goldmark-wiki v0.0.0-20211119234413-891f759dc3aa // indirect + github.com/yuin/goldmark v1.7.13 // indirect +) diff --git a/go.sum b/go.sum index b86cbc1..5c37592 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,14 @@ +git.sr.ht/~kota/fuckery v0.2.0 h1:0JfnSMiQWFVFdGplolb2ZuRqDQLG5uAzDmH/oSaRj9Y= +git.sr.ht/~kota/fuckery v0.2.0/go.mod h1:UuYM/CTzL6F/FYB/uulUV1ceymM5NSJv2Qif3VVj/s8= +git.sr.ht/~kota/goldmark-gemtext v0.3.3 h1:Vchc1qgEoBE0XgztZFJwKyAYmzNL/C0t34F+gNw0FDU= +git.sr.ht/~kota/goldmark-gemtext v0.3.3/go.mod h1:nWcD/0KzomgMaY9t7odGt7mc5QWiaTnrP6CwxYDJHYY= +git.sr.ht/~kota/goldmark-wiki v0.0.0-20211119234413-891f759dc3aa h1:4JHg1p9hfnZEJtVjmhFhvYV4RO12mMDQf6NFolFJDzo= +git.sr.ht/~kota/goldmark-wiki v0.0.0-20211119234413-891f759dc3aa/go.mod h1:7bNFInCQWKDjV6b8qsLDwQ/q5XrVMjrACQjOnJGXe/4= git.travisshears.com/travisshears/gemlog-cli v1.1.0 h1:iFMIeYyzPvoUw2sQqGg8PTejCcKxgmhjy5HqpVo3Ag8= git.travisshears.com/travisshears/gemlog-cli v1.1.0/go.mod h1:N6l94N174EhDOIHU0/RlJ0PWrxB0BMa0W6LcpgAtvCE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kulak/gemini v1.2.2 h1:wPFOAFFdOf9ZaHcpMwTq1xYUWxmyV3h0uQl0OXCGa+A= github.com/kulak/gemini v1.2.2/go.mod h1:8yiD7yhLkUGvOpdvgd/0nKQD2I0ChIAKD3yHuT13R5k= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= @@ -11,6 +18,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/codeview/codeview.go b/internal/codeview/codeview.go index e4b64dc..0de84e5 100644 --- a/internal/codeview/codeview.go +++ b/internal/codeview/codeview.go @@ -1,6 +1,7 @@ package codeview import ( + "bytes" "encoding/json" "fmt" "io" @@ -9,6 +10,10 @@ import ( "net/url" "strings" + gemtext "git.sr.ht/~kota/goldmark-gemtext" + goldmark "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/kulak/gemini" ) @@ -84,10 +89,11 @@ func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { } var dirRes []struct { - Name string `json:"name"` - Path string `json:"path"` - URL string `json:"html_url"` - Type string `json:"type"` + Name string `json:"name"` + Path string `json:"path"` + URL string `json:"html_url"` + Type string `json:"type"` + RawURL string `json:"download_url"` } if err := json.Unmarshal(body, &dirRes); err != nil { @@ -99,7 +105,7 @@ func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { var content strings.Builder w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") - content.WriteString("# Dir CodeView\n") + content.WriteString("# Directory CodeView\n") httpURL := fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/main/%s", filepath) if branch != "" { httpURL = fmt.Sprintf("https://git.travisshears.com/travisshears/personal-gemini-capsule/src/branch/%s/%s", branch, filepath) @@ -108,6 +114,22 @@ func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { } content.WriteString(fmt.Sprintf("This is a code view which proxies my personal git server. On the clearnet the following dir is available:\n=> %s here\n\n", httpURL)) + // print readme page + // var readmeURL string + for _, file := range dirRes { + if file.Type == "file" && file.Name == "README.md" { + slog.Info("Found README.md", "file", file) + gemtext, err := getRemoteMarkdownFile(file.RawURL) + if err != nil { + slog.Error("Failed to fetch README.md", "url", file.RawURL, "error", err) + continue + } + content.WriteString("README.md found. The following is a markdown to gemtext conversion with directory listing below\n") + content.WriteString(gemtext) + content.WriteString("\n\n-----------------------------\n\n") + } + } + for _, file := range dirRes { switch file.Type { case "dir": @@ -123,6 +145,45 @@ func serveDir(w gemini.ResponseWriter, req *gemini.Request, path string) { w.WriteBody([]byte(content.String())) } +func getRemoteMarkdownFile(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + slog.Error("Failed to fetch remote markdown file", "url", url, "error", err) + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + slog.Error("Failed to fetch remote markdown file", "url", url, "status", resp.Status) + return "", fmt.Errorf("failed to fetch remote markdown file") + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + slog.Error("Failed to read remote markdown file", "url", url, "error", err) + return "", err + } + + return convertMdToGemtext(string(body)), nil +} + +func convertMdToGemtext(markdown string) string { + var buf bytes.Buffer + md := goldmark.New( + goldmark.WithExtensions( + extension.Linkify, + extension.Strikethrough, + ), + ) + + // set some options + // options := []Option{WithHeadingLink(HeadingLinkAuto), WithCodeSpan(CodeSpanMarkdown)} + + md.SetRenderer(gemtext.New()) + _ = md.Convert([]byte(markdown), &buf) // ignoring errors for example + return buf.String() +} + func serveFile(w gemini.ResponseWriter, req *gemini.Request, path string) { path = strings.TrimPrefix(path, "/codeview/raw/") rawURL := strings.Replace(path, "src", "raw", 1) From e4ba48fe0bedc381ec1a9707153bd734f58a4fb2 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Wed, 15 Oct 2025 16:46:11 +0200 Subject: [PATCH 4/5] tidy deps --- go.mod | 4 ++-- go.sum | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9a038a6..6d0b21e 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module gemini_site go 1.25.0 require ( + git.sr.ht/~kota/goldmark-gemtext v0.3.3 git.travisshears.com/travisshears/gemlog-cli v1.1.0 github.com/kulak/gemini v1.2.2 github.com/mattn/go-sqlite3 v1.14.32 + github.com/yuin/goldmark v1.7.13 ) require ( git.sr.ht/~kota/fuckery v0.2.0 // indirect - git.sr.ht/~kota/goldmark-gemtext v0.3.3 // indirect git.sr.ht/~kota/goldmark-wiki v0.0.0-20211119234413-891f759dc3aa // indirect - github.com/yuin/goldmark v1.7.13 // indirect ) diff --git a/go.sum b/go.sum index 5c37592..273d2b7 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,7 @@ git.travisshears.com/travisshears/gemlog-cli v1.1.0 h1:iFMIeYyzPvoUw2sQqGg8PTejC git.travisshears.com/travisshears/gemlog-cli v1.1.0/go.mod h1:N6l94N174EhDOIHU0/RlJ0PWrxB0BMa0W6LcpgAtvCE= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kulak/gemini v1.2.2 h1:wPFOAFFdOf9ZaHcpMwTq1xYUWxmyV3h0uQl0OXCGa+A= github.com/kulak/gemini v1.2.2/go.mod h1:8yiD7yhLkUGvOpdvgd/0nKQD2I0ChIAKD3yHuT13R5k= From d636da952b40fde9733682a696278862fff125a9 Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Wed, 15 Oct 2025 16:50:36 +0200 Subject: [PATCH 5/5] add codeview link to homepage --- home.gmi | 1 + 1 file changed, 1 insertion(+) diff --git a/home.gmi b/home.gmi index 345b3c8..0fe52ba 100644 --- a/home.gmi +++ b/home.gmi @@ -31,6 +31,7 @@ What I'm currently working on: => /gemlog Gemlog - Gemini exclusive blog => /guestbook Guestbook - Open guestbook for visitors to sign => /microblog Microblog - Aggregation of all my microblog posts +=> /codeview/dir?repo=personal-gemini-capsule&filepath=. Codeview - Native gemini git repo browser, ex the source code of this capsule ## Site updates