youtube-legend-cli 0.4.0

Non-interactive Rust CLI that downloads YouTube subtitles through third-party providers, using a native Unix stdin/stdout interface.
//! `completions` and `man`: emit shell completion scripts and the roff
//! manual page from the command tree clap already owns.
//!
//! Both surfaces derive their content from [`crate::cli::Cli`], so they
//! can never describe a flag the binary does not accept. That is the
//! whole reason they live here instead of in a build script: a build
//! script would run against the sources rather than the parsed command,
//! and it would also hide the two crates from
//! `unused_crate_dependencies`, which reports a false positive for a
//! dependency reached only from `build.rs`.

use crate::error::{AppError, AppResult};
use crate::io::write_subtitle_to_stdout;
use clap::CommandFactory;
use std::process::ExitCode;

/// Name the generated artefacts announce as the binary.
///
/// Taken from the manifest so a rename cannot leave the completion
/// script pointing at a command that no longer exists.
const BIN_NAME: &str = env!("CARGO_PKG_NAME");

/// Write the completion script for `shell` to stdout.
///
/// Output goes through [`write_subtitle_to_stdout`] rather than
/// `println!` so a closed pipe surfaces as [`AppError::Io`] with the
/// `BrokenPipe` kind, which is what sustains the exit 141 contract for
/// `… completions bash | head -1`.
///
/// # Errors
///
/// - [`AppError::Io`] on stdout write or flush failure.
pub async fn run_completions(shell: clap_complete::Shell) -> AppResult<ExitCode> {
    let mut command = crate::cli::Cli::command();
    let mut buffer: Vec<u8> = Vec::new();
    clap_complete::generate(shell, &mut command, BIN_NAME, &mut buffer);
    write_subtitle_to_stdout(&buffer).await?;
    Ok(ExitCode::SUCCESS)
}

/// Write the section 1 manual page to stdout in roff format.
///
/// # Errors
///
/// - [`AppError::Io`] when rendering fails or stdout cannot be written.
pub async fn run_man() -> AppResult<ExitCode> {
    let command = crate::cli::Cli::command();
    let mut buffer: Vec<u8> = Vec::new();
    clap_mangen::Man::new(command)
        .render(&mut buffer)
        .map_err(AppError::Io)?;
    write_subtitle_to_stdout(&buffer).await?;
    Ok(ExitCode::SUCCESS)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Every shell clap knows must render a non-empty script that names
    /// the binary. A shell that renders nothing would ship a completion
    /// file which silently completes to the empty set.
    #[test]
    fn every_shell_renders_a_script_naming_the_binary() {
        for shell in [
            clap_complete::Shell::Bash,
            clap_complete::Shell::Zsh,
            clap_complete::Shell::Fish,
            clap_complete::Shell::Elvish,
            clap_complete::Shell::PowerShell,
        ] {
            let mut command = crate::cli::Cli::command();
            let mut buffer: Vec<u8> = Vec::new();
            clap_complete::generate(shell, &mut command, BIN_NAME, &mut buffer);
            let script = String::from_utf8(buffer).expect("completion script is not UTF-8");
            assert!(!script.is_empty(), "{shell} rendered an empty script");
            assert!(
                script.contains(BIN_NAME),
                "{shell} script does not name the binary"
            );
        }
    }

    /// The manual page must render and mention the binary name, which is
    /// what `man` uses for the NAME section.
    #[test]
    fn man_page_renders_and_names_the_binary() {
        let command = crate::cli::Cli::command();
        let mut buffer: Vec<u8> = Vec::new();
        clap_mangen::Man::new(command)
            .render(&mut buffer)
            .expect("man page failed to render");
        let page = String::from_utf8(buffer).expect("man page is not UTF-8");
        assert!(!page.is_empty(), "man page is empty");
        assert!(page.contains(BIN_NAME), "man page does not name the binary");
    }

    /// The completion script must mention a flag that only this binary
    /// has, proving the script was derived from our command tree rather
    /// than from a clap default.
    #[test]
    fn completion_script_carries_our_own_flags() {
        let mut command = crate::cli::Cli::command();
        let mut buffer: Vec<u8> = Vec::new();
        clap_complete::generate(
            clap_complete::Shell::Bash,
            &mut command,
            BIN_NAME,
            &mut buffer,
        );
        let script = String::from_utf8(buffer).expect("completion script is not UTF-8");
        assert!(
            script.contains("--print-schema"),
            "completion script does not carry --print-schema"
        );
    }
}