mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-08 14:38:30 -04:00
3f6cd0b579
feat(registry): support multiple plugin/theme registries closes #2763 --------- Co-authored-by: bbedward <bbedward@gmail.com>
131 lines
3.0 KiB
Go
131 lines
3.0 KiB
Go
package registries
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/spf13/afero"
|
|
)
|
|
|
|
const (
|
|
OfficialName = "official"
|
|
officialURL = "https://github.com/AvengeMedia/dms-plugin-registry.git"
|
|
)
|
|
|
|
// Source identifies a registry repository. Name doubles as the per-registry
|
|
// cache subdirectory, so it is restricted to a filesystem-safe slug.
|
|
type Source struct {
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func (s Source) Official() bool {
|
|
return s.Name == OfficialName
|
|
}
|
|
|
|
var nameRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,31}$`)
|
|
|
|
func configPath() (string, error) {
|
|
configDir, err := os.UserConfigDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get user config dir: %w", err)
|
|
}
|
|
return filepath.Join(configDir, "DankMaterialShell", "registries.json"), nil
|
|
}
|
|
|
|
// Load returns the official registry followed by any user-configured extras.
|
|
// A missing or unreadable config yields just the official registry.
|
|
func Load(fs afero.Fs) []Source {
|
|
sources := []Source{{Name: OfficialName, URL: officialURL}}
|
|
|
|
path, err := configPath()
|
|
if err != nil {
|
|
return sources
|
|
}
|
|
data, err := afero.ReadFile(fs, path)
|
|
if err != nil {
|
|
return sources
|
|
}
|
|
var extras []Source
|
|
if err := json.Unmarshal(data, &extras); err != nil {
|
|
return sources
|
|
}
|
|
for _, s := range extras {
|
|
if !nameRe.MatchString(s.Name) || s.Name == OfficialName || s.URL == "" {
|
|
continue
|
|
}
|
|
sources = append(sources, s)
|
|
}
|
|
return sources
|
|
}
|
|
|
|
func saveExtras(fs afero.Fs, extras []Source) error {
|
|
path, err := configPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := fs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return fmt.Errorf("failed to create config dir: %w", err)
|
|
}
|
|
data, err := json.MarshalIndent(extras, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return afero.WriteFile(fs, path, append(data, '\n'), 0o644)
|
|
}
|
|
|
|
func loadExtras(fs afero.Fs) []Source {
|
|
sources := Load(fs)
|
|
return sources[1:]
|
|
}
|
|
|
|
func Add(fs afero.Fs, name, url string) error {
|
|
name = strings.TrimSpace(name)
|
|
url = strings.TrimSpace(url)
|
|
|
|
if !nameRe.MatchString(name) {
|
|
return fmt.Errorf("invalid registry name %q: use 1-32 lowercase letters, digits or hyphens", name)
|
|
}
|
|
if name == OfficialName {
|
|
return fmt.Errorf("registry name %q is reserved", OfficialName)
|
|
}
|
|
if url == "" {
|
|
return fmt.Errorf("registry URL is required")
|
|
}
|
|
|
|
extras := loadExtras(fs)
|
|
for _, s := range extras {
|
|
if s.Name == name {
|
|
return fmt.Errorf("registry %q already exists", name)
|
|
}
|
|
if s.URL == url {
|
|
return fmt.Errorf("registry %q already uses this URL", s.Name)
|
|
}
|
|
}
|
|
|
|
return saveExtras(fs, append(extras, Source{Name: name, URL: url}))
|
|
}
|
|
|
|
func Remove(fs afero.Fs, name string) error {
|
|
if name == OfficialName {
|
|
return fmt.Errorf("the official registry cannot be removed")
|
|
}
|
|
|
|
extras := loadExtras(fs)
|
|
kept := make([]Source, 0, len(extras))
|
|
for _, s := range extras {
|
|
if s.Name != name {
|
|
kept = append(kept, s)
|
|
}
|
|
}
|
|
if len(kept) == len(extras) {
|
|
return fmt.Errorf("registry %q not found", name)
|
|
}
|
|
|
|
return saveExtras(fs, kept)
|
|
}
|