mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 00:47:26 +02:00
155 lines
4.1 KiB
Go
155 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type ProxyEntry struct {
|
|
URL string `yaml:"url" json:"url"`
|
|
}
|
|
|
|
type Config struct {
|
|
Proxies []ProxyEntry `yaml:"proxies,omitempty" json:"proxies,omitempty"`
|
|
Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
|
|
Profiles map[string]Profile `yaml:"profiles" json:"profiles"`
|
|
}
|
|
|
|
type Profile struct {
|
|
Notes string `yaml:"notes,omitempty" json:"notes,omitempty"`
|
|
Tools map[string]yaml.Node `yaml:"tools" json:"tools"`
|
|
Enabled []string `yaml:"enabled" json:"enabled"`
|
|
Disabled []string `yaml:"disabled" json:"disabled"`
|
|
}
|
|
|
|
func (c *Config) DecodeEffective(toolName, profileName string, dst any) error {
|
|
if node, ok := c.Tools[toolName]; ok {
|
|
if err := node.Decode(dst); err != nil {
|
|
return fmt.Errorf("config: decoding global config for tool %q: %w", toolName, err)
|
|
}
|
|
}
|
|
|
|
if profileName != "" {
|
|
// Builtin profiles have their overrides defined in Go, not in YAML.
|
|
if _, isBuiltin := BuiltinProfiles[profileName]; isBuiltin {
|
|
return ApplyBuiltinToolOverride(profileName, toolName, dst)
|
|
}
|
|
p, ok := c.Profiles[profileName]
|
|
if !ok {
|
|
return fmt.Errorf("config: unknown profile %q", profileName)
|
|
}
|
|
if node, ok := p.Tools[toolName]; ok {
|
|
if err := node.Decode(dst); err != nil {
|
|
return fmt.Errorf("config: decoding profile %q override for tool %q: %w", profileName, toolName, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) ActiveTools(profileName string, allToolNames []string) ([]string, error) {
|
|
if profileName == "" {
|
|
return allToolNames, nil
|
|
}
|
|
if builtin, ok := BuiltinProfiles[profileName]; ok {
|
|
return ActiveToolsForProfile(builtin.Profile, allToolNames), nil
|
|
}
|
|
p, ok := c.Profiles[profileName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("config: unknown profile %q", profileName)
|
|
}
|
|
return ActiveToolsForProfile(p, allToolNames), nil
|
|
}
|
|
|
|
// IsReadonly reports whether the config file at path cannot be written to.
|
|
// Returns false if the file does not exist (it can still be created).
|
|
func IsReadonly(path string) bool {
|
|
f, err := os.OpenFile(path, os.O_WRONLY, 0)
|
|
if err != nil {
|
|
return os.IsPermission(err)
|
|
}
|
|
f.Close()
|
|
return false
|
|
}
|
|
|
|
func Load(path string) (*Config, error) {
|
|
f, err := os.Open(path)
|
|
if os.IsNotExist(err) {
|
|
log.Printf("config: %q not found, starting with empty config", path)
|
|
return Default(), nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("config: open %q: %w", path, err)
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
var cfg Config
|
|
dec := yaml.NewDecoder(f)
|
|
dec.KnownFields(true)
|
|
if err := dec.Decode(&cfg); err != nil {
|
|
if err == io.EOF {
|
|
log.Printf("config: %q is empty, starting with empty config", path)
|
|
return Default(), nil
|
|
}
|
|
return nil, fmt.Errorf("config: decode: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
func Default() *Config {
|
|
return &Config{
|
|
Tools: make(map[string]yaml.Node),
|
|
Profiles: make(map[string]Profile),
|
|
}
|
|
}
|
|
|
|
func Save(path string, cfg *Config) error {
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
return fmt.Errorf("config: create %q: %w", path, err)
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
enc := yaml.NewEncoder(f)
|
|
enc.SetIndent(2)
|
|
if err := enc.Encode(cfg); err != nil {
|
|
return fmt.Errorf("config: encode: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MergeNodePatch merges patch key-values into an existing yaml.Node (mapping).
|
|
// If existing is a zero value, it starts from an empty mapping.
|
|
func MergeNodePatch(existing yaml.Node, patch map[string]any) (yaml.Node, error) {
|
|
var m map[string]any
|
|
if existing.Kind != 0 {
|
|
if err := existing.Decode(&m); err != nil {
|
|
return yaml.Node{}, fmt.Errorf("config: decode node: %w", err)
|
|
}
|
|
}
|
|
if m == nil {
|
|
m = make(map[string]any)
|
|
}
|
|
for k, v := range patch {
|
|
m[k] = v
|
|
}
|
|
|
|
b, err := yaml.Marshal(m)
|
|
if err != nil {
|
|
return yaml.Node{}, fmt.Errorf("config: marshal: %w", err)
|
|
}
|
|
var doc yaml.Node
|
|
if err := yaml.Unmarshal(b, &doc); err != nil {
|
|
return yaml.Node{}, fmt.Errorf("config: unmarshal: %w", err)
|
|
}
|
|
if doc.Kind == yaml.DocumentNode && len(doc.Content) == 1 {
|
|
return *doc.Content[0], nil
|
|
}
|
|
return doc, nil
|
|
}
|