env-hooks 0.1.4

Shell integration for automatic environment management
Documentation
use std::collections::HashSet;

use bstr::{B, BString, ByteSlice};
use shell_quote::Fish;

use crate::EnvVarsState;

const FISH_HOOK: &str = r#"
    function __{{.HookPrefix}}_export_eval --on-event fish_prompt;
        {{.ExportCommand}} | source;

        if test "${{.HookPrefix}}_fish_mode" != "disable_arrow";
            function __{{.HookPrefix}}_cd_hook --on-variable PWD;
                if test "${{.HookPrefix}}_fish_mode" = "eval_after_arrow";
                    set -g __{{.HookPrefix}}_export_again 0;
                else;
                    {{.ExportCommand}} | source;
                end;
            end;
        end;
    end;

    function __{{.HookPrefix}}_export_eval_2 --on-event fish_preexec;
        if set -q __{{.HookPrefix}}_export_again;
            set -e __{{.HookPrefix}}_export_again;
            {{.ExportCommand}} | source;
            echo;
        end;

        functions --erase __{{.HookPrefix}}_cd_hook;
    end;
"#;

pub fn hook(hook_prefix: impl AsRef<[u8]>, export_command: impl AsRef<[u8]>) -> BString {
    BString::from(FISH_HOOK)
        .replace("{{.HookPrefix}}", hook_prefix)
        .replace("{{.ExportCommand}}", export_command)
        .into()
}

pub fn export(
    env_vars_state: EnvVarsState,
    semicolon_delimited_env_vars: Option<&HashSet<String>>,
) -> BString {
    let exports = env_vars_state
        .iter()
        .map(|(key, state)| {
            if let Some(value) = state {
                export_var(key, value, semicolon_delimited_env_vars)
            } else {
                unset_var(key)
            }
        })
        .collect::<Vec<_>>();
    bstr::join("\n", exports).into()
}

fn export_var(
    key: &str,
    value: &str,
    semicolon_delimited_env_vars: Option<&HashSet<String>>,
) -> BString {
    let script = bstr::join(" ", [B("set -x -g"), &Fish::quote_vec(key)]);
    let value = if let Some(sdev) = semicolon_delimited_env_vars
        && sdev.contains(key)
    {
        let value_parts = value.split(':').map(Fish::quote_vec).collect::<Vec<_>>();
        bstr::join(" ", value_parts)
    } else {
        Fish::quote_vec(value)
    };
    bstr::concat([&bstr::join(" ", [script, value]), B(";")]).into()
}

fn unset_var(key: &str) -> BString {
    bstr::concat([
        &bstr::join(" ", [B("set -e -g"), &Fish::quote_vec(key)]),
        B(";"),
    ])
    .into()
}