repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / apps / pgs
Eric Bower  ·  2025-12-15

gen_dir_listing.go

  1package pgs
  2
  3import (
  4	"bytes"
  5	"embed"
  6	"fmt"
  7	"html/template"
  8	"os"
  9	"sort"
 10	"strings"
 11
 12	sst "github.com/picosh/pico/pkg/pobj/storage"
 13)
 14
 15//go:embed html/*
 16var dirListingFS embed.FS
 17
 18var dirListingTmpl = template.Must(
 19	template.New("base").ParseFS(
 20		dirListingFS,
 21		"html/base.layout.tmpl",
 22		"html/marketing-footer.partial.tmpl",
 23		"html/directory_listing.page.tmpl",
 24	),
 25)
 26
 27type dirEntryDisplay struct {
 28	Href    string
 29	Display string
 30	Size    string
 31	ModTime string
 32}
 33
 34type DirectoryListingData struct {
 35	Path       string
 36	ShowParent bool
 37	Entries    []dirEntryDisplay
 38}
 39
 40func formatFileSize(size int64) string {
 41	const (
 42		KB = 1024
 43		MB = KB * 1024
 44		GB = MB * 1024
 45	)
 46
 47	switch {
 48	case size >= GB:
 49		return fmt.Sprintf("%.1f GB", float64(size)/float64(GB))
 50	case size >= MB:
 51		return fmt.Sprintf("%.1f MB", float64(size)/float64(MB))
 52	case size >= KB:
 53		return fmt.Sprintf("%.1f KB", float64(size)/float64(KB))
 54	default:
 55		return fmt.Sprintf("%d B", size)
 56	}
 57}
 58
 59func sortEntries(entries []os.FileInfo) {
 60	sort.Slice(entries, func(i, j int) bool {
 61		if entries[i].IsDir() != entries[j].IsDir() {
 62			return entries[i].IsDir()
 63		}
 64		return entries[i].Name() < entries[j].Name()
 65	})
 66}
 67
 68func toDisplayEntries(entries []os.FileInfo) []dirEntryDisplay {
 69	sortEntries(entries)
 70	displayEntries := make([]dirEntryDisplay, 0, len(entries))
 71
 72	for _, entry := range entries {
 73		if strings.HasPrefix(entry.Name(), ".") {
 74			continue
 75		}
 76		display := dirEntryDisplay{
 77			Href:    entry.Name(),
 78			Display: entry.Name(),
 79			Size:    formatFileSize(entry.Size()),
 80			ModTime: entry.ModTime().Format("2006-01-02 15:04"),
 81		}
 82
 83		if entry.IsDir() {
 84			display.Href += "/"
 85			display.Display += "/"
 86			display.Size = "-"
 87		}
 88
 89		displayEntries = append(displayEntries, display)
 90	}
 91
 92	return displayEntries
 93}
 94
 95func shouldGenerateListing(st sst.ObjectStorage, bucket sst.Bucket, projectDir string, path string) bool {
 96	dirPath := projectDir + path
 97	if path == "/" {
 98		dirPath = projectDir + "/"
 99	}
100
101	entries, err := st.ListObjects(bucket, dirPath, false)
102	if err != nil || len(entries) == 0 {
103		return false
104	}
105
106	indexPath := dirPath + "index.html"
107	obj, _, err := st.GetObject(bucket, indexPath)
108	if err != nil {
109		return true
110	}
111	_ = obj.Close()
112	return false
113}
114
115func generateDirectoryHTML(path string, entries []os.FileInfo) string {
116	data := DirectoryListingData{
117		Path:       path,
118		ShowParent: path != "/",
119		Entries:    toDisplayEntries(entries),
120	}
121
122	var buf bytes.Buffer
123	if err := dirListingTmpl.Execute(&buf, data); err != nil {
124		return fmt.Sprintf("Error rendering directory listing: %s", err)
125	}
126
127	return buf.String()
128}