aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Campos <james.r.campos@gmail.com>2020-07-04 18:33:58 -0700
committerJames Campos <james.r.campos@gmail.com>2020-07-04 18:33:58 -0700
commit6fd04571be617c405a0e4c26c88fde255b40e01c (patch)
tree6702caba88ae8690628ade9b36685399d4413dfc
parent0891a8a4773ded7a887328602258af9c8aacaf8e (diff)
downloadbk-6fd04571be617c405a0e4c26c88fde255b40e01c.tar.gz
metadata
-rw-r--r--README.md5
-rw-r--r--src/epub.rs60
-rw-r--r--src/main.rs31
3 files changed, 60 insertions, 36 deletions
diff --git a/README.md b/README.md
index 79b64e5..eb614d7 100644
--- a/README.md
+++ b/README.md
@@ -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();