Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Derive theme metadata from the stylesheet file name

d222b0cf5d23444ea5df9429ec7d3690407dd00c · Keenan Nemetz · 1d ago · parent f7a34d87

Derive theme metadata from the stylesheet file name

profile/themes/index.json is gone. Each profile/themes/<id>.css is self-describing: the file name is the id and the label (acme-ocean.css becomes Acme Ocean), and the rules are scoped to that id. The loader discovers the files, validates the names, and sorts them by label.

Remove the manifest from the starter repository, its README, the Admin Configuration path table, and the configuration docs. An old index.json in an existing repository is ignored.

forge/instancerepo/instancerepo_test.go +13−24

@@ -53,17 +53,16 @@ func TestLoadPicksOnlyTheFixedPaths(t *testing.T) {
53 53 }
54 54 }
55 55
56 func TestLoadReadsThemesInManifestOrder(t *testing.T) {
56 func TestLoadReadsThemesFromFileNames(t *testing.T) {
57 57 t.Parallel()
58 58 g, commit := seed(t, map[string][]byte{
59 "profile/custom.css": []byte(":root[data-theme] { --radius: 2px; }"),
60 "profile/themes/index.json": []byte(`{"themes":[
61 {"id":"acme-ocean","name":"Acme Ocean"},
62 {"id":"acme-paper","name":"Acme Paper"}
63 ]}`),
64 "profile/themes/acme-ocean.css": []byte(`:root[data-theme="acme-ocean"] { color-scheme: dark; }`),
59 "profile/custom.css": []byte(":root[data-theme] { --radius: 2px; }"),
65 60 "profile/themes/acme-paper.css": []byte(`:root[data-theme="acme-paper"] { color-scheme: light; }`),
66 "profile/themes/unlisted.css": []byte("ignored"),
61 "profile/themes/acme-ocean.css": []byte(`:root[data-theme="acme-ocean"] { color-scheme: dark; }`),
62 // An old manifest from before the file-name convention is ignored, and
63 // a non-CSS file under the directory is not a theme.
64 "profile/themes/index.json": []byte(`{"themes":[{"id":"gone","name":"Gone"}]}`),
65 "profile/themes/notes.txt": []byte("ignored"),
67 66 })
68 67 snap, err := load(g, commit)
69 68 if err != nil {
@@ -84,30 +83,20 @@ func TestLoadReadsThemesInManifestOrder(t *testing.T) {
84 83 }
85 84 }
86 85
87 func TestLoadRejectsABadThemeManifest(t *testing.T) {
86 func TestLoadRejectsABadThemeFileName(t *testing.T) {
88 87 t.Parallel()
89 88 cases := map[string]map[string][]byte{
90 "invalid json": {
91 "profile/themes/index.json": []byte(`{"themes":[}`),
92 },
93 "duplicate id": {
94 "profile/themes/index.json": []byte(`{"themes":[{"id":"acme","name":"A"},{"id":"acme","name":"B"}]}`),
95 "profile/themes/acme.css": []byte("body {}"),
96 },
97 89 "reserved id": {
98 "profile/themes/index.json": []byte(`{"themes":[{"id":"everforest-dark","name":"Not Everforest"}]}`),
99 90 "profile/themes/everforest-dark.css": []byte("body {}"),
100 91 },
101 "missing stylesheet": {
102 "profile/themes/index.json": []byte(`{"themes":[{"id":"acme","name":"Acme"}]}`),
92 "system id": {
93 "profile/themes/system.css": []byte("body {}"),
103 94 },
104 95 "invalid id": {
105 "profile/themes/index.json": []byte(`{"themes":[{"id":"Acme Ocean","name":"Acme"}]}`),
106 96 "profile/themes/Acme Ocean.css": []byte("body {}"),
107 97 },
108 "empty name": {
109 "profile/themes/index.json": []byte(`{"themes":[{"id":"acme","name":" "}]}`),
110 "profile/themes/acme.css": []byte("body {}"),
98 "uppercase id": {
99 "profile/themes/Acme.css": []byte("body {}"),
111 100 },
112 101 }
113 102 for name, files := range cases {
@@ -116,7 +105,7 @@ func TestLoadRejectsABadThemeManifest(t *testing.T) {
116 105 t.Parallel()
117 106 g, commit := seed(t, files)
118 107 if _, err := load(g, commit); err == nil {
119 t.Fatal("load accepted a bad theme manifest")
108 t.Fatal("load accepted a bad theme file name")
120 109 }
121 110 })
122 111 }