osp-cli 1.5.1

CLI and REPL for querying and managing OSP infrastructure data
Documentation
use std::io::Write;
use std::process::{Command, Stdio};

use super::ClipboardError;

pub(crate) const OSC52_MAX_BYTES_DEFAULT: usize = 100_000;

#[derive(Debug, Clone, Copy)]
pub(crate) struct ClipboardCommand {
    pub(crate) command: &'static str,
    pub(crate) args: &'static [&'static str],
}

pub(crate) fn platform_backends() -> Vec<ClipboardCommand> {
    let mut backends = Vec::new();

    if cfg!(target_os = "macos") {
        backends.push(ClipboardCommand {
            command: "pbcopy",
            args: &[],
        });
        return backends;
    }

    if cfg!(target_os = "windows") {
        backends.push(ClipboardCommand {
            command: "clip",
            args: &[],
        });
        return backends;
    }

    if std::env::var("WAYLAND_DISPLAY").is_ok() {
        backends.push(ClipboardCommand {
            command: "wl-copy",
            args: &[],
        });
    }

    backends.push(ClipboardCommand {
        command: "xclip",
        args: &["-selection", "clipboard"],
    });
    backends.push(ClipboardCommand {
        command: "xsel",
        args: &["--clipboard", "--input"],
    });

    backends
}

pub(crate) fn copy_via_command(
    command: ClipboardCommand,
    text: &str,
) -> Result<(), ClipboardError> {
    let mut child = Command::new(command.command)
        .args(command.args)
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|err| ClipboardError::SpawnFailed {
            command: command.command.to_string(),
            reason: err.to_string(),
        })?;

    if let Some(stdin) = child.stdin.as_mut() {
        stdin
            .write_all(text.as_bytes())
            .map_err(|err| ClipboardError::Io(err.to_string()))?;
    }

    let output = child
        .wait_with_output()
        .map_err(|err| ClipboardError::Io(err.to_string()))?;

    if output.status.success() {
        Ok(())
    } else {
        Err(ClipboardError::CommandFailed {
            command: command.command.to_string(),
            status: output.status.code().unwrap_or(1),
            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
        })
    }
}

pub(crate) fn osc52_enabled() -> bool {
    match std::env::var("OSC52") {
        Ok(value) => {
            let value = value.trim().to_ascii_lowercase();
            !(value == "0" || value == "false" || value == "off")
        }
        Err(_) => true,
    }
}

pub(crate) fn osc52_max_bytes() -> usize {
    std::env::var("OSC52_MAX_BYTES")
        .ok()
        .and_then(|value| value.parse::<usize>().ok())
        .filter(|value| *value > 0)
        .unwrap_or(OSC52_MAX_BYTES_DEFAULT)
}

pub(crate) fn osc52_payload(text: &str) -> String {
    let encoded = base64_encode(text.as_bytes());
    format!("\x1b]52;c;{encoded}\x07")
}

pub(crate) fn base64_encoded_len(input_len: usize) -> usize {
    if input_len == 0 {
        return 0;
    }

    input_len.div_ceil(3).saturating_mul(4)
}

pub(crate) fn base64_encode(input: &[u8]) -> String {
    if input.is_empty() {
        return String::new();
    }

    const BASE64_TABLE: &[u8; 64] =
        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    let mut output = String::with_capacity(input.len().div_ceil(3) * 4);
    let mut index = 0usize;

    while index < input.len() {
        let b0 = input[index];
        let b1 = input.get(index + 1).copied().unwrap_or(0);
        let b2 = input.get(index + 2).copied().unwrap_or(0);

        let chunk = ((b0 as u32) << 16) | ((b1 as u32) << 8) | (b2 as u32);

        let i0 = ((chunk >> 18) & 0x3f) as usize;
        let i1 = ((chunk >> 12) & 0x3f) as usize;
        let i2 = ((chunk >> 6) & 0x3f) as usize;
        let i3 = (chunk & 0x3f) as usize;

        output.push(BASE64_TABLE[i0] as char);
        output.push(BASE64_TABLE[i1] as char);

        if index + 1 < input.len() {
            output.push(BASE64_TABLE[i2] as char);
        } else {
            output.push('=');
        }

        if index + 2 < input.len() {
            output.push(BASE64_TABLE[i3] as char);
        } else {
            output.push('=');
        }

        index += 3;
    }

    output
}