1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
use self::super::super::util::{xhtml_path_id, book_filename}; use std::path::{MAIN_SEPARATOR, PathBuf, Path}; use self::super::super::Error; use std::str::{self, FromStr}; use std::fmt; use std::fs; /// Representation of an /// [`-I`nclude directory](https://nabijaczleweli.xyz/content/gen-epub-book/programmer.html#features-include-dirs). /// /// Textually, /// *unnamed* `-I`nclude directories take the form of `"path"`, and /// *named* `-I`nclude directories take the form of `"name=path"`. #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum IncludeDirectory { /// An *unnamed* include directory, acting transparently Unnamed { /// Directory path dir: (String, PathBuf), }, /// A *named* include directory, saved with prefixes. Named { /// `-I`nclude directory name. name: String, /// Directory path. dir: (String, PathBuf), }, } impl IncludeDirectory { /// Get the name of the include directory /// /// (Also known as `self.dir.0`, but it's a convenience function, because :enums:.) /// /// # Examples /// /// ``` /// # use gen_epub_book::ops::IncludeDirectory; /// # use std::path::PathBuf; /// assert_eq!( /// IncludeDirectory::Unnamed { /// dir: ("cur-dir".to_string(), PathBuf::from(".")), /// }.directory_name(), /// "cur-dir"); /// assert_eq!( /// IncludeDirectory::Named { /// name: "dot".to_string(), /// dir: ("named-cur-dir".to_string(), PathBuf::from(".")), /// }.directory_name(), /// "named-cur-dir"); /// ``` pub fn directory_name(&self) -> &str { match *self { IncludeDirectory::Named { ref dir, .. } | IncludeDirectory::Unnamed { ref dir } => &dir.0, } } /// Get packed filename for file specified by path. /// /// Basically optionally prefixes [`util::book_filename()`](../util/fn.book_filename.html). /// /// Path separator, if any, is always `'/'`. /// /// # Examples /// /// ``` /// # use gen_epub_book::ops::IncludeDirectory; /// # use gen_epub_book::util::book_filename; /// # use std::path::{PathBuf, Path}; /// let fname = Path::new("content/ch01.html"); /// assert_eq!( /// IncludeDirectory::Unnamed { /// dir: ("cur-dir".to_string(), PathBuf::from(".")), /// }.packed_name(&fname), /// book_filename(&fname)); /// assert_eq!( /// IncludeDirectory::Named { /// name: "dot".to_string(), /// dir: ("named-cur-dir".to_string(), PathBuf::from(".")), /// }.packed_name(&fname).display().to_string(), /// format!("dot/{}", book_filename(&fname).display())); /// ``` pub fn packed_name<P: AsRef<Path>>(&self, f: P) -> PathBuf { match *self { // Okay so here we can't just do Path::new(name).join(book_filename(f)) because that'll give us backslashes on Windows IncludeDirectory::Named { ref name, .. } => Path::new(name).join(book_filename(f)).to_str().unwrap().replace('\\', "/").into(), IncludeDirectory::Unnamed { .. } => book_filename(f), } } /// Get the (X)HTML ID from a path. /// /// Basically optionally prefixes [`util::xhtml_path_id()`](../util/fn.xhtml_path_id.html). /// /// # Examples /// /// ``` /// # use gen_epub_book::ops::IncludeDirectory; /// # use gen_epub_book::util::xhtml_path_id; /// # use std::path::{PathBuf, Path}; /// let fname = Path::new("content/ch01.html"); /// assert_eq!( /// IncludeDirectory::Unnamed { /// dir: ("cur-dir".to_string(), PathBuf::from(".")), /// }.packed_id(&fname), /// xhtml_path_id(&fname)); /// assert_eq!( /// IncludeDirectory::Named { /// name: "dot".to_string(), /// dir: ("named-cur-dir".to_string(), PathBuf::from(".")), /// }.packed_id(&fname), /// format!("dot--{}", xhtml_path_id(&fname))); /// ``` pub fn packed_id(&self, f: &Path) -> String { match *self { IncludeDirectory::Named { ref name, .. } => format!("{}--{}", name, xhtml_path_id(f)), IncludeDirectory::Unnamed { .. } => xhtml_path_id(f), } } /// Resolve the path of the specified file in this include directory, or `None` if nonexistant or isn't a file. /// /// # Examples /// /// ``` /// # use gen_epub_book::ops::IncludeDirectory; /// # use std::fs::{self, File}; /// # use std::env::temp_dir; /// # use std::path::Path; /// # let special_book = temp_dir().join("gen-epub-book.rs-doctest").join("ops-include-dir-resolve-0"); /// # fs::create_dir_all(special_book.join("rendered").join("output")).unwrap(); /// # fs::create_dir_all(special_book.join("previews").join("generated").join("out")).unwrap(); /// # fs::create_dir_all(special_book.join("gep").join("special")).unwrap(); /// # File::create(special_book.join("rendered").join("output").join("ending.html")).unwrap(); /// # File::create(special_book.join("previews").join("generated").join("out").join("main.html")).unwrap(); /// # File::create(special_book.join("gep").join("special").join("intro.html")).unwrap(); /// let default_dir = special_book.join("gep").join("special"); /// let previews_dir = special_book.join("previews").join("generated").join("out"); /// let rendered_dir = special_book.join("rendered").join("output"); /// let default = IncludeDirectory::Unnamed { /// dir: ("".to_string(), default_dir.clone()), /// }; /// let previews = IncludeDirectory::Named { /// name: "previews".to_string(), /// dir: ("../../previews/generated/out".to_string(), previews_dir.clone()), /// }; /// let rendered = IncludeDirectory::Unnamed { /// dir: ("../../rendered/output".to_string(), rendered_dir.clone()), /// }; /// /// assert_eq!(default.resolve(Path::new("intro.html")), Some(default_dir.join("intro.html"))); /// assert_eq!(previews.resolve(Path::new("main.html")), Some(previews_dir.join("main.html"))); /// assert_eq!(rendered.resolve(Path::new("ending.html")), /// Some(rendered_dir.join("ending.html"))); /// assert_eq!(default.resolve(Path::new("cover.png")), None); /// assert_eq!(default.resolve(Path::new("../special")), None); /// ``` pub fn resolve<P: AsRef<Path>>(&self, relpath: P) -> Option<PathBuf> { let abspath = match *self { IncludeDirectory::Named { ref dir, .. } | IncludeDirectory::Unnamed { ref dir } => dir, } .1 .join(relpath.as_ref().to_str().unwrap().replace('/', str::from_utf8(&[MAIN_SEPARATOR as u8]).unwrap())); if abspath.exists() && abspath.is_file() { Some(abspath) } else { None } } } impl FromStr for IncludeDirectory { type Err = Error; fn from_str(s: &str) -> Result<IncludeDirectory, Error> { fn resolve_dir(from: &str) -> Result<(String, PathBuf), Error> { Ok((from.to_string(), try!(fs::canonicalize(from) .map_err(|_| { Error::Parse { tp: "directory", wher: "include directory", more: Some("not found"), } }) .and_then(|f| if !f.is_file() { Ok(f) } else { Err(Error::WrongFileState { what: "a directory", path: PathBuf::from(from), }) })))) } Ok(if let Some(idx) = s.find('=') { IncludeDirectory::Named { name: s[0..idx].to_string(), dir: try!(resolve_dir(&s[idx + 1..])), } } else { IncludeDirectory::Unnamed { dir: try!(resolve_dir(s)) } }) } } impl fmt::Display for IncludeDirectory { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { IncludeDirectory::Named { ref name, ref dir } => write!(f, "{}={}", name, dir.0), IncludeDirectory::Unnamed { ref dir } => write!(f, "{}", dir.0), } } }