cli_xtask/subcommand/
dist_build_completion.rs

1use std::fmt;
2
3use cargo_metadata::camino::{Utf8Path, Utf8PathBuf};
4use clap_complete::Generator;
5
6use crate::{config::Config, fs::ToRelative, Result, Run};
7
8/// Arguments definition of the `dist-build-completion` subcommand.
9#[cfg_attr(doc, doc = include_str!("../../doc/cargo-xtask-dist-build-completion.md"))]
10#[derive(Debug, Clone, Default, clap::Args)]
11#[non_exhaustive]
12pub struct DistBuildCompletion {}
13
14impl Run for DistBuildCompletion {
15    fn run(&self, config: &Config) -> Result<()> {
16        self.run(config)
17    }
18}
19
20#[derive(Debug, Clone, Copy)]
21enum Shell {
22    Bash,
23    Elvish,
24    Fish,
25    #[allow(clippy::enum_variant_names)]
26    PowerShell,
27    Zsh,
28    Nushell,
29}
30
31impl fmt::Display for Shell {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Shell::Bash => fmt::Display::fmt(&clap_complete::Shell::Bash, f),
35            Shell::Elvish => fmt::Display::fmt(&clap_complete::Shell::Elvish, f),
36            Shell::Fish => fmt::Display::fmt(&clap_complete::Shell::Fish, f),
37            Shell::PowerShell => fmt::Display::fmt(&clap_complete::Shell::PowerShell, f),
38            Shell::Zsh => fmt::Display::fmt(&clap_complete::Shell::Zsh, f),
39            Shell::Nushell => fmt::Display::fmt("nushell", f),
40        }
41    }
42}
43
44impl Generator for Shell {
45    fn file_name(&self, name: &str) -> String {
46        match self {
47            Shell::Bash => Generator::file_name(&clap_complete::Shell::Bash, name),
48            Shell::Elvish => Generator::file_name(&clap_complete::Shell::Elvish, name),
49            Shell::Fish => Generator::file_name(&clap_complete::Shell::Fish, name),
50            Shell::PowerShell => Generator::file_name(&clap_complete::Shell::PowerShell, name),
51            Shell::Zsh => Generator::file_name(&clap_complete::Shell::Zsh, name),
52            Shell::Nushell => Generator::file_name(&clap_complete_nushell::Nushell, name),
53        }
54    }
55
56    fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
57        match self {
58            Shell::Bash => Generator::generate(&clap_complete::Shell::Bash, cmd, buf),
59            Shell::Elvish => Generator::generate(&clap_complete::Shell::Elvish, cmd, buf),
60            Shell::Fish => Generator::generate(&clap_complete::Shell::Fish, cmd, buf),
61            Shell::PowerShell => Generator::generate(&clap_complete::Shell::PowerShell, cmd, buf),
62            Shell::Zsh => Generator::generate(&clap_complete::Shell::Zsh, cmd, buf),
63            Shell::Nushell => Generator::generate(&clap_complete_nushell::Nushell, cmd, buf),
64        }
65    }
66}
67
68impl DistBuildCompletion {
69    /// Runs the `dist-build-completion` subcommand.
70    #[tracing::instrument(name = "dist-build-completion", skip_all, err)]
71    pub fn run(&self, config: &Config) -> Result<()> {
72        tracing::info!("Building shell completion files...");
73
74        let Self {} = self;
75        let config = config.dist()?;
76
77        let out_dir = config.dist_working_directory(None).join("completion");
78        crate::fs::remove_dir(&out_dir)?;
79
80        let shells = [
81            Shell::Bash,
82            Shell::Elvish,
83            Shell::Fish,
84            Shell::PowerShell,
85            Shell::Zsh,
86            Shell::Nushell,
87        ];
88
89        for package in config.packages() {
90            for target in package.targets() {
91                let target_name = target.name();
92                if let Some(cmd) = target.command() {
93                    for shell in shells {
94                        generate(shell, cmd, target_name, &out_dir)?;
95                    }
96                }
97            }
98        }
99
100        Ok(())
101    }
102}
103
104fn generate(
105    shell: Shell,
106    cmd: &clap::Command,
107    bin_name: &str,
108    out_dir: &Utf8Path,
109) -> Result<Utf8PathBuf> {
110    crate::fs::create_dir(out_dir)?;
111    let path = clap_complete::generate_to(shell, &mut cmd.clone(), bin_name, out_dir)?;
112    let path = Utf8PathBuf::try_from(path)?;
113    tracing::info!("Generated {shell} completion file: {}", path.to_relative());
114    Ok(path)
115}