xkcd/main.go
2023-11-27 16:13:40 +00:00

191 lines
3.5 KiB
Go

package main
import (
"bytes"
"embed"
"fmt"
"html/template"
"io/fs"
"log"
"os"
"path/filepath"
"sort"
"github.com/spf13/cobra"
)
const (
dirPermissions fs.FileMode = 0755
filePermissions fs.FileMode = 0666
)
var (
//go:embed assets/*
assets embed.FS
)
func init() {
cmd.Flags().Bool("empty", false, "empty output directory")
cmd.Flags().Bool("skip-html", false, "do not generate HTML files")
cmd.Flags().UintP("from", "f", 1, "first comic to download")
cmd.Flags().UintP("to", "t", 0, "last comic to download")
}
func main() {
if err := cmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
var cmd = &cobra.Command{
Use: "xkcd-archiver output",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
empty, _ := cmd.Flags().GetBool("empty")
skipHTML, _ := cmd.Flags().GetBool("skip-html")
from, _ := cmd.Flags().GetUint("from")
to, _ := cmd.Flags().GetUint("to")
out := args[0]
latest, err := getLatestID()
if err != nil {
return err
}
log.Printf("Latest comic is %d.\n", latest)
if from == 0 {
from = 1
}
if to == 0 {
to = uint(latest)
}
if empty {
err := os.RemoveAll(out)
if err != nil {
return err
}
}
err = os.MkdirAll(out, dirPermissions)
if err != nil {
return err
}
log.Printf("Downloading comics from %d to %d.\n", from, to)
comicTemplate, err := getTemplate("comic")
if err != nil {
return err
}
homeTemplate, err := getTemplate("home")
if err != nil {
return err
}
homeData := &homeData{}
for id := from; id <= to; id++ {
if id == 404 {
// Comic 404 does not exist.
continue
}
log.Printf("Downloading comic %d.\n", id)
comicDir := filepath.Join(out, fmt.Sprintf("%d", id))
metadata, err := getComic(comicDir, id)
if err != nil {
return err
}
data := &comicData{
Num: id,
Title: metadata.String("title"),
Alt: metadata.String("alt"),
Image: metadata.String("img"),
Latest: latest,
}
var b bytes.Buffer
err = comicTemplate.Execute(&b, data)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(comicDir, "index.html"), b.Bytes(), filePermissions)
if err != nil {
return err
}
homeData.Comics = append(homeData.Comics, data)
}
if !skipHTML {
sort.Slice(homeData.Comics, func(i, j int) bool {
return homeData.Comics[i].Num > homeData.Comics[j].Num
})
var b bytes.Buffer
err = homeTemplate.Execute(&b, homeData)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), filePermissions)
if err != nil {
return err
}
err = copyAsset(out, "styles.css")
if err != nil {
return err
}
err = copyAsset(out, "favicon.ico")
if err != nil {
return err
}
}
log.Println("Comics fetched.")
return nil
},
}
type comicData struct {
Latest uint
Num uint
Title string
Alt string
Image string
}
type homeData struct {
Comics []*comicData
}
func getTemplate(name string) (*template.Template, error) {
comicBytes, err := assets.ReadFile("assets/" + name + ".html")
if err != nil {
return nil, err
}
return template.New("").Funcs(template.FuncMap{
"minus": func(a, b uint) uint {
return a - b
},
"plus": func(a, b uint) uint {
return a + b
},
}).Parse(string(comicBytes))
}
func copyAsset(out, filename string) error {
data, err := assets.ReadFile("assets/" + filename)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(out, filename), data, filePermissions)
}