Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,145 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
func defaultDocument() writableDocument {
|
||||
return writableDocument{
|
||||
SchemaVersion: CurrentSchemaVersion,
|
||||
ActiveProfile: "default",
|
||||
Profiles: map[string]writableProfile{
|
||||
"default": profileFromSettings(DefaultSettings()),
|
||||
},
|
||||
Applications: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func decodeWritable(contents []byte) (Settings, error) {
|
||||
_, settings, err := decodeDocument(contents)
|
||||
return settings, err
|
||||
}
|
||||
|
||||
func decodeDocument(contents []byte) (writableDocument, Settings, error) {
|
||||
var envelope struct {
|
||||
SchemaVersion int `toml:"schema-version"`
|
||||
}
|
||||
if err := toml.Unmarshal(contents, &envelope); err != nil {
|
||||
return writableDocument{}, Settings{}, fmt.Errorf("parse settings: %w", err)
|
||||
}
|
||||
if envelope.SchemaVersion != CurrentSchemaVersion {
|
||||
return writableDocument{}, Settings{}, fmt.Errorf("unsupported settings schema version %d; expected %d", envelope.SchemaVersion, CurrentSchemaVersion)
|
||||
}
|
||||
var document writableDocument
|
||||
decoder := toml.NewDecoder(bytes.NewReader(contents))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&document); err != nil {
|
||||
var strictError *toml.StrictMissingError
|
||||
if errors.As(err, &strictError) {
|
||||
return writableDocument{}, Settings{}, fmt.Errorf("parse settings schema %d: %s", CurrentSchemaVersion, strictError.String())
|
||||
}
|
||||
return writableDocument{}, Settings{}, fmt.Errorf("parse settings schema %d: %w", CurrentSchemaVersion, err)
|
||||
}
|
||||
settings, err := effectiveSettings(document)
|
||||
if err != nil {
|
||||
return writableDocument{}, Settings{}, err
|
||||
}
|
||||
return document, settings, nil
|
||||
}
|
||||
|
||||
func effectiveSettings(document writableDocument) (Settings, error) {
|
||||
if strings.TrimSpace(document.ActiveProfile) == "" {
|
||||
return Settings{}, errors.New("validate configuration: active-profile is empty")
|
||||
}
|
||||
profile, exists := document.Profiles[document.ActiveProfile]
|
||||
if !exists {
|
||||
return Settings{}, fmt.Errorf("validate configuration: active profile %q does not exist", document.ActiveProfile)
|
||||
}
|
||||
for _, name := range sortedKeys(document.Profiles) {
|
||||
candidate := document.Profiles[name]
|
||||
if strings.TrimSpace(name) != name || name == "" {
|
||||
return Settings{}, fmt.Errorf("validate configuration: invalid profile name %q", name)
|
||||
}
|
||||
if err := validateCommandValue("profile name", name); err != nil {
|
||||
return Settings{}, err
|
||||
}
|
||||
candidateSettings := DefaultSettings()
|
||||
applyProfile(&candidateSettings, candidate)
|
||||
if err := validate(candidateSettings); err != nil {
|
||||
return Settings{}, fmt.Errorf("profile %q: %w", name, err)
|
||||
}
|
||||
}
|
||||
for _, application := range sortedKeys(document.Applications) {
|
||||
profileName := document.Applications[application]
|
||||
if strings.TrimSpace(application) != application || application == "" {
|
||||
return Settings{}, fmt.Errorf("validate configuration: invalid application identifier %q", application)
|
||||
}
|
||||
if err := validateCommandValue("application identifier", application); err != nil {
|
||||
return Settings{}, err
|
||||
}
|
||||
if _, exists := document.Profiles[profileName]; !exists {
|
||||
return Settings{}, fmt.Errorf("validate configuration: application %q selects missing profile %q", application, profileName)
|
||||
}
|
||||
}
|
||||
settings := DefaultSettings()
|
||||
applyProfile(&settings, profile)
|
||||
settings.ActiveProfile = document.ActiveProfile
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func sortedKeys[Value any](values map[string]Value) []string {
|
||||
keys := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func encodeWritable(settings Settings) ([]byte, error) {
|
||||
document := defaultDocument()
|
||||
profileName := settings.ActiveProfile
|
||||
if profileName == "" {
|
||||
profileName = document.ActiveProfile
|
||||
}
|
||||
document.ActiveProfile = profileName
|
||||
document.Profiles = map[string]writableProfile{profileName: profileFromSettings(settings)}
|
||||
return encodeDocument(document)
|
||||
}
|
||||
|
||||
func encodeDocument(document writableDocument) ([]byte, error) {
|
||||
if document.SchemaVersion != CurrentSchemaVersion {
|
||||
return nil, fmt.Errorf("encode settings schema version %d; expected %d", document.SchemaVersion, CurrentSchemaVersion)
|
||||
}
|
||||
if _, err := effectiveSettings(document); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := toml.Marshal(document)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode settings: %w", err)
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
Reference in new issue
Block a user