Compare commits

...

5 commits

Author SHA1 Message Date
d636da952b
add codeview link to homepage 2025-10-15 16:50:39 +02:00
e4ba48fe0b
tidy deps 2025-10-15 16:46:24 +02:00
63775d57e0
convert markdown to gemtext and display README.md files
")
=> %s here

", httpURL))
")

-----------------------------

")
2025-10-15 16:45:42 +02:00
9b100e88a2
add dir view to codeview
")
=> %s here

", httpURL))
", url, file.Path))
", url, file.Path))

-----------------------------

=> / Back to home
")
2025-10-15 15:09:25 +02:00
3cc3582d35
fix docker build 2025-10-15 11:53:48 +02:00
5 changed files with 175 additions and 6 deletions

View file

@ -13,7 +13,7 @@ COPY go.mod go.sum ./
RUN go mod download
COPY ./*.go .
COPY home.gmi .
COPY ./*.gmi .
COPY internal ./internal

7
go.mod
View file

@ -3,7 +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-wiki v0.0.0-20211119234413-891f759dc3aa // indirect
)

12
go.sum
View file

@ -1,7 +1,15 @@
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 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=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
@ -11,6 +19,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=

View file

@ -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

View file

@ -1,12 +1,19 @@
package codeview
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
gemtext "git.sr.ht/~kota/goldmark-gemtext"
goldmark "github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/kulak/gemini"
)
@ -16,18 +23,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 +40,150 @@ 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"`
RawURL string `json:"download_url"`
}
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("# 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)
} 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))
// 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":
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 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)