kobo-db-tools 0.0.13

A Rust library to parse and analyze Kobo e-reader SQLite databases.
Documentation
use super::{error::ExportError, Export};
use crate::model::Bookmark;
use chrono::{DateTime, Utc};
use csv::Writer;
use serde_json;
use std::io::Write;
use std::str::FromStr;

impl Export for [Bookmark] {
    fn to_csv(&self) -> Result<String, ExportError> {
        let mut wtr = Writer::from_writer(vec![]);
        for bookmark in self {
            wtr.serialize(bookmark)?;
        }
        Ok(String::from_utf8(wtr.into_inner()?)?)
    }

    fn to_md(&self) -> Result<String, ExportError> {
        let mut buffer = Vec::new();
        for bookmark in self {
            writeln!(buffer, "### {}", bookmark.book_title)?;
            writeln!(buffer, "\n> {}", bookmark.content)?;
            writeln!(
                buffer,
                "\n**Chapter Progress:** {:.2}%",
                bookmark.chapter_progress * 100.0
            )?;
            let formatted_date = DateTime::<Utc>::from_str(&bookmark.create_date)
                .map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
                .unwrap_or_else(|_| bookmark.create_date.clone());
            writeln!(buffer, "**Created:** {}", formatted_date)?;
            writeln!(buffer, "\n---\n")?;
        }
        Ok(String::from_utf8(buffer)?)
    }

    fn to_json(&self) -> Result<String, ExportError> {
        serde_json::to_string(self).map_err(ExportError::JsonToString)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::Bookmark;

    #[test]
    fn test_to_csv() {
        let bookmarks = [Bookmark {
            content_id: "content1".to_string(),
            content: "content text".to_string(),
            book_id: "book1".to_string(),
            book_title: "Book Title".to_string(),
            color: 1,
            chapter_progress: 0.5,
            create_date: "2025-07-05T10:00:00Z".to_string(),
            write_date: "2025-07-05T10:00:00Z".to_string(),
        }];

        let expected_csv = "content_id,content,book_id,book_title,color,chapter_progress,create_date,write_date\ncontent1,content text,book1,Book Title,1,0.5,2025-07-05T10:00:00Z,2025-07-05T10:00:00Z\n".to_string();
        let result = bookmarks.to_csv().unwrap();
        assert_eq!(result, expected_csv);
    }

    #[test]
    fn test_to_json() {
        let bookmarks = [Bookmark {
            content_id: "content1".to_string(),
            content: "content text".to_string(),
            book_id: "book1".to_string(),
            book_title: "Book Title".to_string(),
            color: 1,
            chapter_progress: 0.5,
            create_date: "2025-07-05T10:00:00Z".to_string(),
            write_date: "2025-07-05T10:00:00Z".to_string(),
        }];

        let expected_json = serde_json::to_string(&bookmarks).unwrap();
        let result = bookmarks.to_json().unwrap();
        assert_eq!(result, expected_json);
    }
}