From c302c29d4fc6d8be1ac7873c4e12b3fde6642143 Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Mon, 13 Apr 2026 17:30:33 -0700 Subject: [PATCH] Add autofill app binding helpers --- internal/autofillcache/bindings.go | 87 ++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 internal/autofillcache/bindings.go diff --git a/internal/autofillcache/bindings.go b/internal/autofillcache/bindings.go new file mode 100644 index 0000000..e6078cc --- /dev/null +++ b/internal/autofillcache/bindings.go @@ -0,0 +1,87 @@ +package autofillcache + +import ( + "encoding/json" + "os" + "path/filepath" + "slices" + "strings" + "time" +) + +type BindingsFile struct { + UpdatedAt string `json:"updatedAt"` + Apps map[string]string `json:"apps,omitempty"` +} + +func ReadBindings(path string) (BindingsFile, error) { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return BindingsFile{}, nil + } + return BindingsFile{}, err + } + var bindings BindingsFile + if err := json.Unmarshal(data, &bindings); err != nil { + return BindingsFile{}, err + } + if bindings.Apps == nil { + bindings.Apps = make(map[string]string) + } + return bindings, nil +} + +func RememberBinding(path, rawTarget, entryID string, now time.Time) error { + bindings, err := ReadBindings(path) + if err != nil { + return err + } + if bindings.Apps == nil { + bindings.Apps = make(map[string]string) + } + target := strings.TrimSpace(rawTarget) + id := strings.TrimSpace(entryID) + if target == "" || id == "" { + return nil + } + bindings.Apps[target] = id + bindings.UpdatedAt = now.UTC().Format(time.RFC3339) + data, err := json.MarshalIndent(bindings, "", " ") + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +func ResolveWithBindings(cache File, bindings BindingsFile, rawTarget string) MatchResult { + target := strings.TrimSpace(rawTarget) + if entryID := strings.TrimSpace(bindings.Apps[target]); entryID != "" { + for _, entry := range cache.Entries { + if entry.ID == entryID { + return MatchResult{Status: MatchStatusFound, Entry: entry} + } + } + } + return Resolve(cache, rawTarget) +} + +func ChooserCandidates(cache File, rawTarget string) []Entry { + if result := Resolve(cache, rawTarget); result.Status == MatchStatusFound { + return []Entry{result.Entry} + } + candidates := append([]Entry(nil), cache.Entries...) + slices.SortFunc(candidates, func(left, right Entry) int { + if cmp := strings.Compare(strings.ToLower(strings.TrimSpace(left.Title)), strings.ToLower(strings.TrimSpace(right.Title))); cmp != 0 { + return cmp + } + if cmp := strings.Compare(strings.ToLower(strings.Join(left.Path, "/")), strings.ToLower(strings.Join(right.Path, "/"))); cmp != 0 { + return cmp + } + return strings.Compare(left.ID, right.ID) + }) + return candidates +}