Skip to main content

dnslib/cli/
completions.rs

1#[cfg(any(
2    feature = "technitium",
3    feature = "pangolin",
4    feature = "cloudflare",
5    feature = "unifi"
6))]
7pub fn generate_completions(shell: clap_complete::Shell) {
8    use clap::CommandFactory;
9    use clap_complete::generate;
10    use std::io::{self, Write};
11
12    use crate::cli::Cli;
13
14    let mut cmd = Cli::command();
15    let bin_name = std::env::current_exe()
16        .ok()
17        .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
18        .unwrap_or_else(|| cmd.get_name().to_string());
19    let fn_name = bin_name.replace('-', "_");
20
21    let mut out = io::stdout();
22
23    // For zsh, patch the generated output so --server specs point at our
24    // dynamic helper instead of the default (_default) completer.
25    if shell == clap_complete::Shell::Zsh {
26        let mut buf: Vec<u8> = Vec::new();
27        generate(shell, &mut cmd, &bin_name, &mut buf);
28        let raw = String::from_utf8_lossy(&buf);
29        let patched: String = raw
30            .lines()
31            .map(|line| {
32                if line.contains("'*--server=") {
33                    line.replace(":_default'", &format!(":_{fn_name}_server_ids'"))
34                } else {
35                    line.to_string()
36                }
37            })
38            .collect::<Vec<_>>()
39            .join("\n");
40        let patched = if raw.ends_with('\n') {
41            patched + "\n"
42        } else {
43            patched
44        };
45        out.write_all(patched.as_bytes()).ok();
46        let helper = format!(
47            "\n# Dynamic --server completion from config\n\
48             _{fn_name}_server_ids() {{\n\
49             \tlocal -a ids=(\"${{(@f)$({bin_name} _servers 2>/dev/null)}}\")\n\
50             \t_describe 'server ID' ids\n\
51             }}\n"
52        );
53        out.write_all(helper.as_bytes()).ok();
54        return;
55    }
56
57    generate(shell, &mut cmd, &bin_name, &mut out);
58
59    let dynamic = match shell {
60        clap_complete::Shell::Fish => format!(
61            "\n# Dynamic --server completion from config\n\
62             complete -e -c {bin_name} -l server\n\
63             complete -c {bin_name} -l server -r -a '({bin_name} _servers 2>/dev/null)'\n"
64        ),
65        clap_complete::Shell::Bash => format!(
66            "\n# Dynamic --server completion from config\n\
67             __{fn_name}_complete() {{\n\
68             \tlocal cur prev\n\
69             \tcur=\"${{COMP_WORDS[COMP_CWORD]}}\"\n\
70             \tprev=\"${{COMP_WORDS[COMP_CWORD-1]}}\"\n\
71             \tif [[ \"$prev\" == \"--server\" ]]; then\n\
72             \t\tmapfile -t COMPREPLY < <(compgen -W \"$({bin_name} _servers 2>/dev/null)\" -- \"$cur\")\n\
73             \t\treturn\n\
74             \tfi\n\
75             \t_{fn_name} \"$@\"\n\
76             }}\n\
77             complete -F __{fn_name}_complete {bin_name}\n"
78        ),
79        _ => String::new(),
80    };
81
82    if !dynamic.is_empty() {
83        out.write_all(dynamic.as_bytes()).ok();
84    }
85}