110 lines
3.1 KiB
Go
110 lines
3.1 KiB
Go
// Cthulhu screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
)
|
|
|
|
const maxConfigSize = 1024 * 1024
|
|
|
|
func DefaultPath() (string, error) {
|
|
if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
|
|
return filepath.Join(dataHome, "cthulhu", "user-settings-go.toml"), nil
|
|
}
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ".local", "share", "cthulhu", "user-settings-go.toml"), nil
|
|
}
|
|
|
|
// Load reads legacy Python settings or the current versioned schema without
|
|
// modifying the file. A present schema marker always selects strict decoding.
|
|
func Load(path string) (Settings, error) {
|
|
settings := DefaultSettings()
|
|
if path == "" {
|
|
var err error
|
|
path, err = DefaultPath()
|
|
if err != nil {
|
|
return Settings{}, err
|
|
}
|
|
}
|
|
settings.Path = path
|
|
|
|
file, err := os.Open(path)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return settings, nil
|
|
}
|
|
if err != nil {
|
|
return Settings{}, fmt.Errorf("open configuration: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
contents, err := io.ReadAll(io.LimitReader(file, maxConfigSize+1))
|
|
if err != nil {
|
|
return Settings{}, fmt.Errorf("read configuration: %w", err)
|
|
}
|
|
if len(contents) > maxConfigSize {
|
|
return Settings{}, fmt.Errorf("configuration exceeds %d bytes", maxConfigSize)
|
|
}
|
|
|
|
var envelope struct {
|
|
SchemaVersion *int `toml:"schema-version"`
|
|
}
|
|
if err := toml.Unmarshal(contents, &envelope); err != nil {
|
|
return Settings{}, fmt.Errorf("parse configuration: %w", err)
|
|
}
|
|
if envelope.SchemaVersion != nil {
|
|
settings, err := decodeWritable(contents)
|
|
if err != nil {
|
|
return Settings{}, err
|
|
}
|
|
settings.Path = path
|
|
settings.Loaded = true
|
|
return settings, nil
|
|
}
|
|
|
|
var parsed document
|
|
if err := toml.Unmarshal(contents, &parsed); err != nil {
|
|
return Settings{}, fmt.Errorf("parse configuration: %w", err)
|
|
}
|
|
apply(&settings, parsed.General)
|
|
|
|
profileName := selectedProfile(parsed.General, parsed.Profiles)
|
|
if profileName != "" {
|
|
profile, exists := parsed.Profiles[profileName]
|
|
if !exists {
|
|
return Settings{}, fmt.Errorf("configured profile %q does not exist", profileName)
|
|
}
|
|
apply(&settings, profile)
|
|
}
|
|
settings.ActiveProfile = profileName
|
|
settings.Loaded = true
|
|
if err := validate(settings); err != nil {
|
|
return Settings{}, err
|
|
}
|
|
return settings, nil
|
|
}
|