2023-11-19 18:00:00 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
2023-11-19 18:00:00 +01:00
|
|
|
"strings"
|
2023-11-19 18:00:00 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ctx = context.Background()
|
|
|
|
previewPort = "8080"
|
|
|
|
)
|
|
|
|
|
|
|
|
// genListingPages generates listing pages recursively inside provided directory
|
2023-11-19 18:00:00 +01:00
|
|
|
func genListingPages(path, prefix string) error {
|
2023-11-19 18:00:00 +01:00
|
|
|
entries, err := os.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
indexFile, err := os.Create(filepath.Join(path, "index.html"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-11-19 18:00:00 +01:00
|
|
|
|
|
|
|
trimedPath := strings.TrimPrefix(path, prefix)
|
|
|
|
if err := listingPage(strings.TrimPrefix(trimedPath, "/"), entries).Render(ctx, indexFile); err != nil {
|
2023-11-19 18:00:00 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Goroutines are cheap, so just spawn them needlessly \^-^/
|
|
|
|
errGroup := new(errgroup.Group)
|
|
|
|
for _, entry := range entries {
|
|
|
|
entry := entry
|
|
|
|
if entry.IsDir() {
|
|
|
|
errGroup.Go(func() error {
|
2023-11-19 18:00:00 +01:00
|
|
|
return genListingPages(filepath.Join(path, entry.Name()), prefix)
|
2023-11-19 18:00:00 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errGroup.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// preview Previews the website at specified root directory on localhost:<port>
|
|
|
|
func preview(root, port string) error {
|
2023-11-19 18:00:00 +01:00
|
|
|
http.Handle("/", http.FileServer(http.Dir(root)))
|
2023-11-19 18:00:00 +01:00
|
|
|
fmt.Println("Serving the preview webpage at http://localhost:" + port)
|
|
|
|
srv := &http.Server{
|
|
|
|
Addr: ":" + port,
|
|
|
|
ReadTimeout: 5 * time.Second,
|
|
|
|
WriteTimeout: 10 * time.Second,
|
|
|
|
}
|
|
|
|
return srv.ListenAndServe()
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate generates index.html files for the root directory
|
|
|
|
func generate(root string) error {
|
|
|
|
// NOTE: it also generate one for the root directory. We'll override this file later
|
2023-11-19 18:00:00 +01:00
|
|
|
if err := genListingPages(root, root); err != nil {
|
2023-11-19 18:00:00 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the home page to override the written listing page at root
|
|
|
|
homePage, err := os.Create(filepath.Join(root, "index.html"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return indexPage().Render(ctx, homePage)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
genFlag := flag.Bool("gen", false, "Generate index files")
|
|
|
|
previewFlag := flag.Bool("preview", false, "Preview the built website")
|
|
|
|
flag.Usage = func() {
|
|
|
|
fmt.Printf(`Usage: go run *.go [OPTIONS] DIR
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
DIR
|
|
|
|
Path to the directory containing the source files
|
|
|
|
|
|
|
|
Options:
|
|
|
|
`)
|
|
|
|
flag.PrintDefaults()
|
|
|
|
}
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
// The root directory argument is required
|
|
|
|
if len(flag.Args()) != 1 {
|
|
|
|
log.Fatal("[ERROR] exactly 1 argument DIR is required")
|
|
|
|
}
|
2023-11-19 18:00:00 +01:00
|
|
|
|
|
|
|
// Specify the starting directory, for consistency
|
|
|
|
_, f, _, _ := runtime.Caller(0)
|
|
|
|
rootDir := filepath.Join(filepath.Dir(f), flag.Arg(0))
|
2023-11-19 18:00:00 +01:00
|
|
|
|
|
|
|
if *genFlag {
|
|
|
|
if err := generate(rootDir); err != nil {
|
|
|
|
log.Fatalf("[ERROR] %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only preview after things are generated
|
|
|
|
if *previewFlag {
|
|
|
|
if err := preview(rootDir, previewPort); err != nil {
|
|
|
|
log.Fatalf("[ERROR] %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|