22// SPDX-License-Identifier: Apache-2.0
33
44//! Shell completion generation and user-local installation helpers.
5+ //!
6+ //! This module owns both ways users interact with completion scripts:
7+ //! generating a script to stdout for manual shell integration and installing a
8+ //! script into a user-local shell-specific completion directory. It does not
9+ //! modify shell startup files; instead, install commands print the next action
10+ //! needed to activate the generated file when a shell requires one.
511
612use crate :: args:: { CompletionArgs , CompletionCommand , CompletionInstallArgs } ;
713use crate :: error:: CliError ;
@@ -13,13 +19,22 @@ use std::fs;
1319use std:: io:: Write ;
1420use std:: path:: { Path , PathBuf } ;
1521
22+ /// Dispatches `dfctl completions` commands.
23+ ///
24+ /// With a shell argument, this writes a generated completion script to stdout.
25+ /// With the `install` subcommand, it writes the generated script into the
26+ /// requested or default per-user completion directory.
1627pub ( crate ) fn run ( stdout : & mut dyn Write , args : CompletionArgs ) -> Result < ( ) , CliError > {
1728 match args. command {
1829 Some ( CompletionCommand :: Install ( args) ) => install ( stdout, args) ,
1930 None => generate ( stdout, args) ,
2031 }
2132}
2233
34+ /// Generates a shell completion script to stdout.
35+ ///
36+ /// The command tree is rebuilt from Clap for every invocation so completion
37+ /// scripts reflect the currently executing binary.
2338fn generate ( stdout : & mut dyn Write , args : CompletionArgs ) -> Result < ( ) , CliError > {
2439 let shell = args. shell . ok_or_else ( || {
2540 CliError :: invalid_usage ( format ! (
@@ -32,6 +47,12 @@ fn generate(stdout: &mut dyn Write, args: CompletionArgs) -> Result<(), CliError
3247 Ok ( ( ) )
3348}
3449
50+ /// Installs a generated completion script into a user-local directory.
51+ ///
52+ /// Installation creates the target directory if needed, writes the completion
53+ /// file using `clap_complete`, and prints a shell-specific activation note. It
54+ /// intentionally avoids editing `.bashrc`, `.zshrc`, PowerShell profiles, or
55+ /// any other user startup file.
3556fn install ( stdout : & mut dyn Write , args : CompletionInstallArgs ) -> Result < ( ) , CliError > {
3657 let dir = args
3758 . dir
@@ -73,11 +94,18 @@ fn install(stdout: &mut dyn Write, args: CompletionInstallArgs) -> Result<(), Cl
7394 Ok ( ( ) )
7495}
7596
97+ /// Resolves the default installation directory from the current process
98+ /// environment.
7699fn default_install_dir ( shell : Shell ) -> Option < PathBuf > {
77100 let env = EnvPaths :: read ( ) ;
78101 default_install_dir_from ( shell, & env)
79102}
80103
104+ /// Maps a shell to the per-user directory where completion files are normally
105+ /// discovered.
106+ ///
107+ /// This helper receives an explicit `EnvPaths` value so tests can validate path
108+ /// selection without mutating process environment variables.
81109fn default_install_dir_from ( shell : Shell , env : & EnvPaths ) -> Option < PathBuf > {
82110 match shell {
83111 Shell :: Bash => Some ( env. data_home ( ) ?. join ( "bash-completion/completions" ) ) ,
@@ -89,6 +117,10 @@ fn default_install_dir_from(shell: Shell, env: &EnvPaths) -> Option<PathBuf> {
89117 }
90118}
91119
120+ /// Returns the post-install activation guidance for a shell.
121+ ///
122+ /// Some shells load files from the install directory automatically, while
123+ /// others need a source command or startup-file configuration from the user.
92124fn activation_note ( shell : Shell , dir : & Path , path : & Path ) -> Option < String > {
93125 let path = shell_quote ( path) ;
94126 let dir = shell_quote ( dir) ;
@@ -110,19 +142,31 @@ fn activation_note(shell: Shell, dir: &Path, path: &Path) -> Option<String> {
110142 }
111143}
112144
145+ /// Quotes a path for display in shell snippets.
146+ ///
147+ /// The output is intended for human instructions, not for launching a process.
113148fn shell_quote ( path : & Path ) -> String {
114149 let raw = path. display ( ) . to_string ( ) ;
115150 format ! ( "'{}'" , raw. replace( '\'' , "'\" '\" '" ) )
116151}
117152
153+ /// Environment-derived base directories used to resolve completion install
154+ /// paths.
155+ ///
156+ /// The values are kept in a struct so path resolution can be tested with
157+ /// synthetic environments and without changing global process state.
118158#[ derive( Debug , Clone , Default ) ]
119159struct EnvPaths {
160+ /// XDG configuration root, typically `$HOME/.config` when unset.
120161 xdg_config_home : Option < PathBuf > ,
162+ /// XDG data root, typically `$HOME/.local/share` when unset.
121163 xdg_data_home : Option < PathBuf > ,
164+ /// User home directory used as the fallback base for XDG paths.
122165 home : Option < PathBuf > ,
123166}
124167
125168impl EnvPaths {
169+ /// Reads relevant environment variables and normalizes empty values away.
126170 fn read ( ) -> Self {
127171 Self {
128172 xdg_config_home : non_empty_env_path ( "XDG_CONFIG_HOME" ) ,
@@ -131,23 +175,27 @@ impl EnvPaths {
131175 }
132176 }
133177
178+ /// Returns the effective user configuration directory.
134179 fn config_home ( & self ) -> Option < PathBuf > {
135180 self . xdg_config_home
136181 . clone ( )
137182 . or_else ( || self . home . as_ref ( ) . map ( |home| home. join ( ".config" ) ) )
138183 }
139184
185+ /// Returns the effective user data directory.
140186 fn data_home ( & self ) -> Option < PathBuf > {
141187 self . xdg_data_home
142188 . clone ( )
143189 . or_else ( || self . home . as_ref ( ) . map ( |home| home. join ( ".local/share" ) ) )
144190 }
145191}
146192
193+ /// Reads an environment variable as a path, ignoring unset and empty values.
147194fn non_empty_env_path ( name : & str ) -> Option < PathBuf > {
148195 std:: env:: var_os ( name) . and_then ( non_empty_path)
149196}
150197
198+ /// Converts an `OsString` into a `PathBuf` only when it contains a value.
151199fn non_empty_path ( value : OsString ) -> Option < PathBuf > {
152200 if value. is_empty ( ) {
153201 None
0 commit comments