presets.go

  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}