-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprofile.go
More file actions
147 lines (132 loc) · 3.9 KB
/
Copy pathprofile.go
File metadata and controls
147 lines (132 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package main
import (
"bufio"
"encoding/json"
"fmt"
"io/fs"
"os"
"sort"
"strconv"
"strings"
)
type profileDef struct {
Description string `json:"description"`
Model string `json:"model,omitempty"`
Servers []string `json:"servers"`
}
type profilesConfig struct {
Core []string `json:"core"`
Profiles map[string]profileDef `json:"profiles"`
}
func loadProfiles(assets fs.FS) (profilesConfig, error) {
data, err := fs.ReadFile(assets, "defaults/profiles.json")
if err != nil {
return profilesConfig{}, fmt.Errorf("read profiles.json: %w", err)
}
var cfg profilesConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return profilesConfig{}, fmt.Errorf("parse profiles.json: %w", err)
}
return cfg, nil
}
// resolvedServers returns the full server list for a profile (core + profile-specific).
func (c profilesConfig) resolvedServers(name string) ([]string, error) {
p, ok := c.Profiles[name]
if !ok {
return nil, fmt.Errorf("unknown profile: %q", name)
}
seen := make(map[string]bool)
var servers []string
for _, s := range c.Core {
if !seen[s] {
seen[s] = true
servers = append(servers, s)
}
}
for _, s := range p.Servers {
if !seen[s] {
seen[s] = true
servers = append(servers, s)
}
}
sort.Strings(servers)
return servers, nil
}
// profileNames returns profile names in display order: "auto" first, "full"
// second, "minimal" last, the rest alphabetical in between. Sentinel names are
// only included when they actually exist in the loaded config, so a config
// missing one of them never produces a menu row with an empty description or a
// numeric choice that resolves to a non-existent profile.
func (c profilesConfig) profileNames() []string {
var middle []string
for k := range c.Profiles {
if k != "auto" && k != "full" && k != "minimal" {
middle = append(middle, k)
}
}
sort.Strings(middle)
var result []string
for _, lead := range []string{"auto", "full"} {
if _, ok := c.Profiles[lead]; ok {
result = append(result, lead)
}
}
result = append(result, middle...)
if _, ok := c.Profiles["minimal"]; ok {
result = append(result, "minimal")
}
return result
}
// selectProfile shows an interactive numbered menu on stderr and reads
// the user's choice from stdin. Returns the selected profile name.
func selectProfile(cfg profilesConfig) (string, error) {
return selectProfileWithDefault(cfg, "")
}
// selectProfileWithDefault is selectProfile with a caller-supplied
// "remembered" profile. An empty or unknown defaultName falls back to
// "auto" (the new default). The default is the value used on EOF
// or empty-line input.
func selectProfileWithDefault(cfg profilesConfig, defaultName string) (string, error) {
names := cfg.profileNames()
chosenDefault := "auto"
if defaultName != "" {
if _, ok := cfg.Profiles[defaultName]; ok {
chosenDefault = defaultName
}
}
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "Select a tool profile:")
fmt.Fprintln(os.Stderr)
for i, name := range names {
p := cfg.Profiles[name]
marker := " "
if name == chosenDefault {
marker = "*"
}
if name == "auto" {
fmt.Fprintf(os.Stderr, " %s %d) %-12s %s\n", marker, i+1, name, p.Description)
} else {
servers, _ := cfg.resolvedServers(name)
fmt.Fprintf(os.Stderr, " %s %d) %-12s %s (%d servers)\n", marker, i+1, name, p.Description, len(servers))
}
}
fmt.Fprintln(os.Stderr)
fmt.Fprintf(os.Stderr, "Profile [1-%s, name, or Enter for %s]: ", strconv.Itoa(len(names)), chosenDefault)
scanner := bufio.NewScanner(os.Stdin)
if !scanner.Scan() {
return chosenDefault, nil
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
return chosenDefault, nil
}
// Try as number first
if n, err := strconv.Atoi(input); err == nil && n >= 1 && n <= len(names) {
return names[n-1], nil
}
// Try as name
if _, ok := cfg.Profiles[input]; ok {
return input, nil
}
return "", fmt.Errorf("invalid profile: %q", input)
}