summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commit.go8
-rw-r--r--gemini.go31
-rw-r--r--gopher.go41
-rw-r--r--refs.go2
-rw-r--r--repo.go30
5 files changed, 111 insertions, 1 deletions
diff --git a/commit.go b/commit.go
index c7a102d..cdbb244 100644
--- a/commit.go
+++ b/commit.go
@@ -5,6 +5,7 @@ import (
"time"
)
+// Commit represents a git commit.
type Commit struct {
Repo *Repository
@@ -22,15 +23,22 @@ type Commit struct {
Message string
}
+// ParentHash returns a ref name usable to reach the commit's parent.
func (c *Commit) ParentHash() string {
return c.Hash + "^"
}
+// ShortMessage returns the first line of the commit message.
func (c *Commit) ShortMessage() string {
short, _, _ := strings.Cut(c.Message, "\n")
return short
}
+// RestOfMessage returns all but the first line of the commit message.
+//
+// It will trim any newline prefixes however, so be aware that
+// c.ShortMessage + "\n" + c.RestOfMessage may not produce the original
+// commit message. For that use c.Message.
func (c *Commit) RestOfMessage() string {
_, rest, _ := strings.Cut(c.Message, "\n")
return strings.TrimPrefix(rest, "\n")
diff --git a/gemini.go b/gemini.go
index 7e106b7..63477d5 100644
--- a/gemini.go
+++ b/gemini.go
@@ -19,6 +19,37 @@ const (
reponamekey = "syw_reponame"
)
+// GeminiRouter builds a router that will handle requests into a directory of git repositories.
+//
+// The routes it defines are:
+// / gemtext listing of the repositories in the directory
+// /:syw_reponame[/] gemtext overview of the repository
+// /:syw_reponame/branches gemtext list of branches/heads
+// /:syw_reponame/tags gemtext listing of tags
+// /:syw_reponame/refs/:ref/ gemtext overview of a ref
+// /:syw_reponame/refs/:ref/tree/*path gemtext listing of directories, raw files
+// /:syw_reponame/diffstat/:fromref/:toref text/plain diffstat between two refs
+// /:syw_reponame/diff/:fromref/:toref text/x-diff between two refs
+//
+// The overrides argument can provide templates to define the behavior of nearly all of the above
+// routes. All of them have default implementations so the argument can even be nil, but otherwise
+// the template names used are:
+// repo_root.gmi gemtext at /
+// repo_home.gmi gemtext at /:syw_reponame/
+// branch_list.gmi gemtext at /:syw_reponame/branches
+// tag_list.gmi gemtext at /:syw_reponame/tags
+// ref.gmi gemtext at /:syw_reponame/refs/:ref/
+// tree.gmi gemtext for directories requested under /:syw_reponame/refs/:ref/tree/*path
+// (file paths return the raw files without any template involved)
+// diffstat.gmi the plaintext diffstat at /:syw_reponame/diffstat/:fromref/:toref
+// diff.gmi the text/x-diff at /:syw_reponame/diff/:fromref/:toref
+//
+// Most of the templates above are rendered with an object with 3 fields:
+// Ctx: the context.Context from the request
+// Repo: a *syw.Repository object corresponding to <repodir>/:syw_reponame
+// Params: a map[string]string of the route parameters
+//
+// The only exception is repo_root.gmi, which is rendered with a slice of the repo names instead.
func GeminiRouter(repodir string, overrides *template.Template) *sliderule.Router {
tmpl, err := addTemplates(geminiTemplate, overrides)
if err != nil {
diff --git a/gopher.go b/gopher.go
index c725d68..e85599b 100644
--- a/gopher.go
+++ b/gopher.go
@@ -14,6 +14,47 @@ import (
"tildegit.org/tjp/sliderule/gopher"
)
+// GopherRouter builds a router that will handle gopher requests in a directory of git repositories.
+//
+// The routes it defines are:
+// / gophermap listing of the repositories in the directory
+// /:syw_reponame gophermap overview of the repository
+// /:syw_reponame/branches gophermap list of branches/head
+// /:syw_reponame/tags gophermap listing of tags
+// /:syw_reponame/refs/:ref gophermap overview of a ref
+// /:syw_reponame/refs/:ref/tree gophermap listing of a ref's root directory
+// /:syw_reponame/refs/:ref/tree/*path for directories: gophermap list of contents
+// for files: raw files (guessed item type text/binary/image/etc)
+// /:syw_reponame/diffstat/:fromref/:toref text diffstat between two refs
+// /:syw_reponame/diff/:fromref/:toref text diff between two refs
+//
+// The overrides argument can provide templates to define the behavior of nearly all of the above routes.
+// All of them have default implementations, so the argument can be nil, but otherwise the template names
+// used are:
+// repo_root.gophermap gophermap at /
+// repo_home.gophermap gophermap at /:syw_reponame
+// branch_list.gophermap gophermap at /:syw_reponame/branches
+// tag_list.gophermap gophermap at /:syw_reponame/tags
+// ref.gophermap gophermap at /:syw_reponame/refs/:ref
+// tree.gophermap gophermap at direcotry paths under /:syw_reponame/refs/:ref/tree/*path
+// (file paths return the raw files without any template involved)
+// diffstat.gophertext plain text diffstat at /:syw_reponame/diffstat/:fromref/:toref
+// diff.gophertext plain text diff at /:syw_reponame/diff/:fromref/:toref
+//
+// Most of the templates above are rendered with an object with 6 fields:
+// Ctx: the context.Context from the request
+// Repo: a *syw.Repository corresponding to <repodir>/:syw_reponame
+// Params: the map[string]string of the route parameters
+// Host: the hostname of the running server
+// Port: the port number of the running server
+// Selector: the selector in the current request
+//
+// The only exception is repo_root.gophermap, which is instead rendered with a slice of the repo names.
+//
+// All templates have 3 additional functions made available to them:
+// combine: func(string, ...string) string - successively combines paths using url.URL.ResolveReference
+// join: func(string, ...string) string - successively joins path segments
+// rawtext: func(selector, host, port, text string) string renders text lines as gopher info-message lines.
func GopherRouter(repodir string, overrides *template.Template) *sliderule.Router {
tmpl, err := addTemplates(gopherTemplate, overrides)
if err != nil {
diff --git a/refs.go b/refs.go
index aec9c91..2b6d1f6 100644
--- a/refs.go
+++ b/refs.go
@@ -2,6 +2,7 @@ package syw
import "strings"
+// Ref is an object representing a commit in a repository.
type Ref struct {
Repo *Repository
Name string
@@ -11,6 +12,7 @@ type Ref struct {
func (r Ref) IsBranch() bool { return strings.HasPrefix(r.Name, "refs/heads/") }
func (r Ref) IsTag() bool { return strings.HasPrefix(r.Name, "refs/tags/") }
+// ShortName returns the branch or tag name with "refs/[heads|tags]/" trimmed off.
func (r Ref) ShortName() string {
if r.IsBranch() {
return r.Name[11:]
diff --git a/repo.go b/repo.go
index 0805913..8bb4a5d 100644
--- a/repo.go
+++ b/repo.go
@@ -14,8 +14,16 @@ import (
"tildegit.org/tjp/sliderule/logging"
)
+// Repository represents a git repository.
type Repository string
+// Open produces a git repository from a directory path.
+//
+// It will also try a few variations (dirpath.git, dirpath/.git) and use the first
+// path found to be a git repository.
+//
+// It returns nil if neither dirpath nor any of its variations are a valid git
+// repository.
func Open(dirpath string) *Repository {
check := []string{dirpath}
if !strings.HasSuffix(dirpath, ".git") {
@@ -38,6 +46,7 @@ func Open(dirpath string) *Repository {
return nil
}
+// Name is the repository name, defined by the directory path.
func (r *Repository) Name() string {
name := filepath.Base(string(*r))
if name == ".git" {
@@ -46,6 +55,7 @@ func (r *Repository) Name() string {
return strings.TrimSuffix(name, ".git")
}
+// NameBytes returns a byte slice of the repository name.
func (r *Repository) NameBytes() []byte {
return []byte(r.Name())
}
@@ -64,6 +74,7 @@ func (r *Repository) cmd(ctx context.Context, cmdname string, args ...string) (*
return result, err
}
+// Type returns the result of "git cat-file -t <hash>".
func (r *Repository) Type(ctx context.Context, hash string) (string, error) {
res, err := r.cmd(ctx, "cat-file", "-t", hash)
if err != nil {
@@ -76,6 +87,7 @@ func (r *Repository) Type(ctx context.Context, hash string) (string, error) {
return strings.Trim(res.out.String(), "\n"), nil
}
+// Refs returns a list of branch and tag references.
func (r *Repository) Refs(ctx context.Context) ([]Ref, error) {
res, err := r.cmd(ctx, "show-ref", "--head", "--heads", "--tags")
if err != nil {
@@ -101,6 +113,7 @@ func (r *Repository) Refs(ctx context.Context) ([]Ref, error) {
var badRevListOutput = errors.New("unexpected 'git rev-list' output")
+// Commits lists commits backwards from a given head.
func (r *Repository) Commits(ctx context.Context, head string, count int) ([]Commit, error) {
res, err := r.cmd(ctx, "rev-list",
"--format=%an%n%ae%n%aI%n%cn%n%ce%n%cI%n%P%n%B$$END$$",
@@ -176,6 +189,7 @@ func (r *Repository) Commits(ctx context.Context, head string, count int) ([]Com
return commits, nil
}
+// Commit gathers a single commit by a reference string.
func (r *Repository) Commit(ctx context.Context, ref string) (*Commit, error) {
commits, err := r.Commits(ctx, ref, 1)
if err != nil {
@@ -184,6 +198,7 @@ func (r *Repository) Commit(ctx context.Context, ref string) (*Commit, error) {
return &commits[0], nil
}
+// Diffstat produces a diffstat of two trees by their references.
func (r *Repository) Diffstat(ctx context.Context, fromref, toref string) (string, error) {
res, err := r.cmd(ctx, "diff-tree", "-r", "--stat", fromref, toref)
if err != nil {
@@ -195,6 +210,7 @@ func (r *Repository) Diffstat(ctx context.Context, fromref, toref string) (strin
return res.out.String(), nil
}
+// Diff produces a diff of two trees by their references.
func (r *Repository) Diff(ctx context.Context, fromref, toref string) (string, error) {
res, err := r.cmd(ctx, "diff-tree", "-r", "-p", "-u", fromref, toref)
if err != nil {
@@ -206,19 +222,27 @@ func (r *Repository) Diff(ctx context.Context, fromref, toref string) (string, e
return res.out.String(), nil
}
+// Readme represents a README file.
type Readme struct {
Filename string
RawContents string
}
+// GeminiEscapedContent produces the file contents with any ```-leading lines prefixed with a space.
func (r Readme) GeminiEscapedContents() string {
- return strings.ReplaceAll(r.RawContents, "\n```", "\n ```")
+ body := r.RawContents
+ if strings.HasPrefix(body, "```") {
+ body = " " + body
+ }
+ return strings.ReplaceAll(body, "\n```", "\n ```")
}
+// GopherEscapedContent produces the file formatted as gophermap with every line an info-message line.
func (r Readme) GopherEscapedContents(selector, host, port string) string {
return gopherRawtext(selector, host, port, r.RawContents)
}
+// Readme finds a README blob in the root path under a ref string.
func (r *Repository) Readme(ctx context.Context, ref string) (*Readme, error) {
dir, err := r.Tree(ctx, ref, "")
if err != nil {
@@ -248,6 +272,7 @@ func (r *Repository) Readme(ctx context.Context, ref string) (*Readme, error) {
return nil, nil
}
+// Description reads the "description" file from in the git repository.
func (r *Repository) Description() string {
f, err := os.Open(filepath.Join(string(*r), "description"))
if err != nil {
@@ -263,6 +288,7 @@ func (r *Repository) Description() string {
return strings.TrimRight(string(b), "\n")
}
+// Blob returns the contents of a blob at a given ref (commit) and path.
func (r *Repository) Blob(ctx context.Context, ref, path string) ([]byte, error) {
res, err := r.cmd(ctx, "cat-file", "blob", ref+":"+path)
switch {
@@ -277,6 +303,7 @@ func (r *Repository) Blob(ctx context.Context, ref, path string) ([]byte, error)
}
}
+// ObjectDescription represents an object within a git tree (directory).
type ObjectDescription struct {
Mode int
Type string
@@ -285,6 +312,7 @@ type ObjectDescription struct {
Path string
}
+// Tree lists the contents of a given directory (path) in a commit (ref).
func (r *Repository) Tree(ctx context.Context, ref, path string) ([]ObjectDescription, error) {
pattern := ref
if path != "" && path != "." {