From 6fd04571be617c405a0e4c26c88fde255b40e01c Mon Sep 17 00:00:00 2001
From: James Campos <james.r.campos@gmail.com>
Date: Sat, 4 Jul 2020 18:33:58 -0700
Subject: metadata

---
 src/epub.rs | 60 +++++++++++++++++++++++++++++++++++++-----------------------
 src/main.rs | 31 ++++++++++++++++++++-----------
 2 files changed, 57 insertions(+), 34 deletions(-)

(limited to 'src')

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();
-- 
cgit v1.2.3