nils-git-cli 0.7.3

CLI crate for nils-git-cli in the nils-cli workspace.
Documentation
use anyhow::Result;
use nils_common::clipboard::{ClipboardOutcome, ClipboardPolicy, ClipboardTool, copy_best_effort};
use std::env;

const CLIPBOARD_TOOL_ORDER: [ClipboardTool; 4] = [
    ClipboardTool::Pbcopy,
    ClipboardTool::WlCopy,
    ClipboardTool::Xclip,
    ClipboardTool::Xsel,
];

pub fn set_clipboard_best_effort(text: &str) -> Result<()> {
    if env::var("GIT_CLI_FIXTURE_CLIPBOARD_MODE").ok().as_deref() == Some("missing") {
        eprintln!("⚠️  No clipboard tool found (requires pbcopy, wl-copy, xclip, or xsel)");
        return Ok(());
    }

    let policy = ClipboardPolicy::new(&CLIPBOARD_TOOL_ORDER);
    if matches!(
        copy_best_effort(text, &policy),
        ClipboardOutcome::SkippedNoTool | ClipboardOutcome::SkippedFailure
    ) {
        eprintln!("⚠️  No clipboard tool found (requires pbcopy, wl-copy, xclip, or xsel)");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use nils_test_support::{EnvGuard, GlobalStateLock, StubBinDir, prepend_path};
    use pretty_assertions::assert_eq;
    use std::fs;
    use tempfile::TempDir;

    fn write_clipboard_stub(stubs: &StubBinDir, name: &str) {
        stubs.write_exe(
            name,
            &format!(
                r#"#!/bin/bash
set -euo pipefail
chosen="${{CLIPBOARD_TOOL_CHOSEN:?CLIPBOARD_TOOL_CHOSEN is required}}"
payload="${{CLIPBOARD_PAYLOAD_OUT:?CLIPBOARD_PAYLOAD_OUT is required}}"
printf "%s" "{name}" > "$chosen"
/bin/cat > "$payload"
"#
            ),
        );
    }

    #[test]
    fn set_clipboard_best_effort_prefers_pbcopy_when_present() {
        let lock = GlobalStateLock::new();

        let stubs = StubBinDir::new();
        let out_dir = TempDir::new().expect("tempdir");
        let out_path = out_dir.path().join("pbcopy.out");

        stubs.write_exe(
            "pbcopy",
            r#"#!/bin/bash
set -euo pipefail
out="${PB_COPY_OUT:?PB_COPY_OUT is required}"
/bin/cat > "$out"
"#,
        );

        let _path_guard: EnvGuard = prepend_path(&lock, stubs.path());
        let out_path_str = out_path.to_string_lossy();
        let _out_guard = EnvGuard::set(&lock, "PB_COPY_OUT", out_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        let out = fs::read_to_string(out_path).expect("read stub output");
        assert_eq!(out, "hello");
    }

    #[test]
    fn set_clipboard_best_effort_prefers_pbcopy_over_other_tools() {
        let lock = GlobalStateLock::new();
        let stubs = StubBinDir::new();
        write_clipboard_stub(&stubs, "pbcopy");
        write_clipboard_stub(&stubs, "wl-copy");
        write_clipboard_stub(&stubs, "xclip");
        write_clipboard_stub(&stubs, "xsel");

        let out_dir = TempDir::new().expect("tempdir");
        let chosen_path = out_dir.path().join("chosen.txt");
        let payload_path = out_dir.path().join("payload.txt");

        let _path_guard = EnvGuard::set(&lock, "PATH", &stubs.path_str());
        let chosen_path_str = chosen_path.to_string_lossy();
        let payload_path_str = payload_path.to_string_lossy();
        let _chosen_guard = EnvGuard::set(&lock, "CLIPBOARD_TOOL_CHOSEN", chosen_path_str.as_ref());
        let _payload_guard =
            EnvGuard::set(&lock, "CLIPBOARD_PAYLOAD_OUT", payload_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        assert_eq!(
            fs::read_to_string(chosen_path).expect("chosen"),
            "pbcopy".to_string()
        );
        assert_eq!(fs::read_to_string(payload_path).expect("payload"), "hello");
    }

    #[test]
    fn set_clipboard_best_effort_prefers_wl_copy_over_xclip_and_xsel() {
        let lock = GlobalStateLock::new();
        let stubs = StubBinDir::new();
        write_clipboard_stub(&stubs, "wl-copy");
        write_clipboard_stub(&stubs, "xclip");
        write_clipboard_stub(&stubs, "xsel");

        let out_dir = TempDir::new().expect("tempdir");
        let chosen_path = out_dir.path().join("chosen.txt");
        let payload_path = out_dir.path().join("payload.txt");

        let _path_guard = EnvGuard::set(&lock, "PATH", &stubs.path_str());
        let chosen_path_str = chosen_path.to_string_lossy();
        let payload_path_str = payload_path.to_string_lossy();
        let _chosen_guard = EnvGuard::set(&lock, "CLIPBOARD_TOOL_CHOSEN", chosen_path_str.as_ref());
        let _payload_guard =
            EnvGuard::set(&lock, "CLIPBOARD_PAYLOAD_OUT", payload_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        assert_eq!(
            fs::read_to_string(chosen_path).expect("chosen"),
            "wl-copy".to_string()
        );
        assert_eq!(fs::read_to_string(payload_path).expect("payload"), "hello");
    }

    #[test]
    fn set_clipboard_best_effort_prefers_xclip_over_xsel() {
        let lock = GlobalStateLock::new();
        let stubs = StubBinDir::new();
        write_clipboard_stub(&stubs, "xclip");
        write_clipboard_stub(&stubs, "xsel");

        let out_dir = TempDir::new().expect("tempdir");
        let chosen_path = out_dir.path().join("chosen.txt");
        let payload_path = out_dir.path().join("payload.txt");

        let _path_guard = EnvGuard::set(&lock, "PATH", &stubs.path_str());
        let chosen_path_str = chosen_path.to_string_lossy();
        let payload_path_str = payload_path.to_string_lossy();
        let _chosen_guard = EnvGuard::set(&lock, "CLIPBOARD_TOOL_CHOSEN", chosen_path_str.as_ref());
        let _payload_guard =
            EnvGuard::set(&lock, "CLIPBOARD_PAYLOAD_OUT", payload_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        assert_eq!(
            fs::read_to_string(chosen_path).expect("chosen"),
            "xclip".to_string()
        );
        assert_eq!(fs::read_to_string(payload_path).expect("payload"), "hello");
    }

    #[test]
    fn set_clipboard_best_effort_uses_xsel_when_present() {
        let lock = GlobalStateLock::new();

        let stubs = StubBinDir::new();
        let out_dir = TempDir::new().expect("tempdir");
        let out_path = out_dir.path().join("xsel.out");

        stubs.write_exe(
            "xsel",
            r#"#!/bin/bash
set -euo pipefail
out="${XSEL_OUT:?XSEL_OUT is required}"
/bin/cat > "$out"
"#,
        );

        let _path_guard = EnvGuard::set(&lock, "PATH", &stubs.path_str());
        let out_path_str = out_path.to_string_lossy();
        let _out_guard = EnvGuard::set(&lock, "XSEL_OUT", out_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        let out = fs::read_to_string(out_path).expect("read stub output");
        assert_eq!(out, "hello");
    }

    #[test]
    fn set_clipboard_best_effort_falls_back_when_pbcopy_fails() {
        let lock = GlobalStateLock::new();

        let stubs = StubBinDir::new();
        let out_dir = TempDir::new().expect("tempdir");
        let out_path = out_dir.path().join("wl-copy.out");

        stubs.write_exe(
            "pbcopy",
            r#"#!/bin/bash
exit 1
"#,
        );
        stubs.write_exe(
            "wl-copy",
            r#"#!/bin/bash
set -euo pipefail
out="${WL_COPY_OUT:?WL_COPY_OUT is required}"
/bin/cat > "$out"
"#,
        );

        let _path_guard = EnvGuard::set(&lock, "PATH", &stubs.path_str());
        let out_path_str = out_path.to_string_lossy();
        let _out_guard = EnvGuard::set(&lock, "WL_COPY_OUT", out_path_str.as_ref());

        set_clipboard_best_effort("hello").expect("copy");
        let out = fs::read_to_string(out_path).expect("read stub output");
        assert_eq!(out, "hello");
    }

    #[test]
    fn set_clipboard_best_effort_missing_mode_short_circuits_with_ok() {
        let lock = GlobalStateLock::new();
        let _mode = EnvGuard::set(&lock, "GIT_CLI_FIXTURE_CLIPBOARD_MODE", "missing");
        set_clipboard_best_effort("hello").expect("missing mode should still succeed");
    }
}