use std::env;
use std::fs;
use std::path::Path;
use std::process::ExitCode;
use tabkit::{Column, Engine, Error, ReadOptions, Reader, Result, Row, Table, Value};
struct SsvReader;
impl Reader for SsvReader {
fn extensions(&self) -> &[&'static str] {
&["ssv"]
}
fn name(&self) -> &'static str {
"ssv-example"
}
fn read(&self, path: &Path, options: &ReadOptions) -> Result<Table> {
let content = fs::read_to_string(path)
.map_err(|e| Error::ParseError(format!("ssv: read failed: {e}")))?;
let mut lines = content.lines();
let column_names: Vec<String> = if options.has_header {
let Some(header) = lines.next() else {
return Ok(Table::default());
};
header
.split(';')
.enumerate()
.map(|(idx, h)| {
if h.trim().is_empty() {
format!("column_{idx}")
} else {
h.to_string()
}
})
.collect()
} else {
return Err(Error::ParseError(
"ssv example: headerless mode left as exercise to the reader".into(),
));
};
let mut sample_rows: Vec<Row> = Vec::new();
let mut row_count: u64 = 0;
for line in lines {
row_count += 1;
if sample_rows.len() < options.max_sample_rows {
let cells: Row = line
.split(';')
.map(parse_cell)
.chain(std::iter::repeat_with(|| Value::Null))
.take(column_names.len())
.collect();
sample_rows.push(cells);
}
}
let columns: Vec<Column> = column_names
.iter()
.enumerate()
.map(|(idx, name)| {
let inferred = sample_rows
.iter()
.map(|r| r[idx].data_type())
.fold(None, |acc, t| match (acc, t) {
(None, x) => x,
(Some(a), Some(b)) if a == b => Some(a),
_ => None,
})
.unwrap_or(tabkit::DataType::Unknown);
let nullable = sample_rows.iter().any(|r| matches!(r[idx], Value::Null));
Column::new(name.clone(), inferred, nullable)
})
.collect();
let mut table = Table::new(columns, sample_rows);
table.row_count = Some(row_count);
table.metadata.insert("delimiter".into(), ";".into());
Ok(table)
}
}
fn parse_cell(raw: &str) -> Value {
let trimmed = raw.trim();
if trimmed.is_empty() {
return Value::Null;
}
if let Ok(i) = trimmed.parse::<i64>() {
return Value::Integer(i);
}
if let Ok(f) = trimmed.parse::<f64>() {
return Value::Float(f);
}
Value::Text(trimmed.to_string())
}
fn main() -> ExitCode {
let Some(path) = env::args().nth(1) else {
eprintln!("usage: custom_reader <path-to-.ssv-file>");
return ExitCode::FAILURE;
};
let mut engine = Engine::new();
engine.register(Box::new(SsvReader));
match engine.read(Path::new(&path), &ReadOptions::default().max_sample_rows(5)) {
Ok(table) => {
println!("Schema:");
for col in &table.columns {
let null_marker = if col.nullable { " ?" } else { "" };
println!(" {} : {:?}{}", col.name, col.data_type, null_marker);
}
if let Some(n) = table.row_count {
println!();
println!("Row count: {n}");
}
println!();
println!("Sample rows:");
for row in &table.sample_rows {
println!(" {row:?}");
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: {e}");
ExitCode::FAILURE
}
}
}