diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-08 10:01:57 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-08 10:01:57 +0300 |
| commit | 08d7d36ac6d49d7d5efd39a837bc08c9c52b6eb8 (patch) | |
| tree | 8ce1eecdeb832dadf1d35a4edf496542e13de2a0 /internal/processor/cli_config_resolver.go | |
| parent | 35a3c860f63403d14c83668dcdd017b6ef517fbf (diff) | |
refactor(processor): split BatchProcessor, AnkiExporter, CLIConfigResolver
Extract CLIConfigResolver for flag/config precedence and GUI wiring; embed
it on Processor so audio and image helpers use promoted accessors.
Move batch file flow to BatchProcessor and Anki generation to AnkiExporter;
Processor delegates while keeping per-word processing and translation cache.
Made-with: Cursor
Diffstat (limited to 'internal/processor/cli_config_resolver.go')
| -rw-r--r-- | internal/processor/cli_config_resolver.go | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/internal/processor/cli_config_resolver.go b/internal/processor/cli_config_resolver.go new file mode 100644 index 0000000..5cb18cb --- /dev/null +++ b/internal/processor/cli_config_resolver.go @@ -0,0 +1,179 @@ +package processor + +// CLIConfigResolver applies CLI-flag vs config-file precedence for run-mode +// settings used by the CLI, GUI wiring, and downstream helpers (audio format, +// provider names, Nano Banana models). It does not own API clients or I/O; +// those stay on Processor. + +import ( + "strings" + + "codeberg.org/snonux/totalrecall/internal/audio" + "codeberg.org/snonux/totalrecall/internal/cli" + "codeberg.org/snonux/totalrecall/internal/gui" + "codeberg.org/snonux/totalrecall/internal/image" + "codeberg.org/snonux/totalrecall/internal/phonetic" + "codeberg.org/snonux/totalrecall/internal/translation" +) + +// CLIConfigResolver holds the resolved CLI flags and config snapshot from the +// composition root. Methods implement precedence rules between explicit flags +// and YAML config without importing Viper. +type CLIConfigResolver struct { + Flags *cli.Flags + Config *Config +} + +// AudioProviderName returns the configured audio provider name, preferring the +// config-file value over the CLI flag so config-file settings win. +func (r *CLIConfigResolver) AudioProviderName() string { + if r.Config.AudioProvider != "" { + return r.Config.AudioProvider + } + if r != nil && r.Flags != nil { + return strings.ToLower(strings.TrimSpace(r.Flags.AudioProvider)) + } + return "" +} + +// EffectiveAudioFormat resolves the audio format from flags and config, with +// CLI flag taking precedence, then the config-file value, then provider-specific defaults. +func (r *CLIConfigResolver) EffectiveAudioFormat() string { + if r != nil && r.Flags != nil && r.Flags.AudioFormatSpecified { + if format := strings.ToLower(strings.TrimSpace(r.Flags.AudioFormat)); format != "" { + return format + } + } + + if r.Config.AudioFormatSet && r.Config.AudioFormat != "" { + return r.Config.AudioFormat + } + + if r != nil && r.Flags != nil { + if format := strings.ToLower(strings.TrimSpace(r.Flags.AudioFormat)); format != "" { + return format + } + } + + if r.AudioProviderName() == "gemini" { + return audio.DefaultProviderConfig().OutputFormat + } + + return "mp3" +} + +// GeminiTTSModel returns the Gemini TTS model, preferring the config-file value over the CLI flag. +func (r *CLIConfigResolver) GeminiTTSModel() string { + if r.Config.GeminiTTSModel != "" { + return r.Config.GeminiTTSModel + } + if r != nil && r.Flags != nil { + return strings.TrimSpace(r.Flags.GeminiTTSModel) + } + return "" +} + +// GeminiVoice returns the Gemini voice, preferring the config-file value over the CLI flag. +func (r *CLIConfigResolver) GeminiVoice() string { + if r.Config.GeminiVoice != "" { + return r.Config.GeminiVoice + } + if r != nil && r.Flags != nil { + return strings.TrimSpace(r.Flags.GeminiVoice) + } + return "" +} + +// OpenAIVoice returns the OpenAI voice, preferring the config-file value over the CLI flag. +func (r *CLIConfigResolver) OpenAIVoice() string { + if r.Config.OpenAIVoice != "" { + return r.Config.OpenAIVoice + } + if r != nil && r.Flags != nil { + return strings.TrimSpace(r.Flags.OpenAIVoice) + } + return "" +} + +// GUIConfig returns a gui.Config populated from flags and config. +// Callers (typically cmd/main.go) use this to construct the GUI application +// so that gui.New() lives outside the processor package and the processor→gui +// dependency is limited to the Config type only. +func (r *CLIConfigResolver) GUIConfig() *gui.Config { + imageProvider := r.Flags.ImageAPI + if !r.Flags.ImageAPISpecified { + imageProvider = gui.DefaultConfig().ImageProvider + } + + openAIKey := cli.GetOpenAIKey() + googleAPIKey := cli.GetGoogleAPIKey() + translationProvider := translation.Provider(r.Config.TranslationProvider) + phoneticProvider := phonetic.Provider(r.Config.PhoneticProvider) + + phoneticFetcher := phonetic.NewFetcher(&phonetic.Config{ + Provider: phoneticProvider, + OpenAIKey: openAIKey, + GoogleAPIKey: googleAPIKey, + }) + translator := translation.NewTranslator(&translation.Config{ + Provider: translationProvider, + OpenAIKey: openAIKey, + GeminiModel: r.Config.TranslationGeminiModel, + }) + + return &gui.Config{ + AudioFormat: r.EffectiveAudioFormat(), + AudioProvider: r.AudioProviderName(), + ImageProvider: imageProvider, + OpenAIKey: openAIKey, + GoogleAPIKey: googleAPIKey, + NanoBananaModel: r.NanoBananaModelForRunMode(), + NanoBananaTextModel: r.NanoBananaTextModelForRunMode(), + GeminiTTSModel: r.GeminiTTSModel(), + GeminiVoice: r.GeminiVoice(), + TranslationProvider: translationProvider, + PhoneticProvider: phoneticProvider, + AutoPlay: !r.Flags.NoAutoPlay, // Invert the flag (--no-auto-play disables auto-play) + PhoneticFetcher: phoneticFetcher, + Translator: translator, + } +} + +// NanoBananaModelForRunMode resolves the NanoBanana image model, preferring +// the explicit CLI flag value when set, then the config-file value, then the +// package default. +func (r *CLIConfigResolver) NanoBananaModelForRunMode() string { + if r != nil && r.Flags != nil && r.Flags.NanoBananaModelSpecified { + if model := strings.TrimSpace(r.Flags.NanoBananaModel); model != "" { + return model + } + } + if r.Config.ImageNanoBananaModel != "" { + return r.Config.ImageNanoBananaModel + } + if r != nil && r.Flags != nil { + if model := strings.TrimSpace(r.Flags.NanoBananaModel); model != "" { + return model + } + } + return image.DefaultNanoBananaModel +} + +// NanoBananaTextModelForRunMode resolves the NanoBanana text (prompt) model +// using the same CLI-flag-over-config precedence as NanoBananaModelForRunMode. +func (r *CLIConfigResolver) NanoBananaTextModelForRunMode() string { + if r != nil && r.Flags != nil && r.Flags.NanoBananaTextModelSpecified { + if model := strings.TrimSpace(r.Flags.NanoBananaTextModel); model != "" { + return model + } + } + if r.Config.ImageNanoBananaTextModel != "" { + return r.Config.ImageNanoBananaTextModel + } + if r != nil && r.Flags != nil { + if model := strings.TrimSpace(r.Flags.NanoBananaTextModel); model != "" { + return model + } + } + return image.DefaultNanoBananaTextModel +} |
