hdmistat/internal/config/config.go
sneak 2f8256b310 Refactor hdmistat to use dependency injection and fix linter issues
Major changes:
- Converted all cobra commands from global variables to CLI struct methods
- Eliminated global logger variable in favor of dependency injection
- Fixed all errcheck linter issues by properly handling errors
- Fixed Makefile to check formatting instead of modifying files
- Integrated smartconfig library for configuration management
- Added CLAUDE.md with project-specific development guidelines

Key improvements:
- All commands (daemon, install, status, info) now use CLI struct methods
- Logger is injected as dependency through fx providers
- Proper error handling for all DrawText and file.Close() calls
- Configuration loading now uses smartconfig with proper defaults
- Fixed formatting check in Makefile (make test no longer modifies files)

Technical details:
- Created CLI struct with log field (renamed from logger per request)
- All command constructors return *cobra.Command from CLI methods
- Config package uses smartconfig.NewFromAppName() correctly
- Fixed all critical errcheck issues throughout the codebase
- Maintained backward compatibility with existing functionality

All tests passing, code formatted, and ready for deployment.
2025-07-23 15:01:51 +02:00

119 lines
2.7 KiB
Go

package config
import (
"context"
"log/slog"
"time"
"git.eeqj.de/sneak/smartconfig"
)
// Config holds the application configuration
type Config struct {
FramebufferDevice string
RotationInterval string
UpdateInterval string
Screens []string
LogLevel string
Width int
Height int
// Parsed durations (not from config)
rotationDuration time.Duration
updateDuration time.Duration
}
// Load loads configuration from all available sources
func Load(ctx context.Context) (*Config, error) {
// Start with defaults
cfg := &Config{
FramebufferDevice: "/dev/fb0",
RotationInterval: "10s",
UpdateInterval: "1s",
Screens: []string{"overview", "top_cpu", "top_memory"},
LogLevel: "info",
Width: 1920,
Height: 1080,
}
// Try to load from the default location for hdmistat
sc, err := smartconfig.NewFromAppName("hdmistat")
if err == nil {
// Override defaults with config file values if they exist
if val, err := sc.GetString("framebuffer_device"); err == nil && val != "" {
cfg.FramebufferDevice = val
}
if val, err := sc.GetString("rotation_interval"); err == nil && val != "" {
cfg.RotationInterval = val
}
if val, err := sc.GetString("update_interval"); err == nil && val != "" {
cfg.UpdateInterval = val
}
if val, err := sc.GetString("log_level"); err == nil && val != "" {
cfg.LogLevel = val
}
if val, err := sc.GetInt("width"); err == nil && val > 0 {
cfg.Width = val
}
if val, err := sc.GetInt("height"); err == nil && val > 0 {
cfg.Height = val
}
// Load screens array
if screensRaw, exists := sc.Get("screens"); exists {
if screens, ok := screensRaw.([]interface{}); ok && len(screens) > 0 {
cfg.Screens = make([]string, 0, len(screens))
for _, s := range screens {
if str, ok := s.(string); ok {
cfg.Screens = append(cfg.Screens, str)
}
}
}
}
}
// Parse durations
cfg.rotationDuration, err = time.ParseDuration(cfg.RotationInterval)
if err != nil {
return nil, err
}
cfg.updateDuration, err = time.ParseDuration(cfg.UpdateInterval)
if err != nil {
return nil, err
}
return cfg, nil
}
// GetRotationDuration returns the parsed rotation interval
func (c *Config) GetRotationDuration() time.Duration {
return c.rotationDuration
}
// GetUpdateDuration returns the parsed update interval
func (c *Config) GetUpdateDuration() time.Duration {
return c.updateDuration
}
// GetLogLevel returns the slog level
func (c *Config) GetLogLevel() slog.Level {
switch c.LogLevel {
case "debug":
return slog.LevelDebug
case "info":
return slog.LevelInfo
case "warn":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}