forgejo/models/theme/theme_meta.go
Minecon724 d303f10bdc
feat: Theme loading and metadata (#13)
commit c92f17c167
Author: Minecon724 <minecon724@noreply.git.m724.eu>
Date:   Sat Sep 13 10:46:57 2025 +0000

    Display everything in the UI

commit fa533b8ba5
Author: Minecon724 <minecon724@noreply.git.m724.eu>
Date:   Sat Sep 13 10:46:40 2025 +0000

    Add theme loader

commit adbd5af8d1
Author: Minecon724 <minecon724@noreply.git.m724.eu>
Date:   Sat Sep 13 10:17:00 2025 +0000

    Fix webpack

commit 933ad0b23a
Author: Minecon724 <minecon724@noreply.git.m724.eu>
Date:   Sat Sep 13 10:16:51 2025 +0000

    Add metadata to builtin themes

commit 1b8aa2fc35
Author: Minecon724 <minecon724@noreply.git.m724.eu>
Date:   Sat Sep 13 10:16:38 2025 +0000

    Remove accessibility themes
2025-10-05 17:16:53 +02:00

108 lines
2.6 KiB
Go

package theme
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"forgejo.org/modules/assetfs"
)
// The Theme struct represents the metadata of a theme. Most fields are allowed to be null / blank.
type Theme struct {
// ID is the theme id, e.g., forgejo-dark
ID string
// Name is the theme's readable name, e.g., Forgejo. Note that the scheme is excluded in favor of the Scheme property.
Name string
// Family is the theme's family, e.g. Catppuccin.
Family string
// Author is the theme's author. It may be an invididual, a group, or an entity.
Author string
// Url is the URL to the theme's website, e.g. https://forgejo.org.
Url string
// Description is the theme's short description.
Description string
// Scheme is the theme's color scheme. It must be either: dark, light, or auto.
Scheme string
// Recommended means the theme is guaranteed to be bug-free, usually a first-party theme.
Recommended bool
}
// loadThemeMeta loads theme metadata from a file in a layered FS.
//
// Theme ID is expected to be prefilled. The metadata will be assigned to the passed `theme` object.
func loadThemeMeta(assetFs *assetfs.LayeredFS, filename string, theme *Theme) error {
file, err := assetFs.Open("assets/css/" + filename)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
kv, err := scanThemeForKeyValueMetaMap(file, theme.ID) // ID is expected to be prefilled
if err != nil {
return fmt.Errorf("error scanning theme for meta: %w", err)
}
theme.Name = kv["name"]
theme.Family = kv["family"]
theme.Author = kv["author"]
theme.Url = kv["url"]
theme.Description = kv["description"]
theme.Scheme = strings.ToLower(kv["scheme"])
theme.Recommended = kv["recommended"] == "yes"
return nil
}
// scanThemeForKeyValueMetaMap finds raw key-value metadata in an open file.
func scanThemeForKeyValueMetaMap(file io.Reader, id string) (map[string]string, error) {
var kv = make(map[string]string)
scanner := bufio.NewScanner(file)
ok := false
// webpack adds headers
for scanner.Scan() {
if strings.TrimSpace(scanner.Text()) == "/* theme "+id {
ok = true
break
}
}
if !ok {
return nil, errors.New("theme has no meta")
}
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 0 {
continue
}
key := strings.ToLower(parts[0])
if key == "*/" {
break
}
if len(parts) == 1 {
kv[key] = "yes"
} else {
kv[key] = strings.Join(parts[1:], " ")
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scanner error: %w", err)
}
return kv, nil
}