use crossterm::clipboard::CopyToClipboard;
use crossterm::execute;
use polars::prelude::*;
pub fn cell_text(df: &DataFrame, row: usize, col: usize) -> Option<String> {
let s = df.get_columns().get(col)?.as_materialized_series();
let str_s = s.cast(&DataType::String).ok()?;
let ca = str_s.str().ok()?;
(row < ca.len()).then(|| ca.get(row).unwrap_or_default().to_string())
}
pub fn row_text(df: &DataFrame, row: usize) -> Option<String> {
if row >= df.height() {
return None;
}
let cells: Vec<String> = (0..df.width())
.map(|col| cell_text(df, row, col).unwrap_or_default())
.collect();
Some(cells.join("\t"))
}
pub fn copy(text: &str) -> std::io::Result<()> {
execute!(std::io::stdout(), CopyToClipboard::to_clipboard_from(text))
}
#[cfg(test)]
mod tests {
use super::*;
fn frame() -> DataFrame {
df![
"name" => ["alice", "bob", "carol"],
"qty" => [1i64, 2, 3],
"rate" => [1.5f64, 2.25, 3.0],
]
.unwrap()
}
#[test]
fn test_cell_text_reads_each_dtype() {
let df = frame();
assert_eq!(cell_text(&df, 0, 0).as_deref(), Some("alice"));
assert_eq!(cell_text(&df, 1, 1).as_deref(), Some("2"));
assert_eq!(cell_text(&df, 2, 2).as_deref(), Some("3.0"));
}
#[test]
fn test_cell_text_null_is_empty_not_glyph() {
let df = df![
"name" => [Some("alice"), None],
"qty" => [Some(1i64), None],
]
.unwrap();
assert_eq!(cell_text(&df, 1, 0).as_deref(), Some(""));
assert_eq!(cell_text(&df, 1, 1).as_deref(), Some(""));
}
#[test]
fn test_cell_text_on_single_row_frame() {
let df = df!["name" => ["solitary"], "qty" => [7i64]].unwrap();
assert_eq!(cell_text(&df, 0, 0).as_deref(), Some("solitary"));
assert_eq!(cell_text(&df, 0, 1).as_deref(), Some("7"));
}
#[test]
fn test_cell_text_formats_temporal_as_displayed() {
let df = df!["ts" => ["2025-05-23 10:52:37", "2025-05-23 11:46:14"]]
.unwrap()
.lazy()
.select([col("ts").str().to_datetime(
None,
None,
StrptimeOptions {
format: Some("%Y-%m-%d %H:%M:%S".into()),
..Default::default()
},
lit("raise"),
)])
.collect()
.unwrap();
assert_eq!(
cell_text(&df, 0, 0).as_deref(),
Some("2025-05-23 10:52:37.000000")
);
}
#[test]
fn test_cell_text_out_of_range() {
let df = frame();
assert_eq!(cell_text(&df, 3, 0), None);
assert_eq!(cell_text(&df, 0, 3), None);
}
#[test]
fn test_row_text_is_tab_separated_across_all_columns() {
assert_eq!(row_text(&frame(), 1).as_deref(), Some("bob\t2\t2.25"));
}
#[test]
fn test_row_text_keeps_position_of_null() {
let df = df![
"a" => [Some("x")],
"b" => [None::<i64>],
"c" => [Some("z")],
]
.unwrap();
assert_eq!(row_text(&df, 0).as_deref(), Some("x\t\tz"));
}
#[test]
fn test_row_text_out_of_range() {
assert_eq!(row_text(&frame(), 3), None);
}
}