package hook import ( "fmt" "os" "path/filepath" "strings" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/storage" ) const completionMarker = "# netssh command alias completion — generated automatically" // SyncCommandCompletion installs lazy loaders for the configured command alias. // The loaders obtain the canonical completion from the current netssh binary, // so aliases configured by the wizard work without a separate completion install. func SyncCommandCompletion(alias string) error { if err := config.ValidateCommandAlias(alias); err != nil { return err } statePath := filepath.Join(filepath.Dir(AliasesPath()), "completion_alias") data, err := os.ReadFile(statePath) if err != nil && !os.IsNotExist(err) { return err } previous := strings.TrimSpace(string(data)) if err := config.ValidateCommandAlias(previous); err != nil { return fmt.Errorf("invalid completion alias state: %w", err) } if alias == "" && previous == "" { return nil } for _, shell := range []string{"bash", "zsh", "fish"} { if previous != "" && previous != alias { path := commandCompletionPath(shell, previous) data, err := os.ReadFile(path) if err != nil && !os.IsNotExist(err) { return err } // Only delete files owned by netssh; preserve any user replacements. if strings.HasPrefix(string(data), completionMarker+"\n") || strings.HasPrefix(string(data), "#compdef "+previous+"\n"+completionMarker+"\n") { if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return err } } } if alias == "" { continue } if shell == "fish" { if _, err := os.Stat(FishConfigDir()); os.IsNotExist(err) { continue } else if err != nil { return err } } path := commandCompletionPath(shell, alias) script := commandCompletionScript(shell, alias) existing, err := os.ReadFile(path) if err != nil && !os.IsNotExist(err) { return err } if string(existing) == script { continue } if err := storage.WriteFile(path, []byte(script), 0o644); err != nil { return fmt.Errorf("writing %s alias completion: %w", shell, err) } } if alias == "" { if err := os.Remove(statePath); err != nil && !os.IsNotExist(err) { return err } return nil } return storage.WriteFile(statePath, []byte(alias+"\n"), 0o600) } func commandCompletionPath(shell, alias string) string { switch shell { case "bash": return filepath.Join(os.Getenv("HOME"), ".local", "share", "bash-completion", "completions", alias) case "zsh": return filepath.Join(ZshFuncDir(), "_"+alias) default: return filepath.Join(FishConfigDir(), "completions", alias+".fish") } } func commandCompletionScript(shell, alias string) string { switch shell { case "bash": return fmt.Sprintf(`%s source <(netssh completion bash) if [[ $(type -t compopt) == builtin ]]; then complete -o default -F __start_netssh %s else complete -o default -o nospace -F __start_netssh %s fi `, completionMarker, alias, alias) case "zsh": return fmt.Sprintf(`#compdef %s %s source <(netssh completion zsh) compdef _netssh %s _netssh "$@" `, alias, completionMarker, alias) default: return fmt.Sprintf(`%s netssh completion fish | source complete --command %s --wraps netssh `, completionMarker, alias) } }