2026-03-12 20:51:38 -07:00

133 lines
3.1 KiB
Go

// Package config provides INI-style configuration file parsing and editing
// for autarch_settings.conf. It preserves comments and formatting.
package config
import (
"fmt"
"os"
"strings"
)
// ListSections returns all [section] names from an INI file.
func ListSections(path string) ([]string, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
var sections []string
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
sec := line[1 : len(line)-1]
sections = append(sections, sec)
}
}
return sections, nil
}
// GetSection returns all key-value pairs from a specific section.
func GetSection(path, section string) (keys []string, vals []string, err error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, nil, fmt.Errorf("read config: %w", err)
}
inSection := false
target := "[" + section + "]"
for _, line := range strings.Split(string(data), "\n") {
trimmed := strings.TrimSpace(line)
// Check for section headers
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
inSection = (trimmed == target)
continue
}
if !inSection {
continue
}
// Skip comments and empty lines
if trimmed == "" || strings.HasPrefix(trimmed, "#") || strings.HasPrefix(trimmed, ";") {
continue
}
// Parse key = value
eqIdx := strings.Index(trimmed, "=")
if eqIdx < 0 {
continue
}
key := strings.TrimSpace(trimmed[:eqIdx])
val := strings.TrimSpace(trimmed[eqIdx+1:])
keys = append(keys, key)
vals = append(vals, val)
}
return keys, vals, nil
}
// SetValue updates a single key in a section within the INI content string.
// Returns the modified content. If the key doesn't exist, it's appended to the section.
func SetValue(content, section, key, value string) string {
lines := strings.Split(content, "\n")
target := "[" + section + "]"
inSection := false
found := false
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]") {
// If we were in our target section and didn't find the key, insert before this line
if inSection && !found {
lines[i] = key + " = " + value + "\n" + line
found = true
}
inSection = (trimmed == target)
continue
}
if !inSection {
continue
}
// Check if this line matches our key
eqIdx := strings.Index(trimmed, "=")
if eqIdx < 0 {
continue
}
lineKey := strings.TrimSpace(trimmed[:eqIdx])
if lineKey == key {
lines[i] = key + " = " + value
found = true
}
}
// If key wasn't found and we're still in section (or section was last), append
if !found {
if inSection {
lines = append(lines, key+" = "+value)
}
}
return strings.Join(lines, "\n")
}
// GetValue reads a single value from a section.
func GetValue(path, section, key string) (string, error) {
keys, vals, err := GetSection(path, section)
if err != nil {
return "", err
}
for i, k := range keys {
if k == key {
return vals[i], nil
}
}
return "", fmt.Errorf("key %q not found in [%s]", key, section)
}