Commit ec5a5f9093

ec5a5f9093810bc596131924ad0d80a20b53bc57

parent: ffd964b971

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-25 23:15 UTC

Add format registry

Layout: unified · split

.gitignore added +4
@@ -0,0 +1,4 @@
1/photos/
2/cache/
3/gallery
4.superpowers/
go.mod added +3
@@ -0,0 +1,3 @@
1module gitbay.org/cmc/gallery
2
3go 1.27.1
internal/format/format.go added +61
@@ -0,0 +1,61 @@
1// Package format describes the file types the gallery can show.
2package format
3
4import (
5 "io"
6 "path/filepath"
7 "strings"
8 "time"
9)
10
11type Kind int
12
13const (
14 KindImage Kind = iota
15)
16
17// Meta holds display metadata. Width and Height are after EXIF orientation.
18type Meta struct {
19 Width, Height int
20 Taken time.Time
21 Camera, Lens string
22 Aperture string
23 Shutter string
24 ISO int
25}
26
27type Format interface {
28 Name() string
29 Match(path string) bool
30 Metadata(path string) (Meta, error)
31 Resize(src string, width int, dst io.Writer) error
32 Kind() Kind
33}
34
35// Registry resolves a path to the first registered Format that matches it.
36type Registry struct {
37 formats []Format
38}
39
40func (r *Registry) Register(f Format) {
41 r.formats = append(r.formats, f)
42}
43
44func (r *Registry) Lookup(path string) Format {
45 for _, f := range r.formats {
46 if f.Match(path) {
47 return f
48 }
49 }
50 return nil
51}
52
53func hasExt(path string, exts []string) bool {
54 ext := strings.ToLower(filepath.Ext(path))
55 for _, e := range exts {
56 if ext == e {
57 return true
58 }
59 }
60 return false
61}
internal/format/format_test.go added +44
@@ -0,0 +1,44 @@
1package format
2
3import (
4 "io"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10type fake struct{ name, ext string }
11
12func (f fake) Name() string { return f.name }
13func (f fake) Match(p string) bool { return strings.EqualFold(filepath.Ext(p), f.ext) }
14func (fake) Metadata(string) (Meta, error) { return Meta{}, nil }
15func (fake) Resize(string, int, io.Writer) error { return nil }
16func (fake) Kind() Kind { return KindImage }
17
18func TestRegistryFirstMatchWins(t *testing.T) {
19 var r Registry
20 r.Register(fake{"a", ".jpg"})
21 r.Register(fake{"b", ".jpg"})
22 r.Register(fake{"c", ".png"})
23
24 if got := r.Lookup("x/IMG.JPG"); got == nil || got.Name() != "a" {
25 t.Fatalf("jpg: got %v, want a", got)
26 }
27 if got := r.Lookup("x.png"); got == nil || got.Name() != "c" {
28 t.Fatalf("png: got %v, want c", got)
29 }
30 if got := r.Lookup("notes.txt"); got != nil {
31 t.Fatalf("txt: got %v, want nil", got)
32 }
33}
34
35func TestHasExt(t *testing.T) {
36 exts := []string{".jpg", ".jpeg"}
37 for p, want := range map[string]bool{
38 "a.jpg": true, "a.JPEG": true, "a.png": false, "jpg": false, "a.jpg.txt": false,
39 } {
40 if got := hasExt(p, exts); got != want {
41 t.Errorf("hasExt(%q) = %v, want %v", p, got, want)
42 }
43 }
44}