153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
"time"
|
||
|
|
||
|
"gitlab.com/golang-commonmark/markdown"
|
||
|
"gopkg.in/src-d/go-git.v4"
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||
|
"gopkg.in/src-d/go-git.v4/plumbing/object"
|
||
|
)
|
||
|
|
||
|
type handler struct {
|
||
|
repo *git.Repository
|
||
|
branch plumbing.ReferenceName
|
||
|
tpl *template.Template
|
||
|
}
|
||
|
|
||
|
func (h handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||
|
path := r.URL.Path
|
||
|
var mdPath string
|
||
|
if strings.HasSuffix(path, "/") {
|
||
|
mdPath = filepath.Join(path, "README.md") // do before modifying path
|
||
|
path = filepath.Join(path, "index.html")
|
||
|
|
||
|
} else if strings.HasSuffix(path, "/index.html") {
|
||
|
mdPath = filepath.Join(filepath.Dir(path), "README.md")
|
||
|
|
||
|
} else if filepath.Ext(path) == ".html" {
|
||
|
mdPath = strings.TrimSuffix(path, ".html") + ".md"
|
||
|
}
|
||
|
|
||
|
path = strings.TrimPrefix(path, "/")
|
||
|
mdPath = strings.TrimPrefix(mdPath, "/")
|
||
|
|
||
|
ref, err := h.repo.Reference(h.branch, true)
|
||
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||
|
http.Error(rw, "branch does not exist", 404)
|
||
|
return
|
||
|
} else if err != nil {
|
||
|
log.Printf("resolving reference %q: %v", h.branch, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
hash := ref.Hash()
|
||
|
commit, err := h.repo.CommitObject(hash)
|
||
|
if err != nil {
|
||
|
log.Printf("fetching commit %q: %v", hash, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
tree, err := h.repo.TreeObject(commit.TreeHash)
|
||
|
if err != nil {
|
||
|
log.Printf("fetching tree %q of commit %q: %v", commit.TreeHash, hash, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var usingMD bool
|
||
|
f, err := tree.File(path)
|
||
|
if errors.Is(err, object.ErrFileNotFound) {
|
||
|
usingMD = true
|
||
|
f, err = tree.File(mdPath)
|
||
|
}
|
||
|
|
||
|
if errors.Is(err, object.ErrFileNotFound) {
|
||
|
http.Error(rw, fmt.Sprintf("%q not found", path), 404)
|
||
|
return
|
||
|
} else if err != nil {
|
||
|
log.Printf("fetching file %q / %q: %v", path, mdPath, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
fr, err := f.Blob.Reader()
|
||
|
if err != nil {
|
||
|
log.Printf("getting reader of file %q: %v", f.Name, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
defer fr.Close()
|
||
|
|
||
|
b, err := ioutil.ReadAll(fr)
|
||
|
if err != nil {
|
||
|
log.Printf("reading in contents of file %q: %v", f.Name, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !usingMD {
|
||
|
http.ServeContent(rw, r, filepath.Base(path), time.Now(), bytes.NewReader(b))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
mdBuf := new(bytes.Buffer)
|
||
|
if err := markdown.New().Render(mdBuf, b); err != nil {
|
||
|
log.Printf("rendering file %q to markdown: %v", f.Name, err)
|
||
|
http.Error(rw, "internal error", 500)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if h.tpl == nil {
|
||
|
http.ServeContent(rw, r, filepath.Base(path), time.Now(), bytes.NewReader(mdBuf.Bytes()))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
h.tpl.Execute(rw, struct {
|
||
|
Body string
|
||
|
}{mdBuf.String()})
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
addr := flag.String("addr", ":8000", "Address to listen for http requests on")
|
||
|
branchName := flag.String("branch", "master", "git branch to serve the HEAD of")
|
||
|
repoPath := flag.String("repo-path", ".", "Path to the git repository to server")
|
||
|
tplPath := flag.String("tpl-path", "", "Path to an optional template file which can be used when rendering markdown")
|
||
|
flag.Parse()
|
||
|
|
||
|
repo, err := git.PlainOpen(*repoPath)
|
||
|
if err != nil {
|
||
|
log.Fatalf("opening git repo at path %q: %v", *repoPath, err)
|
||
|
}
|
||
|
branch := plumbing.NewBranchReferenceName(*branchName)
|
||
|
|
||
|
// do an initial check for the branch, for funsies
|
||
|
if _, err := repo.Reference(branch, true); err != nil {
|
||
|
log.Fatalf("resolving reference %q: %v", branch, err)
|
||
|
}
|
||
|
|
||
|
h := &handler{
|
||
|
repo: repo,
|
||
|
branch: branch,
|
||
|
}
|
||
|
|
||
|
if *tplPath != "" {
|
||
|
h.tpl = template.Must(template.ParseFiles(*tplPath))
|
||
|
}
|
||
|
|
||
|
log.Printf("listening on %q", *addr)
|
||
|
http.ListenAndServe(*addr, h)
|
||
|
}
|