package setup import ( "errors" "fmt" "sort" "strconv" "strings" "charm.land/huh/v2" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" ) func editMulti(cfg *config.Config) error { multiplexer := cfg.Multi.Multiplexer if multiplexer == "" { multiplexer = "auto" } perWindow := "" if cfg.Multi.PanesPerWindow > 0 { perWindow = strconv.Itoa(cfg.Multi.PanesPerWindow) } groups := formatGroups(cfg.Groups) err := huh.NewForm(huh.NewGroup( huh.NewSelect[string](). Title("Multiplexer"). Description("netssh multi opens hosts side by side. auto uses the one you are in, else tmux, else zellij."). Options(huh.NewOption("auto", "auto"), huh.NewOption("tmux", "tmux"), huh.NewOption("zellij", "zellij")). Value(&multiplexer), huh.NewInput(). Title("Panes per window"). Description(fmt.Sprintf("More hosts open in further windows or tabs. Leave empty for %d.", config.DefaultPanesPerWindow)). Value(&perWindow). Validate(func(s string) error { if s = strings.TrimSpace(s); s == "" { return nil } if n, err := strconv.Atoi(s); err != nil || n < 1 || n > 64 { return errors.New("must be a number between 1 and 64") } return nil }), huh.NewText(). Title("Groups"). Description("One per line: NAME: host, tag:TAG, cluster:NAME, @other — open with netssh multi @NAME."). Lines(4). Value(&groups). Validate(func(s string) error { _, err := parseGroups(s) return err }), ).Title("Multi-session")).Run() if err != nil { return err } cfg.Multi.Multiplexer = multiplexer if multiplexer == "auto" { cfg.Multi.Multiplexer = "" } cfg.Multi.PanesPerWindow, _ = strconv.Atoi(strings.TrimSpace(perWindow)) cfg.Groups, _ = parseGroups(groups) return nil } // parseGroups reads "NAME: sel1, sel2" lines into groups:. func parseGroups(s string) (map[string][]string, error) { var out map[string][]string for _, line := range parseLines(s) { name, members, ok := strings.Cut(line, ":") name = strings.ToLower(strings.TrimSpace(name)) // "tag:x" alone has no group name before the first colon worth keeping. if !ok || name == "" || name == "tag" || name == "cluster" { return nil, fmt.Errorf("%q: expected NAME: host, tag:TAG, …", line) } var selectors []string for _, field := range strings.FieldsFunc(members, func(r rune) bool { return r == ',' || r == ' ' || r == '\t' }) { selectors = append(selectors, field) } if out == nil { out = make(map[string][]string) } out[name] = append(out[name], selectors...) } if err := (&config.Config{Groups: out}).ValidateMulti(); err != nil { return nil, err } return out, nil } func formatGroups(groups map[string][]string) string { names := make([]string, 0, len(groups)) for name := range groups { names = append(names, name) } sort.Strings(names) lines := make([]string, 0, len(names)) for _, name := range names { lines = append(lines, name+": "+strings.Join(groups[name], ", ")) } return strings.Join(lines, "\n") }