2024-06-28 18:28:42 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-06-28 23:03:06 +01:00
|
|
|
"bytes"
|
|
|
|
"embed"
|
2024-06-28 18:28:42 +01:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2024-06-28 23:03:06 +01:00
|
|
|
"html/template"
|
2024-06-28 18:28:42 +01:00
|
|
|
"log/slog"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
|
|
)
|
|
|
|
|
|
|
|
type RepoDir struct {
|
|
|
|
Name string
|
|
|
|
Description string
|
|
|
|
Owner string
|
|
|
|
LastCommit time.Time
|
|
|
|
}
|
|
|
|
|
2024-06-28 23:03:06 +01:00
|
|
|
//go:embed templates
|
|
|
|
var embedTmpl embed.FS
|
|
|
|
|
|
|
|
func executeTemplate(name string, data any) (string, error) {
|
|
|
|
path := filepath.Join("templates", fmt.Sprintf("%s.html.tmpl", name))
|
|
|
|
b, err := embedTmpl.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("cannot read embedded file %q: %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpl, err := template.New("").Parse(string(b))
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("cannot parse template %q: %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var strb bytes.Buffer
|
|
|
|
if err := tmpl.Execute(&strb, data); err != nil {
|
|
|
|
return "", fmt.Errorf("cannot execute template %q: %w", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strb.String(), nil
|
|
|
|
}
|
|
|
|
|
2024-06-28 18:28:42 +01:00
|
|
|
func errAndExit(msg string, args ...any) {
|
|
|
|
fmt.Fprintf(os.Stderr, msg, args...)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
indexFlag := flag.Bool("index", false, "set to true if you want to generate the index")
|
|
|
|
repoFlag := flag.String("repo", "", "the repository path to generate the static files from")
|
|
|
|
debugFlag := flag.Bool("debug", false, "print debug information")
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
if *debugFlag {
|
|
|
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case *indexFlag:
|
|
|
|
if len(flag.Args()) == 0 {
|
|
|
|
errAndExit("Usage of -index:\n %s -index [repositories]\n", os.Args[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
slog.Debug("Generating index", "args", flag.Args())
|
|
|
|
|
|
|
|
if err := generateIndex(flag.Args()); err != nil {
|
|
|
|
errAndExit("ERROR: %s\n", err)
|
|
|
|
}
|
|
|
|
case *repoFlag != "":
|
|
|
|
slog.Debug("Generating repository", "path", *repoFlag)
|
|
|
|
|
|
|
|
if err := generateRepo(*repoFlag); err != nil {
|
|
|
|
errAndExit("ERROR: %s\n", err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
flag.Usage()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func readFile(path string) (string, error) {
|
|
|
|
if fi, err := os.Stat(path); err == nil && !fi.IsDir() {
|
|
|
|
b, err := os.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("cannot read contents of file %q: %w", path, err)
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(b)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readRepoFile(dirname, name string) (contents string, err error) {
|
|
|
|
contents, err = readFile(filepath.Join(dirname, ".git", name))
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if contents == "" {
|
|
|
|
contents, err = readFile(filepath.Join(dirname, name))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateIndex(args []string) error {
|
|
|
|
repoDirs := []*RepoDir{}
|
|
|
|
for _, dirname := range args {
|
|
|
|
slog.Debug("Processing directory", "dirname", dirname)
|
|
|
|
reponame := strings.TrimSuffix(filepath.Base(dirname), ".git")
|
|
|
|
repoDir := &RepoDir{Name: reponame}
|
|
|
|
|
|
|
|
description, err := readRepoFile(dirname, "description")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot read description for repository %q: %w", dirname, err)
|
|
|
|
}
|
|
|
|
repoDir.Description = description
|
|
|
|
|
|
|
|
owner, err := readRepoFile(dirname, "owner")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot read owner for repository %q: %w", dirname, err)
|
|
|
|
}
|
|
|
|
repoDir.Owner = owner
|
|
|
|
|
|
|
|
// get last commit date
|
|
|
|
repo, err := git.PlainOpen(dirname)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot open repository %q: %w", dirname, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
head, err := repo.Head()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get repository head for %q: %w", dirname, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := repo.CommitObject(head.Hash())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get commit %q for repository %q: %w", head.Hash(), dirname, err)
|
|
|
|
}
|
|
|
|
repoDir.LastCommit = c.Author.When
|
|
|
|
|
|
|
|
repoDirs = append(repoDirs, repoDir)
|
|
|
|
}
|
|
|
|
|
2024-06-28 23:03:06 +01:00
|
|
|
data := map[string]any{
|
|
|
|
"repoDirs": repoDirs,
|
|
|
|
}
|
|
|
|
contents, err := executeTemplate("index", data)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot execute index template: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.WriteFile("index.html", []byte(contents), 0755); err != nil {
|
|
|
|
return fmt.Errorf("cannot write index contents to \"index.html\": %w", err)
|
2024-06-28 18:28:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateRepo(path string) error {
|
|
|
|
return nil
|
|
|
|
}
|