cnf 0.6.1

Distribution-agnostic 'command not found'-handler
Documentation
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: (C) 2023 Andreas Hartmann <hartan@7x.de>
// This file is part of cnf, available at <https://gitlab.com/hartang/rust/cnf>

//! # CLI defintion
use clap::Parser;

static AFTER_HELP_TEXT: &str = concat!(
    "Copyright (C) 2023 Andreas Hartmann <hartan@7x.de>
Licensed under the GNU General Public License v3.0+
Read the docs at: https://docs.rs/",
    env!("CARGO_PKG_NAME"),
    "/",
    env!("CARGO_PKG_VERSION"),
    "/",
    env!("CARGO_PKG_NAME"),
    "/"
);

/// Targets that hooks can be generated for.
#[derive(clap::ValueEnum, Debug, Clone)]
pub(crate) enum Hooks {
    Bash,
    Zsh,
    Help,
}

#[derive(Parser, Debug)]
#[doc(hidden)]
#[command(
    author,
    version,
    about,
    after_help = AFTER_HELP_TEXT,
    arg_required_else_help(true),
    trailing_var_arg(true)
)]
pub struct Args {
    /// source environment to run alias from (optional)
    #[arg(long, requires = "alias_target_env")]
    pub(crate) alias_source_env: Option<String>,

    /// target environment to translate alias into
    #[arg(long)]
    pub(crate) alias_target_env: Option<String>,

    /// whether to run an alias with privileges in the target environment
    #[arg(long, requires = "alias_target_env")]
    pub(crate) alias_privileged: bool,

    /// run alias interactively (for privileged execution authentication)
    #[arg(long, requires = "alias_target_env")]
    pub(crate) alias_interactive: bool,

    /// print/install shell hooks for automating calls to cnf
    #[arg(long, value_enum, exclusive = true)]
    pub(crate) hooks: Option<Hooks>,

    /// enable debug logging
    #[arg(long, short, default_value_t = false)]
    pub(crate) debug: bool,

    /// command to search for with arguments
    #[arg(required = true)]
    pub(crate) command: Vec<String>,
}

pub(crate) fn install_hook(hook: Hooks) {
    let alias_path = crate::directories::get().aliases();
    if matches!(hook, Hooks::Help) {
        println!(
            r#"
                         == Installing Shell Hooks ==

cnf on its own isn't nearly half as useful as it could be. To unleash its full
potential, you will likely want to install the hooks for your current shell.
The hooks perform the following tasks:

1. Ensure `cnf` is always called when your shell doesn't find a command, and
   pass the command along with all its' arguments to `cnf` instead.
2. Extend your `$PATH` with the configured location for `cnf` command aliases
   so aliases created through the UI are available to your shell.
3. Create the `sual` function alias which works like `sudo` but specifically to
   execute command aliases. Regular `sudo` will not have the desired effect for
   command aliases, so use `sual` instead.

To get started, run this command again, giving it either 'bash' or 'zsh' as
argument, depending on your current shell. Pass the generated output to `eval`
to update your current shells configuration temporarily. Add a snippet like
this:

    eval "$(cnf --hooks <SHELL>)"

to your shell configuration (`~/.bashrc`, `~/.zshrc`, ...) to automatically
apply the configuration in new shell instances."#
        );
        return;
    }

    println!(
        r#"

# Added by 'cnf'
if command -v "cnf" &>/dev/null; then
    # pick up aliases from configured alias path
    CNF_ALIASES="{alias_path}"
    [[ "$PATH" != *"$CNF_ALIASES"* ]] && export PATH="$PATH:$CNF_ALIASES"

    # automatically call cnf when a command isn't found in $PATH
    function {function_name} {{
        cnf "$@"
    }}

    function sual {{
        {alias_privileged_env}=1 "$@"
    }}
fi"#,
        function_name = match hook {
            Hooks::Zsh => "command_not_found_handler",
            Hooks::Bash => "command_not_found_handle",
            _ => unreachable!(),
        },
        alias_path = alias_path.display(),
        alias_privileged_env = crate::Env::AliasPrivileged
    );
}