use std::path::Path;
use anyhow::{Result, bail};
#[cfg(not(test))]
use anyhow::Context;
const CLIPBOARD_STEM: &str = "@clipboard";
pub(crate) fn is_clipboard_path(path: &Path) -> bool {
path.file_stem()
.and_then(|stem| stem.to_str())
.is_some_and(|stem| stem.eq_ignore_ascii_case(CLIPBOARD_STEM))
}
#[allow(dead_code)]
pub(crate) fn read_text(_path: &Path) -> Result<String> {
#[cfg(test)]
{
Ok(test_store().lock().expect("clipboard lock").clone())
}
#[cfg(not(test))]
{
let mut clipboard =
arboard::Clipboard::new().context("failed to access the system clipboard")?;
clipboard
.get_text()
.with_context(|| format!("failed to read text from clipboard for {}", _path.display()))
}
}
#[allow(dead_code)]
pub(crate) fn write_text(_path: &Path, text: &str) -> Result<()> {
#[cfg(test)]
{
*test_store().lock().expect("clipboard lock") = text.to_owned();
Ok(())
}
#[cfg(not(test))]
{
let mut clipboard =
arboard::Clipboard::new().context("failed to access the system clipboard")?;
clipboard
.set_text(text.to_owned())
.with_context(|| format!("failed to write text to clipboard for {}", _path.display()))
}
}
#[allow(dead_code)]
pub(crate) fn ensure_supported_clipboard_input(path: &Path) -> Result<()> {
if matches!(
path.extension().and_then(|extension| extension.to_str()),
Some(extension)
if ["csv", "json", "jsonl", "md", "markdown", "html", "htm", "xml"]
.iter()
.any(|candidate| extension.eq_ignore_ascii_case(candidate))
) {
return Ok(());
}
bail!(
"clipboard input {} requires a format specifier; use the |type suffix such as \
@clipboard|csv, @clipboard|json, @clipboard|jsonl, @clipboard|md, @clipboard|html, \
or @clipboard|xml",
path.display()
)
}
#[allow(dead_code)]
pub(crate) fn ensure_supported_clipboard_output(path: &Path) -> Result<()> {
if matches!(
path.extension().and_then(|extension| extension.to_str()),
Some(extension)
if ["txt", "csv", "json", "jsonl", "md", "markdown", "html", "htm", "xml"]
.iter()
.any(|candidate| extension.eq_ignore_ascii_case(candidate))
) || path.extension().is_none()
{
return Ok(());
}
bail!(
"clipboard output {} only supports text-based formats; use the |type suffix such as \
@clipboard|csv, @clipboard|json, @clipboard|jsonl, @clipboard|md, @clipboard|html, \
or @clipboard|xml",
path.display()
)
}
#[cfg(test)]
fn test_store() -> &'static std::sync::Mutex<String> {
static STORE: std::sync::OnceLock<std::sync::Mutex<String>> = std::sync::OnceLock::new();
STORE.get_or_init(|| std::sync::Mutex::new(String::new()))
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn set_test_text(text: &str) {
*test_store().lock().expect("clipboard lock") = text.to_owned();
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn get_test_text() -> String {
test_store().lock().expect("clipboard lock").clone()
}