use anyhow::Result;
use crate::db;
pub fn generate_commands_text(db: &db::Database) -> Result<String> {
let mut text = String::new();
db.iterate_entries(|entry| {
if entry.is_separator {
return Ok(());
}
text.push_str(&entry.command);
text.push('\n');
Ok(())
})?;
Ok(text)
}
pub fn generate_markdown(db: &db::Database) -> Result<String> {
let mut md = String::new();
md.push_str("# Cahier Export\n\n");
db.iterate_entries(|entry| {
if entry.is_separator {
md.push_str("\n---\n\n");
return Ok(());
}
if let Some(annotation) = &entry.annotation {
if !annotation.is_empty() {
md.push_str(&format!("{}\n\n", annotation));
}
}
md.push_str("```bash\n");
let exit_code_str = entry.exit_code.map_or("?".to_string(), |c| c.to_string());
md.push_str(&format!("({} - {}ms)\n", exit_code_str, entry.duration_ms));
md.push_str(&format!("$ {}\n", entry.command));
if let Some(output_file) = entry.output_file {
md.push_str(&format!(
"[Output stored in external file: {}]\n",
output_file
));
} else if !entry.output.is_empty() {
let clean_output = strip_ansi_escapes::strip(&entry.output);
md.push_str(&String::from_utf8_lossy(&clean_output));
if !entry.output.ends_with('\n') {
md.push('\n');
}
}
md.push_str("```\n\n");
Ok(())
})?;
Ok(md)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_commands_text() -> Result<()> {
let db = db::Database::init_memory()?;
db.log_entry("echo hello", "hello\n", Some(0), 100, None)?;
db.log_entry("ls -la", "file1\nfile2\n", Some(0), 50, None)?;
db.log_entry("cat missing.txt", "No such file", Some(1), 25, None)?;
let text = generate_commands_text(&db)?;
assert!(text.contains("echo hello"));
assert!(text.contains("ls -la"));
assert!(text.contains("cat missing.txt"));
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "echo hello");
assert_eq!(lines[1], "ls -la");
assert_eq!(lines[2], "cat missing.txt");
Ok(())
}
#[test]
fn test_generate_markdown() -> Result<()> {
let db = db::Database::init_memory()?;
db.log_entry("echo hello", "hello world\n", Some(0), 100, None)?;
db.log_entry("failing_cmd", "error message", Some(1), 250, None)?;
db.log_entry(
"large_output",
"[Output too large, redirected to .cahier/outputs/output_123.txt]\n",
Some(0),
500,
Some(".cahier/outputs/output_123.txt"),
)?;
let md = generate_markdown(&db)?;
assert!(md.contains("# Cahier Export"));
assert!(md.contains("(0 - 100ms)"));
assert!(md.contains("$ echo hello"));
assert!(md.contains("hello world"));
assert!(md.contains("(1 - 250ms)"));
assert!(md.contains("$ failing_cmd"));
assert!(md.contains("error message"));
assert!(md.contains("(0 - 500ms)"));
assert!(md.contains("$ large_output"));
assert!(md.contains("[Output stored in external file: .cahier/outputs/output_123.txt]"));
assert!(md.contains("```bash\n"));
let bash_count = md.matches("```bash").count();
assert_eq!(bash_count, 3);
Ok(())
}
#[test]
fn test_generate_markdown_with_annotation() -> Result<()> {
let db = db::Database::init_memory()?;
db.log_entry("echo annotated", "output\n", Some(0), 100, None)?;
let entries = db.get_all_entries_ordered()?;
let id = entries[0].id;
db.update_annotation(id, "This is a test annotation".to_string())?;
let md = generate_markdown(&db)?;
assert!(md.contains("This is a test annotation"));
Ok(())
}
#[test]
fn test_generate_markdown_empty_output() -> Result<()> {
let db = db::Database::init_memory()?;
db.log_entry("cd /tmp", "", Some(0), 10, None)?;
let md = generate_markdown(&db)?;
assert!(md.contains("$ cd /tmp"));
assert!(md.contains("(0 - 10ms)"));
Ok(())
}
#[test]
fn test_generate_markdown_with_separator() -> Result<()> {
let db = db::Database::init_memory()?;
db.log_entry("echo before", "output\n", Some(0), 100, None)?;
db.insert_separator(2)?; db.log_entry("echo after", "output\n", Some(0), 100, None)?;
let md = generate_markdown(&db)?;
assert!(md.contains("$ echo before"));
assert!(md.contains("\n---\n\n"));
assert!(md.contains("$ echo after"));
Ok(())
}
}