Go documentation for all public functions

This commit is contained in:
tjpcc 2023-09-22 12:39:00 -06:00
parent 1a1832b77d
commit 6cfa6622f3
5 changed files with 111 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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

30
repo.go
View File

@ -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 != "." {