diff options
author | James Campos <james.r.campos@gmail.com> | 2020-07-04 18:33:58 -0700 |
---|---|---|
committer | James Campos <james.r.campos@gmail.com> | 2020-07-04 18:33:58 -0700 |
commit | 6fd04571be617c405a0e4c26c88fde255b40e01c (patch) | |
tree | 6702caba88ae8690628ade9b36685399d4413dfc | |
parent | 0891a8a4773ded7a887328602258af9c8aacaf8e (diff) | |
download | bk-6fd04571be617c405a0e4c26c88fde255b40e01c.tar.gz |
metadata
-rw-r--r-- | README.md | 5 | ||||
-rw-r--r-- | src/epub.rs | 60 | ||||
-rw-r--r-- | src/main.rs | 31 |
3 files changed, 60 insertions, 36 deletions
@@ -21,13 +21,14 @@ or from github: # Usage - Usage: bk [<path>] [-w <width>] + Usage: bk [<path>] [-m] [-t] [-w <width>] read a book Options: - -w, --width characters per line + -m, --meta print metadata and exit -t, --toc start with table of contents open + -w, --width characters per line --help display usage information Running `bk` without a path will load the most recent Epub. diff --git a/src/epub.rs b/src/epub.rs index 8cfb1c7..0945ad6 100644 --- a/src/epub.rs +++ b/src/epub.rs @@ -7,6 +7,7 @@ use roxmltree::{Document, Node}; pub struct Epub { container: zip::ZipArchive<File>, pub chapters: Vec<(String, String)>, + pub meta: String, } impl Epub { @@ -15,12 +16,16 @@ impl Epub { let mut epub = Epub { container: zip::ZipArchive::new(file)?, chapters: Vec::new(), + meta: String::new(), }; - epub.chapters = epub - .get_nav() + epub.get_rootfile(); + Ok(epub) + } + pub fn get_chapters(&mut self) { + self.chapters = std::mem::take(&mut self.chapters) .into_iter() .filter_map(|(path, title)| { - let xml = epub.get_text(&path); + let xml = self.get_text(&path); // https://github.com/RazrFalcon/roxmltree/issues/12 // UnknownEntityReference for HTML entities let doc = Document::parse(&xml).unwrap(); @@ -30,11 +35,10 @@ impl Epub { if chapter.is_empty() { None } else { - Some((title, chapter)) + Some((chapter, title)) } }) .collect(); - Ok(epub) } fn render(buf: &mut String, n: Node) { if n.is_text() { @@ -84,7 +88,7 @@ impl Epub { .unwrap(); text } - fn get_nav(&mut self) -> Vec<(String, String)> { + fn get_rootfile(&mut self) { let xml = self.get_text("META-INF/container.xml"); let doc = Document::parse(&xml).unwrap(); let path = doc @@ -95,26 +99,36 @@ impl Epub { .unwrap(); let xml = self.get_text(path); let doc = Document::parse(&xml).unwrap(); - let root = doc.root_element(); // zip expects unix path even on windows let rootdir = match path.rfind('/') { Some(n) => &path[..=n], None => "", }; - let spine = root.children().find(|n| n.has_tag_name("spine")).unwrap(); - let manifest = root - .children() - .find(|n| n.has_tag_name("manifest")) - .unwrap(); - let mut manifest_map = HashMap::new(); - manifest.children().filter(Node::is_element).for_each(|n| { - manifest_map.insert(n.attribute("id").unwrap(), n.attribute("href").unwrap()); - }); + let mut manifest = HashMap::new(); let mut nav = HashMap::new(); + let mut children = doc.root_element().children().filter(Node::is_element); + let meta_node = children.next().unwrap(); + let manifest_node = children.next().unwrap(); + let spine_node = children.next().unwrap(); - if root.attribute("version") == Some("3.0") { - let path = manifest + meta_node + .children() + .filter(|n| n.is_element() && n.tag_name().name() != "meta") + .for_each(|n| { + let name = n.tag_name().name(); + let text = n.text().unwrap(); + self.meta.push_str(&format!("{}: {}\n", name, text)); + }); + manifest_node + .children() + .filter(Node::is_element) + .for_each(|n| { + manifest.insert(n.attribute("id").unwrap(), n.attribute("href").unwrap()); + }); + + if doc.root_element().attribute("version") == Some("3.0") { + let path = manifest_node .children() .find(|n| n.attribute("properties") == Some("nav")) .unwrap() @@ -138,8 +152,8 @@ impl Epub { nav.insert(path, text); }) } else { - let toc = spine.attribute("toc").unwrap_or("ncx"); - let path = manifest_map.get(toc).unwrap(); + let toc = spine_node.attribute("toc").unwrap_or("ncx"); + let path = manifest.get(toc).unwrap(); let xml = self.get_text(&format!("{}{}", rootdir, path)); let doc = Document::parse(&xml).unwrap(); @@ -167,18 +181,18 @@ impl Epub { }) } - spine + self.chapters = spine_node .children() .filter(Node::is_element) .enumerate() .map(|(i, n)| { let id = n.attribute("idref").unwrap(); - let path = manifest_map.remove(id).unwrap(); + let path = manifest.remove(id).unwrap(); let label = nav.remove(path).unwrap_or_else(|| i.to_string()); let path = format!("{}{}", rootdir, path); (path, label) }) - .collect() + .collect(); } } diff --git a/src/main.rs b/src/main.rs index 695e70a..72da8fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -355,7 +355,7 @@ impl Bk<'_> { let width = min(cols, args.width) as usize; let mut chapters = Vec::with_capacity(epub.chapters.len()); - for (title, text) in epub.chapters { + for (text, title) in epub.chapters { let title = if title.chars().count() > width { title .chars() @@ -529,13 +529,17 @@ struct Args { #[argh(positional)] path: Option<String>, - /// characters per line - #[argh(option, short = 'w', default = "75")] - width: u16, + /// print metadata and exit + #[argh(switch, short = 'm')] + meta: bool, /// start with table of contents open #[argh(switch, short = 't')] toc: bool, + + /// characters per line + #[argh(option, short = 'w', default = "75")] + width: u16, } struct Props { @@ -545,11 +549,9 @@ struct Props { toc: bool, } -fn restore(save_path: &str) -> Option<(String, Props)> { +fn init(save_path: &str) -> Option<(String, bool, Props)> { let args: Args = argh::from_env(); - let width = args.width; - // XXX we shouldn't panic, but it gives a useful error, and we - // don't want to load the saved path if an invalid path is given + // TODO nice error message instead of panic let path = args .path .map(|s| fs::canonicalize(s).unwrap().to_str().unwrap().to_string()); @@ -577,10 +579,11 @@ fn restore(save_path: &str) -> Option<(String, Props)> { Some(( path, + args.meta, Props { chapter, line, - width, + width: args.width, toc: args.toc, }, )) @@ -593,16 +596,22 @@ fn main() { format!("{}/.local/share/bk", env::var("HOME").unwrap()) }; - let (path, args) = restore(&save_path).unwrap_or_else(|| { + let (path, meta, args) = init(&save_path).unwrap_or_else(|| { println!("error: need a path"); exit(1); }); - let epub = epub::Epub::new(&path).unwrap_or_else(|e| { + let mut epub = epub::Epub::new(&path).unwrap_or_else(|e| { println!("error reading epub: {}", e); exit(1); }); + if meta { + println!("{}", epub.meta); + exit(0); + } + + epub.get_chapters(); let mut bk = Bk::new(epub, args); // i have never seen crossterm error bk.run().unwrap(); |