187 lines
3.8 KiB
Go
187 lines
3.8 KiB
Go
package module
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"aego/engine/res"
|
|
)
|
|
|
|
type IconSet struct {
|
|
Owner string
|
|
FS fs.FS
|
|
Prefix string
|
|
IDs []string
|
|
}
|
|
|
|
func iconID(name string) (string, bool) {
|
|
base, ok := strings.CutSuffix(name, ".png")
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
id, variant, ok := strings.Cut(base, "@")
|
|
if !ok || id == "" {
|
|
return "", false
|
|
}
|
|
size, scale, ok := strings.Cut(variant, "x")
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
if _, err := strconv.Atoi(size); err != nil {
|
|
return "", false
|
|
}
|
|
if _, err := strconv.Atoi(scale); err != nil {
|
|
return "", false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func builtinIcons() map[string]bool {
|
|
out := map[string]bool{}
|
|
data, err := fs.ReadFile(res.FS(), "icons/icons.json")
|
|
if err != nil {
|
|
return out
|
|
}
|
|
var f struct {
|
|
Atlases []struct {
|
|
Icons []struct {
|
|
ID string `json:"id"`
|
|
} `json:"icons"`
|
|
} `json:"atlases"`
|
|
}
|
|
if json.Unmarshal(data, &f) != nil {
|
|
return out
|
|
}
|
|
for _, a := range f.Atlases {
|
|
for _, ic := range a.Icons {
|
|
out[ic.ID] = true
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
type IconProblemKind uint8
|
|
|
|
const (
|
|
IconMissing IconProblemKind = iota
|
|
IconUnused
|
|
IconBadName
|
|
IconEmptySource
|
|
)
|
|
|
|
type IconProblem struct {
|
|
Kind IconProblemKind
|
|
Owner string
|
|
Icon string
|
|
By string
|
|
}
|
|
|
|
func (p IconProblem) String() string {
|
|
switch p.Kind {
|
|
case IconMissing:
|
|
return p.By + " uses icon " + p.Icon + ", which no icon source provides"
|
|
case IconUnused:
|
|
return "icon " + p.Icon + " is not used by any component or template"
|
|
case IconBadName:
|
|
return "icon file " + p.Icon + " is not named <id>@<size>x<scale>.png"
|
|
}
|
|
return "icon source with prefix " + p.Icon + " has no <id>@<size>x<scale>.png files"
|
|
}
|
|
|
|
type IconReport struct {
|
|
Problems []IconProblem
|
|
used map[string]bool
|
|
seen map[string]bool
|
|
}
|
|
|
|
func (r *IconReport) Write(w io.Writer) int {
|
|
for _, p := range r.Problems {
|
|
fmt.Fprintln(w, p.Owner+": "+p.String())
|
|
}
|
|
if len(r.Problems) > 0 {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (r *Registry) iconIssue(p IconProblem) {
|
|
switch {
|
|
case r.audit != nil:
|
|
key := strconv.Itoa(int(p.Kind)) + "|" + p.Owner + "|" + p.Icon
|
|
if !r.audit.seen[key] {
|
|
r.audit.seen[key] = true
|
|
r.audit.Problems = append(r.audit.Problems, p)
|
|
}
|
|
case p.Kind == IconBadName:
|
|
r.Warn(p.Owner, p.String())
|
|
default:
|
|
r.report(p.Owner, p.String())
|
|
}
|
|
}
|
|
|
|
func (r *Registry) useIcon(known map[string]bool, owner, icon, by string) {
|
|
if icon == "" {
|
|
return
|
|
}
|
|
if r.audit != nil {
|
|
r.audit.used[icon] = true
|
|
}
|
|
if !known[icon] {
|
|
r.iconIssue(IconProblem{Kind: IconMissing, Owner: owner, Icon: icon, By: by})
|
|
}
|
|
}
|
|
|
|
func (r *Registry) scanIcons() map[string]bool {
|
|
known := builtinIcons()
|
|
owners := map[string]string{}
|
|
for id := range known {
|
|
owners[id] = "engine"
|
|
}
|
|
for i := range r.icons {
|
|
set := &r.icons[i]
|
|
seen := map[string]bool{}
|
|
err := fs.WalkDir(set.FS, ".", func(p string, d fs.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
id, ok := iconID(path.Base(p))
|
|
if !ok {
|
|
if strings.HasSuffix(p, ".png") {
|
|
r.iconIssue(IconProblem{Kind: IconBadName, Owner: set.Owner, Icon: p})
|
|
}
|
|
return nil
|
|
}
|
|
full := set.Prefix + id
|
|
if seen[full] {
|
|
return nil
|
|
}
|
|
seen[full] = true
|
|
if !strings.HasPrefix(full, set.Owner+".") && full != "cat."+set.Owner {
|
|
r.report(set.Owner, "icon "+full+" must be named "+set.Owner+".<name> or cat."+set.Owner)
|
|
return nil
|
|
}
|
|
if other, dup := owners[full]; dup {
|
|
r.report(set.Owner, "icon "+full+" is already provided by "+other)
|
|
return nil
|
|
}
|
|
owners[full] = set.Owner
|
|
set.IDs = append(set.IDs, full)
|
|
known[full] = true
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
r.report(set.Owner, "icon source cannot be read: "+err.Error())
|
|
continue
|
|
}
|
|
if len(set.IDs) == 0 {
|
|
r.iconIssue(IconProblem{Kind: IconEmptySource, Owner: set.Owner, Icon: set.Prefix})
|
|
}
|
|
}
|
|
return known
|
|
}
|