rastrillo / fiesta Public

Expose where each example state came from as fiesta.Sources()

hack/seed/sources.json records which partial, idiom, shell, format or
screen each example state was seeded from, and which of its states,
locale and view. Sources() returns it from the embedded file, keyed by
the example path, so a server holding its templates to the examples
reads the mapping from the pinned release instead of keeping a copy.
TestSourcesIsSourcesJSON holds it to the file key for key and field
for field, and every key is an example state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev a3f741894fcc959a6ac430742f9a5d96350b50ea parent 6bf2969
2 files changed, +146 −0
  • sources.go +44 −0
  • sources_test.go +102 −0
diff --git a/sources.go b/sources.go
new file mode 100644
index 0000000..9532fbf
--- /dev/null
+++ b/sources.go
@@ -0,0 +1,44 @@
+package fiesta
+
+import (
+ _ "embed"
+ "encoding/json"
+ "maps"
+ "sync"
+)
+
+// Source says where one example state came from when the examples were
+// seeded: the kind of thing it was in the gallery it was taken from (a
+// partial, an idiom, a shell, a format or a screen), that thing's name,
+// which of its states, the locale it was rendered in, and, for a shell,
+// which view of it (index or page). A server whose templates render the
+// same parts can hold its output to the example a Source names.
+type Source struct {
+ Kind string `json:"kind"`
+ Source string `json:"source"`
+ State int `json:"state"`
+ Locale string `json:"locale"`
+ View string `json:"view,omitempty"`
+}
+
+// sourcesJSON is the seed's record, kept where the seeding wrote it.
+//
+//go:embed hack/seed/sources.json
+var sourcesJSON []byte
+
+// sources is sourcesJSON read once. The file is embedded, so a record
+// that does not read is a broken build rather than a condition to
+// report: the panic says so.
+var sources = sync.OnceValue(func() map[string]Source {
+ var out map[string]Source
+ if err := json.Unmarshal(sourcesJSON, &out); err != nil {
+ panic("fiesta: hack/seed/sources.json does not read: " + err.Error())
+ }
+ return out
+})
+
+// Sources returns where every seeded example state came from, keyed by
+// its path under examples/ without the .html: "badge/positive" for
+// examples/badge/positive.html. The returned map is a copy, so a caller
+// that edits it cannot change the next caller's.
+func Sources() map[string]Source { return maps.Clone(sources()) }
diff --git a/sources_test.go b/sources_test.go
new file mode 100644
index 0000000..b586eee
--- /dev/null
+++ b/sources_test.go
@@ -0,0 +1,102 @@
+package fiesta
+
+import (
+ "encoding/json"
+ "io/fs"
+ "os"
+ "strconv"
+ "testing"
+)
+
+// TestSourcesIsSourcesJSON: Sources() is hack/seed/sources.json, key
+// for key and field for field. The file is read from disk here, not
+// through the embed, so the test compares the API with the record
+// rather than with itself; a field the record has and Source lacks
+// fails by name, as does a key either side lacks.
+func TestSourcesIsSourcesJSON(t *testing.T) {
+ b, err := os.ReadFile("hack/seed/sources.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var record map[string]map[string]json.RawMessage
+ if err := json.Unmarshal(b, &record); err != nil {
+ t.Fatalf("hack/seed/sources.json: %v", err)
+ }
+ got := Sources()
+ if len(record) < 160 {
+ t.Fatalf("hack/seed/sources.json holds %d example states, want the seeded 160 or more; the comparison would be over too little", len(record))
+ }
+ for _, key := range sortedNames(record) {
+ source, ok := got[key]
+ if !ok {
+ t.Errorf("Sources() has no %s, which sources.json records", key)
+ continue
+ }
+ fields := map[string]string{
+ "kind": strconv.Quote(source.Kind),
+ "source": strconv.Quote(source.Source),
+ "locale": strconv.Quote(source.Locale),
+ "state": strconv.Itoa(source.State),
+ }
+ if source.View != "" {
+ fields["view"] = strconv.Quote(source.View)
+ }
+ for _, field := range sortedNames(record[key]) {
+ if want := string(record[key][field]); fields[field] != want {
+ t.Errorf("Sources()[%q] has %s %s, and sources.json %s", key, field, fields[field], want)
+ }
+ }
+ for field := range fields {
+ if _, ok := record[key][field]; !ok {
+ t.Errorf("Sources()[%q] has a %s, and sources.json none", key, field)
+ }
+ }
+ }
+ for _, key := range sortedNames(got) {
+ if _, ok := record[key]; !ok {
+ t.Errorf("Sources() has %s, which sources.json does not record", key)
+ }
+ }
+}
+
+// TestEverySourceIsAnExampleState: each key Sources() returns is an
+// example and one of its states, as examples/examples.json lists them,
+// so a caller can open the example a source names.
+func TestEverySourceIsAnExampleState(t *testing.T) {
+ var manifest struct {
+ Examples []struct {
+ Name string
+ States []struct{ ID string }
+ }
+ }
+ b, err := fs.ReadFile(FS(), "examples/examples.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := json.Unmarshal(b, &manifest); err != nil {
+ t.Fatal(err)
+ }
+ states := map[string]bool{}
+ for _, example := range manifest.Examples {
+ for _, state := range example.States {
+ states[example.Name+"/"+state.ID] = true
+ }
+ }
+ for _, key := range sortedNames(Sources()) {
+ if !states[key] {
+ t.Errorf("Sources() has %s, which is no example state in examples/examples.json", key)
+ }
+ }
+}
+
+// TestSourcesIsACopy: a caller that edits the map it was given does not
+// edit the next caller's.
+func TestSourcesIsACopy(t *testing.T) {
+ first := Sources()
+ for key := range first {
+ delete(first, key)
+ }
+ if len(Sources()) == 0 {
+ t.Error("emptying the map Sources() returned emptied the next call's")
+ }
+}