Files

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
}