use std::io::Read;
use std::path::PathBuf;
use std::time::Duration;
use anyhow::anyhow;
use super::paste::{self, CLIPBOARD_READ_TIMEOUT, ClipboardError, PASTE_DIRS};
pub(crate) trait ValueSource {
fn read(&self) -> anyhow::Result<String>;
}
#[cfg(target_os = "macos")]
pub(crate) const PASTE_TOOLS: &[(&str, &[&str])] = &[("pbpaste", &[])];
#[cfg(not(target_os = "macos"))]
pub(crate) const PASTE_TOOLS: &[(&str, &[&str])] = &[
("wl-paste", &["--no-newline"]),
("xclip", &["-selection", "clipboard", "-o"]),
("xsel", &["--clipboard", "--output"]),
];
pub(crate) struct SystemClipboard {
tools: Vec<(PathBuf, Vec<String>)>,
searched: Vec<PathBuf>,
timeout: Duration,
}
impl SystemClipboard {
pub(crate) fn system() -> Self {
let dirs: Vec<PathBuf> = PASTE_DIRS.iter().map(PathBuf::from).collect();
Self::in_dirs(&dirs, CLIPBOARD_READ_TIMEOUT)
}
pub(crate) fn in_dirs(dirs: &[PathBuf], timeout: Duration) -> Self {
let tools = PASTE_TOOLS
.iter()
.flat_map(|(name, args)| {
let args: Vec<String> = args.iter().map(|a| (*a).to_string()).collect();
dirs.iter().map(move |dir| (dir.join(name), args.clone()))
})
.collect();
Self {
tools,
searched: dirs.to_vec(),
timeout,
}
}
#[cfg(test)]
pub(crate) fn with_tools(tools: Vec<(PathBuf, Vec<String>)>, timeout: Duration) -> Self {
let mut searched: Vec<PathBuf> = Vec::new();
for parent in tools.iter().filter_map(|(path, _)| path.parent()) {
if !searched.iter().any(|dir| dir == parent) {
searched.push(parent.to_path_buf());
}
}
Self {
tools,
searched,
timeout,
}
}
}
impl ValueSource for SystemClipboard {
fn read(&self) -> anyhow::Result<String> {
for (program, args) in &self.tools {
if !program.is_absolute() || !paste::is_executable_file(program) {
continue;
}
let bytes = paste::run_bounded(program, args, self.timeout)?;
return String::from_utf8(bytes).map_err(|_| ClipboardError::NotUtf8.into());
}
let searched: Vec<String> = self
.searched
.iter()
.map(|dir| dir.display().to_string())
.collect();
Err(ClipboardError::NoReader {
searched: searched.join(", "),
}
.into())
}
}
pub(crate) struct StdinSource;
impl ValueSource for StdinSource {
fn read(&self) -> anyhow::Result<String> {
let mut text = String::new();
std::io::stdin()
.read_to_string(&mut text)
.map_err(|e| anyhow!("tm secrets set: cannot read stdin: {e}"))?;
Ok(text)
}
}