1package config
2
3import (
4 "sort"
5 "strings"
6)
7
8// Presets loads all discovered config files and returns the usable preset
9// names derived from the top-level TOML sections. Raw section fragments
10// like "@cloud" or "home@" are combined into invocable presets like
11// "home@cloud".
12func Presets() []string {
13 files := DiscoverFiles()
14 return presetsFrom(files)
15}
16
17// presetsFrom is the testable core of Presets. It categorises top-level
18// sections and generates the list of presets a user can actually invoke.
19//
20// The returned list is ordered: plain presets first, then prefix@suffix
21// combinations, then bare suffixes. Within each group, entries are sorted
22// alphabetically.
23func presetsFrom(files []string) []string {
24 raw, err := loadFiles(files)
25 if err != nil {
26 return nil
27 }
28
29 var (
30 plain []string // no "@" — e.g. "archive"
31 prefixes []string // ends with "@" — e.g. "home@"
32 suffixes []string // starts with "@" — e.g. "@cloud"
33 full = map[string]bool{} // both sides — e.g. "home@cloud"
34 )
35
36 for key := range raw {
37 if key == "global" || key == "vars" {
38 continue
39 }
40 // Sub-sections like "home.backup" appear as nested maps under
41 // their parent; they are never top-level keys themselves after
42 // TOML parsing, so this guard is just defensive.
43 if strings.Contains(key, ".") {
44 continue
45 }
46
47 switch {
48 case strings.HasPrefix(key, "@"):
49 suffixes = append(suffixes, key)
50 case strings.HasSuffix(key, "@"):
51 prefixes = append(prefixes, key)
52 case strings.Contains(key, "@"):
53 full[key] = true
54 default:
55 plain = append(plain, key)
56 }
57 }
58
59 sort.Strings(plain)
60 sort.Strings(prefixes)
61 sort.Strings(suffixes)
62
63 seen := make(map[string]bool)
64 var out []string
65
66 add := func(s string) {
67 if !seen[s] {
68 seen[s] = true
69 out = append(out, s)
70 }
71 }
72
73 // 1. Plain presets.
74 for _, p := range plain {
75 add(p)
76 }
77
78 // 2. Prefix × suffix combinations.
79 for _, pfx := range prefixes {
80 for _, sfx := range suffixes {
81 add(pfx[:len(pfx)-1] + sfx) // "home@" + "@cloud" → "home@cloud"
82 }
83 }
84
85 // 3. Explicit full split presets that weren't already generated.
86 fullSorted := make([]string, 0, len(full))
87 for k := range full {
88 fullSorted = append(fullSorted, k)
89 }
90 sort.Strings(fullSorted)
91 for _, f := range fullSorted {
92 add(f)
93 }
94
95 // 4. Bare suffixes (usable with just global defaults).
96 for _, sfx := range suffixes {
97 add(sfx)
98 }
99
100 return out
101}
102
103// Prefixes returns the distinct prefix fragments (sections ending with "@")
104// found in the config. These are suitable for tab-completion of the first
105// positional argument.
106func Prefixes() []string {
107 files := DiscoverFiles()
108 return prefixesFrom(files)
109}
110
111func prefixesFrom(files []string) []string {
112 raw, err := loadFiles(files)
113 if err != nil {
114 return nil
115 }
116
117 var out []string
118 for key := range raw {
119 if strings.HasSuffix(key, "@") {
120 out = append(out, key)
121 }
122 }
123 sort.Strings(out)
124 return out
125}
126
127// Suffixes returns the distinct suffix fragments (sections starting with "@")
128// found in the config. These are suitable for completing the part after "@"
129// when the user has typed a prefix.
130func Suffixes() []string {
131 files := DiscoverFiles()
132 return suffixesFrom(files)
133}
134
135func suffixesFrom(files []string) []string {
136 raw, err := loadFiles(files)
137 if err != nil {
138 return nil
139 }
140
141 var out []string
142 for key := range raw {
143 if strings.HasPrefix(key, "@") {
144 out = append(out, key)
145 }
146 }
147 sort.Strings(out)
148 return out
149}