summaryrefslogtreecommitdiff
path: root/internal/utils.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-03 08:03:05 +0300
committerPaul Buetow <paul@buetow.org>2026-04-03 08:03:05 +0300
commit63bd86d8046949e80e5b1122a1fcf717f51915c6 (patch)
tree10bc72c97d1ba597bc82fb273987716eabf2e989 /internal/utils.go
parent5a1a6b863c3adaa8ec9d087ba130f7676fc9575b (diff)
tasks 00d, 006, 007: move test seams to struct fields, centralize Viper config, extract shared card-dir logic
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 <noreply@anthropic.com>
Diffstat (limited to 'internal/utils.go')
-rw-r--r--internal/utils.go59
1 files changed, 59 insertions, 0 deletions
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 {