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}