From 63bd86d8046949e80e5b1122a1fcf717f51915c6 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 3 Apr 2026 08:03:05 +0300 Subject: tasks 00d, 006, 007: move test seams to struct fields, centralize Viper config, extract shared card-dir logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task 00d: remove package-level var test seams from processor and gui packages; factory functions (newAudioProvider, newOpenAIImageClient, newNanoBananaImageClient) are now struct fields on Processor and Application, initialized with production defaults in constructors and replaced in tests without global mutation. task 006: add viperConfig struct captured once in NewProcessor; no method body calls viper.GetString/IsSet/GetFloat64 directly any more — all config-file values are accessed via p.viperCfg fields. task 007: extract FindCardDirectory and FindOrCreateCardDirectory into internal/utils.go; both Processor.findCardDirectory and Application.findCardDirectory now delegate to the shared implementation, which also handles the legacy _word.txt backward-compat fallback. Co-Authored-By: Claude Sonnet 4.6 --- internal/utils.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) (limited to 'internal/utils.go') diff --git a/internal/utils.go b/internal/utils.go index 47513ae..b83d46a 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -4,6 +4,8 @@ import ( "crypto/md5" "encoding/hex" "fmt" + "os" + "path/filepath" "strings" "time" ) @@ -23,6 +25,63 @@ func GenerateCardID(bulgarianWord string) string { return fmt.Sprintf("%d_%s", epochMillis, hashStr) } +// FindCardDirectory searches outputDir for a subdirectory whose word.txt +// (or legacy _word.txt) matches the given word. Returns the directory path +// or an empty string if not found. +func FindCardDirectory(outputDir, word string) string { + entries, err := os.ReadDir(outputDir) + if err != nil { + return "" + } + + for _, entry := range entries { + if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { + continue + } + + dirPath := filepath.Join(outputDir, entry.Name()) + wordFile := filepath.Join(dirPath, "word.txt") + + if data, err := os.ReadFile(wordFile); err == nil { + if strings.TrimSpace(string(data)) == word { + return dirPath + } + } else { + // Backward-compatible fallback: old format used _word.txt + wordFile = filepath.Join(dirPath, "_word.txt") + if data, err := os.ReadFile(wordFile); err == nil { + if strings.TrimSpace(string(data)) == word { + return dirPath + } + } + } + } + + return "" +} + +// FindOrCreateCardDirectory returns the existing card directory for word inside +// outputDir, or creates a new one with a generated card ID. It also writes +// word.txt so subsequent calls can find the directory. +func FindOrCreateCardDirectory(outputDir, word string) string { + if dir := FindCardDirectory(outputDir, word); dir != "" { + return dir + } + + cardID := GenerateCardID(word) + wordDir := filepath.Join(outputDir, cardID) + if err := os.MkdirAll(wordDir, 0755); err != nil { + fmt.Printf("Warning: failed to create word directory: %v\n", err) + return outputDir + } + + if err := os.WriteFile(filepath.Join(wordDir, "word.txt"), []byte(word), 0644); err != nil { + fmt.Printf("Warning: failed to save word metadata: %v\n", err) + } + + return wordDir +} + // SanitizeFilename creates a safe filename from a string. // Uses strings.Builder to avoid per-rune heap allocations. func SanitizeFilename(s string) string { -- cgit v1.2.3