Files
dnd/cmd/dndimport/main.go

238 lines
7.1 KiB
Go
Raw Normal View History

2023-05-14 15:49:04 -07:00
package main
import (
"archive/zip"
"flag"
"io"
"os"
"path/filepath"
"strings"
2023-05-14 17:11:49 -07:00
"unicode"
2023-05-14 15:49:04 -07:00
2023-05-14 16:35:29 -07:00
"github.com/fatih/camelcase"
2023-05-14 15:49:04 -07:00
"github.com/firestuff/dnd/internal"
"github.com/samber/lo"
"golang.org/x/exp/slog"
)
var removeWords = map[string]bool{
2023-05-14 16:35:29 -07:00
"+": true,
2023-05-14 17:11:49 -07:00
"-": true,
"-diamond": true,
2023-05-14 16:21:40 -07:00
"1": true,
"2": true,
"diamond": true,
"gridless": true,
"high": true,
2023-05-14 17:11:49 -07:00
"high-res": true,
"part": true,
2023-05-14 16:21:40 -07:00
"psd": true,
"res": true,
2023-05-14 16:35:29 -07:00
"roll20": true,
2023-05-14 16:21:40 -07:00
"roll20+tokens": true,
2023-05-14 18:01:39 -07:00
"support": true,
"tier": true,
2023-05-14 16:35:29 -07:00
"tokens": true,
2023-05-14 15:49:04 -07:00
}
var actions = map[string]string{
2023-05-14 18:01:39 -07:00
"2nd floors/*.png": "Maps/{MAPNAME}/Objects",
"assets & tiles/*.png": "Maps/{MAPNAME}/Objects",
"assets & tiles/rooftops/*.png": "Maps/{MAPNAME}/Objects",
"assets & tokens/*.png": "Maps/{MAPNAME}/Objects",
"assets & tokens/rooftops/*.png": "Maps/{MAPNAME}/Objects",
"assets & tokens/rooftop_tiles/*.png": "Maps/{MAPNAME}/Objects",
"assets & tokens/tiles or tokens/*.png": "Maps/{MAPNAME}/Objects",
"creature tokens/*.png": "Creatures",
"grid/*.jpg": "{SKIP}",
"grid/grid 1/*.jpg": "{SKIP}",
"grid/grid 2/*.jpg": "{SKIP}",
"gridded/*.dd2vtt": "{SKIP}",
"gridded/*.jpg": "{SKIP}",
"gridded/*.webp": "{SKIP}",
"gridless/*.jpg": "Maps/{MAPNAME}",
"gridless/extra color/*.jpg": "Maps/{MAPNAME}",
"gridless/line/*.jpg": "Maps/{MAPNAME}",
"gridless/normal/*.jpg": "Maps/{MAPNAME}",
"line/*.jpg": "Maps/{MAPNAME}",
"map tokens/*.png": "Maps/{MAPNAME}/Objects",
"sloop vessel/*.png": "Maps/{MAPNAME}/Objects",
"tokens/*.png": "Maps/{MAPNAME}/Objects",
"ungridded/*.dd2vtt": "{SKIP}",
"ungridded/*.jpg": "Maps/{MAPNAME}",
"ungridded/*.webp": "Maps/{MAPNAME}",
"./*.jpg": "Maps/{MAPNAME}",
"./*.psd": "Maps/{MAPNAME}",
"./*.pdf": "{SKIP}",
"*/high resolution/*.jpg": "Maps/{MAPNAME}", // Trust that it's gridless
"*/high resolution/*.png": "Maps/{MAPNAME}", // Trust that it's gridless
"*/gridless/*.jpg": "Maps/{MAPNAME}",
"*/high-res/gridless/*.jpg": "Maps/{MAPNAME}",
"*/high-res/gridless/*.png": "Maps/{MAPNAME}",
"*/high res/gridless/*.jpg": "Maps/{MAPNAME}",
"*/high res/gridless/*.png": "Maps/{MAPNAME}",
"*/high res/gridless/attic/*.jpg": "Maps/{MAPNAME}",
"*/high res/gridless/attic/*.png": "Maps/{MAPNAME}",
"*/high res/gridless/basement/*.jpg": "Maps/{MAPNAME}",
"*/high res/gridless/basement/*.png": "Maps/{MAPNAME}",
"*/high res/gridless/floor 1/*.jpg": "Maps/{MAPNAME}",
"*/high res/gridless/floor 1/*.png": "Maps/{MAPNAME}",
"*/high res/gridless/floor 2/*.jpg": "Maps/{MAPNAME}",
"*/high res/gridless/floor 2/*.png": "Maps/{MAPNAME}",
"*/high resolution/gridless/*.jpg": "Maps/{MAPNAME}",
"*/grid/*.jpg": "{SKIP}",
"*/gridded/*.jpg": "{SKIP}",
"*/high-res/grid/*.jpg": "{SKIP}",
"*/high res/grid/*.jpg": "{SKIP}",
"*/high resolution/grid/*.jpg": "{SKIP}",
"*/creature tokens/*.png": "Creatures",
"*/creature tokens/variants/*.png": "Creatures",
"*/map tokens/*.png": "Maps/{MAPNAME}/Objects",
"*/tokens/*.png": "Maps/{MAPNAME}/Objects",
"*/roll20/*.jpg": "{SKIP}",
"*/roll20/*.png": "{SKIP}",
"*/roll20/grid/*.jpg": "{SKIP}",
"*/roll20/gridded/*.jpg": "{SKIP}",
"*/roll20/grid/*.png": "{SKIP}",
"*/roll20/grid/attic/*.jpg": "{SKIP}",
"*/roll20/grid/attic/*.png": "{SKIP}",
"*/roll20/grid/basement/*.jpg": "{SKIP}",
"*/roll20/grid/basement/*.png": "{SKIP}",
"*/roll20/grid/floor 1/*.jpg": "{SKIP}",
"*/roll20/grid/floor 1/*.png": "{SKIP}",
"*/roll20/grid/floor 2/*.jpg": "{SKIP}",
"*/roll20/grid/floor 2/*.png": "{SKIP}",
"*/roll20/gridless/*.jpg": "{SKIP}",
"*/roll20/gridless/*.png": "{SKIP}",
"*/roll20/gridless/attic/*.jpg": "{SKIP}",
"*/roll20/gridless/attic/*.png": "{SKIP}",
"*/roll20/gridless/basement/*.jpg": "{SKIP}",
"*/roll20/gridless/basement/*.png": "{SKIP}",
"*/roll20/gridless/floor 1/*.jpg": "{SKIP}",
"*/roll20/gridless/floor 1/*.png": "{SKIP}",
"*/roll20/gridless/floor 2/*.jpg": "{SKIP}",
"*/roll20/gridless/floor 2/*.png": "{SKIP}",
2023-05-14 15:49:04 -07:00
}
var root = flag.String("root", "", "root directory to write to")
func main() {
l := slog.New(slog.NewTextHandler(os.Stderr, nil))
flag.Parse()
if *root == "" {
l.Error("please specify --root")
os.Exit(1)
}
for _, path := range flag.Args() {
zipL := l.With("zipPath", path)
s := lo.Must(os.Stat(path))
if s.Size() == 0 {
zipL.Warn("SKIP")
continue
}
ok := importZIP(zipL, path)
if !ok {
os.Exit(1)
}
lo.Must0(os.Remove(path))
}
}
func importZIP(l *slog.Logger, path string) bool {
name := mapName(path)
l = l.With("mapName", name)
z := lo.Must(zip.OpenReader(path))
defer z.Close()
for _, file := range z.File {
if strings.HasSuffix(file.Name, "/") {
continue
}
2023-05-14 16:21:40 -07:00
sig := strings.ToLower(internal.PathSig(file.Name))
2023-05-14 15:49:04 -07:00
dst := actions[sig]
if dst == "" {
parts := strings.Split(sig, "/")
dst = actions["*/"+filepath.Join(parts[1:]...)]
}
if dst == "" {
l.Error("unknown file signature",
"filePath", file.Name,
"signature", sig,
)
return false
}
if dst == "{SKIP}" {
l.Info("SKIP",
"src", file.Name,
)
continue
}
dst = strings.ReplaceAll(dst, "{MAPNAME}", name)
2023-05-14 17:11:49 -07:00
dst = printable(filepath.Join(*root, dst, filepath.Base(file.Name)))
2023-05-14 15:49:04 -07:00
l.Info("COPY",
"src", file.Name,
"dst", dst,
)
lo.Must0(os.MkdirAll(filepath.Dir(dst), 0755))
dstFile := lo.Must(os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644))
srcFile := lo.Must(file.Open())
lo.Must(io.Copy(dstFile, srcFile))
dstFile.Close()
srcFile.Close()
}
return true
}
func mapName(path string) string {
withoutZIP := strings.TrimSuffix(filepath.Base(path), ".zip")
parts := strings.Split(withoutZIP, " ")
2023-05-14 16:35:29 -07:00
if len(parts) == 1 {
parts = camelcase.Split(parts[0])
}
2023-05-14 15:49:04 -07:00
i := len(parts) - 1
2023-05-14 16:21:40 -07:00
for i >= 0 && removeWords[strings.ToLower(parts[i])] {
2023-05-14 15:49:04 -07:00
i--
}
2023-05-14 18:01:39 -07:00
parts = parts[:i+1]
for len(parts) > 0 && removeWords[strings.ToLower(parts[0])] {
parts = parts[1:]
}
2023-05-14 17:11:49 -07:00
if len(parts) == 0 {
panic(path)
}
2023-05-14 18:01:39 -07:00
return strings.Join(parts, " ")
2023-05-14 15:49:04 -07:00
}
2023-05-14 17:11:49 -07:00
func printable(in string) string {
return strings.Map(func(r rune) rune {
if unicode.IsPrint(r) {
return r
}
return -1
}, in)
}