Define core data models for book and theme
This commit is contained in:
90
src/book.rs
90
src/book.rs
@@ -0,0 +1,90 @@
|
|||||||
|
pub fn strip_html(input: &str) -> String {
|
||||||
|
let mut result = String::with_capacity(input.len());
|
||||||
|
let mut in_tag = false;
|
||||||
|
let mut in_entity = false;
|
||||||
|
let mut entity = String::new();
|
||||||
|
|
||||||
|
for c in input.chars() {
|
||||||
|
match c {
|
||||||
|
'<' => in_tag = true,
|
||||||
|
'>' if in_tag => in_tag = false,
|
||||||
|
'&' if !in_tag => {
|
||||||
|
in_entity = true;
|
||||||
|
entity.clear();
|
||||||
|
}
|
||||||
|
';' if in_entity => {
|
||||||
|
in_entity = false;
|
||||||
|
let decoded = match entity.as_str() {
|
||||||
|
"amp" => "&",
|
||||||
|
"lt" => "<",
|
||||||
|
"gt" => ">",
|
||||||
|
"quot" => "\"",
|
||||||
|
"nbsp" => " ",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
result.push_str(decoded);
|
||||||
|
}
|
||||||
|
c if !in_tag && !in_entity => result.push(c),
|
||||||
|
c if in_entity => entity.push(c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TocEntry {
|
||||||
|
pub label: String,
|
||||||
|
pub section: usize,
|
||||||
|
pub children: Vec<TocEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Section {
|
||||||
|
pub title: String,
|
||||||
|
pub content: String,
|
||||||
|
pub pages: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Book {
|
||||||
|
pub title: String,
|
||||||
|
pub author: String,
|
||||||
|
pub cover: Option<Vec<u8>>,
|
||||||
|
pub sections: Vec<Section>,
|
||||||
|
pub toc: Vec<TocEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_html_plain_text() {
|
||||||
|
assert_eq!(strip_html("Hello World"), "Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_html_simple_tags() {
|
||||||
|
assert_eq!(strip_html("<p>Hello</p>"), "Hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_html_nested_tags() {
|
||||||
|
assert_eq!(
|
||||||
|
strip_html("<div><p>Hello <b>World</b></p></div>"),
|
||||||
|
"Hello World"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_html_html_entities() {
|
||||||
|
assert_eq!(strip_html("Hello & World"), "Hello & World");
|
||||||
|
assert_eq!(strip_html("Hello World"), "Hello World");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_html_empty() {
|
||||||
|
assert_eq!(strip_html(""), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
79
src/theme.rs
79
src/theme.rs
@@ -0,0 +1,79 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Theme {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Theme {
|
||||||
|
fn default() -> Self {
|
||||||
|
Theme::Light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub font_size: f32,
|
||||||
|
pub theme: Theme,
|
||||||
|
pub recent_files: Vec<String>,
|
||||||
|
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
|
||||||
|
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
|
||||||
|
pub window_size: Option<(f32, f32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
font_size: 20.0,
|
||||||
|
theme: Theme::Light,
|
||||||
|
recent_files: Vec::new(),
|
||||||
|
reading_positions: std::collections::HashMap::new(),
|
||||||
|
bookmarks: std::collections::HashMap::new(),
|
||||||
|
window_size: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct ReadingPosition {
|
||||||
|
pub section: usize,
|
||||||
|
pub page: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Bookmark {
|
||||||
|
pub section: usize,
|
||||||
|
pub page: usize,
|
||||||
|
pub label: String,
|
||||||
|
pub timestamp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_settings_default() {
|
||||||
|
let s = Settings::default();
|
||||||
|
assert_eq!(s.font_size, 20.0);
|
||||||
|
assert_eq!(s.theme, Theme::Light);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_settings_round_trip() {
|
||||||
|
let s = Settings::default();
|
||||||
|
let json = serde_json::to_string(&s).unwrap();
|
||||||
|
let restored: Settings = serde_json::from_str(&json).unwrap();
|
||||||
|
assert_eq!(restored.font_size, s.font_size);
|
||||||
|
assert_eq!(restored.theme, s.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_theme_serialize() {
|
||||||
|
let json = serde_json::to_string(&Theme::Dark).unwrap();
|
||||||
|
assert_eq!(json, "\"Dark\"");
|
||||||
|
let restored: Theme = serde_json::from_str(&json).unwrap();
|
||||||
|
assert_eq!(restored, Theme::Dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user