package main import ( "bytes" "embed" "flag" "fmt" "html/template" "log/slog" "os" "path/filepath" "strings" "time" "github.com/alecthomas/kong" "github.com/go-git/go-git/v5" ) type RepoDir struct { Name string Description string Owner string LastCommit time.Time } //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 } func errAndExit(msg string, args ...any) { fmt.Fprintf(os.Stderr, msg, args...) os.Exit(1) } func (i *indexCmd) Run() error { slog.Debug("Generating index", "args", flag.Args()) return generateIndex(i.Paths) } func (r *repoCmd) Run() error { slog.Debug("Generating repository", "path", r.Path) return generateRepo(r.Path) } func main() { ctx := kong.Parse(&cli) if cli.Debug { slog.SetLogLoggerLevel(slog.LevelDebug) } if err := ctx.Run(); err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(1) } } 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) } 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) } return nil } func generateRepo(path string) error { return nil }