icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

check.go (2905B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"io"
      7 	"os"
      8 	"path/filepath"
      9 
     10 	"github.com/jackmordaunt/icns/v4"
     11 	"github.com/jackmordaunt/icns/v4/exe"
     12 	"github.com/jackmordaunt/icns/v4/ico"
     13 )
     14 
     15 // check reports what the platform that owns the format will make of an icon
     16 // file. Findings are printed in order of severity, and the error reports the
     17 // ones that change what is drawn.
     18 func check(path string, r io.Reader) error {
     19 	data, err := io.ReadAll(r)
     20 	if err != nil {
     21 		return fmt.Errorf("reading %s: %w", name(path), err)
     22 	}
     23 	var lines []string
     24 	serious := 0
     25 	switch container(data, extension(filepath.Ext(path))) {
     26 	case ".icns":
     27 		problems, err := icns.Validate(bytes.NewReader(data))
     28 		if err != nil {
     29 			return fmt.Errorf("reading %s: %w", name(path), err)
     30 		}
     31 		for _, p := range problems {
     32 			lines = append(lines, p.String())
     33 			if p.Severity != icns.Advice {
     34 				serious++
     35 			}
     36 		}
     37 	case ".ico":
     38 		problems, err := ico.Validate(bytes.NewReader(data))
     39 		if err != nil {
     40 			return fmt.Errorf("reading %s: %w", name(path), err)
     41 		}
     42 		for _, p := range problems {
     43 			lines = append(lines, p.String())
     44 			if p.Severity != ico.Advice {
     45 				serious++
     46 			}
     47 		}
     48 	case ".exe", ".dll":
     49 		// The icons Explorer draws for a shipped binary are the ones worth
     50 		// checking, and they are only reachable through its resources.
     51 		groups, err := exe.Icons(bytes.NewReader(data))
     52 		if err != nil {
     53 			return fmt.Errorf("reading %s: %w", name(path), err)
     54 		}
     55 		for _, group := range groups {
     56 			problems, err := ico.Validate(bytes.NewReader(group.ICO()))
     57 			if err != nil {
     58 				return fmt.Errorf("reading %s: %s: %w", name(path), group, err)
     59 			}
     60 			for _, p := range problems {
     61 				lines = append(lines, fmt.Sprintf("%s: %s", group, p))
     62 				if p.Severity != ico.Advice {
     63 					serious++
     64 				}
     65 			}
     66 		}
     67 	default:
     68 		return fmt.Errorf("%s is not an icns, ico or Windows binary", name(path))
     69 	}
     70 	for _, line := range lines {
     71 		fmt.Fprintf(os.Stdout, "%s: %s\n", name(path), line)
     72 	}
     73 	if serious > 0 {
     74 		return fmt.Errorf("%s: %d of %d findings change what is drawn", name(path), serious, len(lines))
     75 	}
     76 	return nil
     77 }
     78 
     79 // container names what the data holds, by the bytes it begins with and
     80 // failing that by the extension it was given. A Windows binary is named by
     81 // its extension, since the icons are inside it rather than at its start.
     82 func container(data []byte, ext string) string {
     83 	switch {
     84 	case len(data) >= 4 && string(data[:4]) == "icns":
     85 		return ".icns"
     86 	case len(data) >= 4 && string(data[:4]) == "\x00\x00\x01\x00":
     87 		return ".ico"
     88 	case len(data) >= 2 && string(data[:2]) == "MZ":
     89 		if binaries[ext] {
     90 			return ext
     91 		}
     92 		return ".exe"
     93 	}
     94 	if containers[ext] {
     95 		return ext
     96 	}
     97 	return ""
     98 }
     99 
    100 // name labels the file in a report, calling the one arriving on a pipe what
    101 // it is rather than leaving it blank.
    102 func name(path string) string {
    103 	if path == "" {
    104 		return "stdin"
    105 	}
    106 	return filepath.Base(path)
    107 }