use const_format::concatcp;
use html_parser::{Dom, Node};
use mdbook::book::BookItem;
use mdbook::renderer::RenderContext;
use mime_guess::Mime;
use pulldown_cmark::{Event, Tag};
use std::collections::HashMap;
use std::path::{Path, PathBuf, MAIN_SEPARATOR_STR};
use url::Url;
use crate::{utils, Error};
const UPPER_PARENT: &str = concatcp!("..", MAIN_SEPARATOR_STR);
const UPPER_PARENT_LINUX: &str = concatcp!("..", "/");
const UPPER_PARENT_STARTS_SLASH: &str = concatcp!(MAIN_SEPARATOR_STR, "..", MAIN_SEPARATOR_STR);
const UPPER_PARENT_STARTS_SLASH_LINUX: &str = concatcp!("/", "..", "/");
#[cfg(not(target_os = "windows"))]
const UPPER_FOLDER_PATHS: &[&str] = &[MAIN_SEPARATOR_STR, UPPER_PARENT, UPPER_PARENT_LINUX];
#[cfg(target_os = "windows")]
const UPPER_FOLDER_PATHS: &[&str] = &[&"/", MAIN_SEPARATOR_STR, UPPER_PARENT, UPPER_PARENT_LINUX];
pub(crate) fn find(ctx: &RenderContext) -> Result<HashMap<String, Asset>, Error> {
let mut assets: HashMap<String, Asset> = HashMap::new();
debug!("Finding resources by:\n{:?}", ctx.config);
let src_dir = ctx.root.join(&ctx.config.book.src).canonicalize()?;
debug!(
"Start iteration over a [{:?}] sections in src_dir = {:?}",
ctx.book.sections.len(),
src_dir
);
for section in ctx.book.iter() {
match *section {
BookItem::Chapter(ref ch) => {
let mut assets_count = 0;
debug!("Searching links and assets for: '{}'", ch);
if ch.path.is_none() {
debug!("'{}' is a draft chapter and should be no content.", ch.name);
continue;
}
for link in find_assets_in_markdown(&ch.content)? {
let asset = match Url::parse(&link) {
Ok(url) => Asset::from_url(url, &ctx.destination),
Err(_) => Asset::from_local(&link, &src_dir, ch.path.as_ref().unwrap()),
}?;
debug!(
"Check relative path assets for: '{}' for {:?}",
ch.name, asset
);
match asset.source {
AssetKind::Local(_) => {
let relative = asset.location_on_disk.strip_prefix(&src_dir);
match relative {
Ok(relative_link_path) => {
let link_key: String =
String::from(relative_link_path.to_str().unwrap());
if !assets.contains_key(&link_key) {
debug!(
"Adding asset by link '{:?}' : {:#?}",
link_key, &asset
);
assets.insert(link_key, asset);
assets_count += 1;
} else {
debug!("Skipped asset for '{}'", link_key);
}
}
_ => {
warn!("Sorry, we can't add 'Local asset' that is outside of book's /src/ folder, {:?}", &asset);
}
}
}
AssetKind::Remote(_) => {
let link_key: String =
String::from(asset.location_on_disk.to_str().unwrap());
debug!(
"Adding Remote asset by link '{:?}' : {:#?}",
link_key, &asset
);
assets.insert(link_key, asset);
assets_count += 1;
}
};
}
debug!(
"Found '{}' links and assets inside '{}'",
assets_count, ch.name
);
}
BookItem::Separator => trace!("Skip separator."),
BookItem::PartTitle(ref title) => trace!("Skip part title: {}.", title),
}
}
debug!("Added '{}' links and assets in total", assets.len());
Ok(assets)
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) enum AssetKind {
Remote(Url),
Local(PathBuf),
}
#[derive(Clone, PartialEq, Debug)]
pub(crate) struct Asset {
pub(crate) location_on_disk: PathBuf,
pub(crate) filename: PathBuf,
pub(crate) mimetype: Mime,
pub(crate) source: AssetKind,
}
impl Asset {
pub(crate) fn new<P, Q, K>(filename: P, absolute_location: Q, source: K) -> Self
where
P: Into<PathBuf>,
Q: Into<PathBuf>,
K: Into<AssetKind>,
{
let location_on_disk = absolute_location.into();
let mt = mime_guess::from_path(&location_on_disk).first_or_octet_stream();
let source = source.into();
Self {
location_on_disk,
filename: filename.into(),
mimetype: mt,
source,
}
}
fn from_url(url: Url, dest_dir: &Path) -> Result<Asset, Error> {
trace!("Extract from URL: {:#?} into folder = {:?}", url, dest_dir);
let filename = utils::hash_link(&url);
let dest_dir = utils::normalize_path(dest_dir);
let full_filename = dest_dir.join(filename);
let absolute_location = utils::normalize_path(full_filename.as_path());
let filename = absolute_location.strip_prefix(dest_dir).unwrap();
let asset = Asset::new(filename, &absolute_location, AssetKind::Remote(url));
debug!("Created from URL: {:#?}", asset);
Ok(asset)
}
fn from_local(link: &str, src_dir: &Path, chapter_path: &Path) -> Result<Asset, Error> {
debug!(
"Composing asset path for {:?} + {:?} in chapter = {:?}",
src_dir, link, chapter_path
);
let chapter_path = src_dir.join(chapter_path);
let stripped_path = Self::compute_asset_path_by_src_and_link(link, &chapter_path);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
debug!("Composing full_filename by '{:?}' + '{:?}'", &stripped_path, &normalized_link.clone());
let full_filename = stripped_path.join(normalized_link);
debug!("Joined full_filename = {:?}", &full_filename.display());
let absolute_location = full_filename.canonicalize().map_err(|this_error| {
Error::AssetFileNotFound(format!(
"Asset was not found: '{link}' by '{}', error = {}",
&full_filename.display(),
this_error
))
})?;
if !absolute_location.is_file() || absolute_location.is_symlink() {
return Err(Error::AssetFile(absolute_location));
}
let binding = utils::normalize_path(Path::new(link.clone()));
debug!("Extracting file name from = {:?}, binding = '{binding:?}'", &full_filename.display());
let filename;
if cfg!(target_os = "windows") {
filename = binding.as_os_str().to_os_string()
.into_string().expect("Error getting filename for Local Asset").replace("\\", "/");
} else {
filename = String::from(binding.as_path().to_str().unwrap());
}
let asset = Asset::new(
filename,
&absolute_location,
AssetKind::Local(PathBuf::from(link)),
);
trace!(
"[{:#?}] = {:?} : {:?}",
asset.source,
asset.filename,
asset.location_on_disk
);
debug!("Created from local: {:#?}", asset);
Ok(asset)
}
fn compute_asset_path_by_src_and_link(link: &str, chapter_dir: &PathBuf) -> PathBuf {
let mut reassigned_asset_root: PathBuf = PathBuf::from(chapter_dir);
let link_string = String::from(link);
if chapter_dir.is_file() {
reassigned_asset_root.pop();
}
trace!("check if parent present by '{}' = '{}' || '{}' || '{}'",
link_string, MAIN_SEPARATOR_STR, UPPER_PARENT, UPPER_PARENT_STARTS_SLASH);
if !link_string.is_empty()
&& (link_string.starts_with(MAIN_SEPARATOR_STR)
|| link_string.starts_with(UPPER_PARENT_LINUX)
|| link_string.starts_with(UPPER_PARENT)
|| link_string.starts_with(UPPER_PARENT_STARTS_SLASH)
|| link_string.starts_with(UPPER_PARENT_STARTS_SLASH_LINUX))
{
reassigned_asset_root.pop(); let new_link = Self::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
reassigned_asset_root = Self::compute_asset_path_by_src_and_link(&new_link, &reassigned_asset_root);
}
reassigned_asset_root }
fn remove_prefixes<'a>(link_to_strip: String, prefixes: &[&str]) -> String {
let mut stripped_link = String::from(link_to_strip.clone());
for prefix in prefixes {
match link_to_strip.strip_prefix(prefix) {
Some(s) => {
stripped_link = s.to_string();
return stripped_link
},
None => &link_to_strip
};
};
stripped_link
}
}
fn find_assets_in_markdown(chapter_src_content: &str) -> Result<Vec<String>, Error> {
let mut found_asset = Vec::new();
let pull_down_parser = utils::create_new_pull_down_parser(chapter_src_content);
for event in pull_down_parser {
match event {
Event::Start(Tag::Image(_, dest, _)) => {
found_asset.push(dest.to_string());
}
Event::Html(html) => {
let content = html.into_string();
if let Ok(dom) = Dom::parse(&content) {
for item in dom.children {
match item {
Node::Element(ref element) if element.name == "img" => {
if let Some(dest) = &element.attributes["src"] {
found_asset.push(dest.clone());
}
}
_ => {}
}
}
}
}
_ => {}
}
}
found_asset.sort();
found_asset.dedup();
if !found_asset.is_empty() {
trace!("Assets found in content : {:?}", found_asset);
}
Ok(found_asset)
}
pub(crate) mod handler {
use std::{
fs::{self, File, OpenOptions},
io::{self, Read},
path::Path,
};
#[cfg(test)]
use mockall::automock;
use crate::Error;
use super::{Asset, AssetKind};
#[cfg_attr(test, automock)]
pub(crate) trait ContentRetriever {
fn download(&self, asset: &Asset) -> Result<(), Error> {
if let AssetKind::Remote(url) = &asset.source {
let dest = &asset.location_on_disk;
if dest.is_file() {
debug!("Cache file {:?} to '{}' already exists.", dest, url);
} else {
if let Some(cache_dir) = dest.parent() {
fs::create_dir_all(cache_dir)?;
}
debug!("Downloading asset : {}", url);
let mut file = OpenOptions::new().create(true).write(true).open(dest)?;
let mut resp = self.retrieve(url.as_str())?;
io::copy(&mut resp, &mut file)?;
debug!("Downloaded asset by '{}'", url);
}
}
Ok(())
}
fn read(&self, path: &Path, buffer: &mut Vec<u8>) -> Result<(), Error> {
File::open(path)?.read_to_end(buffer)?;
Ok(())
}
fn retrieve(&self, url: &str) -> Result<Box<(dyn Read + Send + Sync + 'static)>, Error>;
}
pub(crate) struct ResourceHandler;
impl ContentRetriever for ResourceHandler {
fn retrieve(&self, url: &str) -> Result<Box<(dyn Read + Send + Sync + 'static)>, Error> {
let res = ureq::get(url).call()?;
match res.status() {
200 => Ok(res.into_reader()),
404 => Err(Error::AssetFileNotFound(format!(
"Missing remote resource: {url}"
))),
_ => unreachable!("Unexpected response status for '{url}'"),
}
}
}
#[cfg(test)]
mod tests {
use super::ContentRetriever;
use crate::{resources::Asset, Error};
use tempdir::TempDir;
type BoxRead = Box<(dyn std::io::Read + Send + Sync + 'static)>;
#[test]
fn download_success() {
use std::io::Read;
struct TestHandler;
impl ContentRetriever for TestHandler {
fn retrieve(&self, _url: &str) -> Result<BoxRead, Error> {
Ok(Box::new("Downloaded content".as_bytes()))
}
}
let cr = TestHandler {};
let a = temp_remote_asset("https://mdbook-epub.org/image.svg").unwrap();
let r = cr.download(&a);
assert!(r.is_ok());
let mut buffer = String::new();
let mut f = std::fs::File::open(&a.location_on_disk).unwrap();
f.read_to_string(&mut buffer).unwrap();
assert_eq!(buffer, "Downloaded content");
}
#[test]
fn download_fail_when_resource_not_exist() {
struct TestHandler;
impl ContentRetriever for TestHandler {
fn retrieve(&self, url: &str) -> Result<BoxRead, Error> {
Err(Error::AssetFileNotFound(format!(
"Missing remote resource: {url}"
)))
}
}
let cr = TestHandler {};
let a = temp_remote_asset("https://mdbook-epub.org/not-exist.svg").unwrap();
let r = cr.download(&a);
assert!(r.is_err());
assert!(matches!(r.unwrap_err(), Error::AssetFileNotFound(_)));
}
#[test]
#[should_panic(expected = "NOT 200 or 404")]
fn download_fail_with_unexpected_status() {
struct TestHandler;
impl ContentRetriever for TestHandler {
fn retrieve(&self, _url: &str) -> Result<BoxRead, Error> {
panic!("NOT 200 or 404")
}
}
let cr = TestHandler {};
let a = temp_remote_asset("https://mdbook-epub.org/bad.svg").unwrap();
let r = cr.download(&a);
panic!("{}", r.unwrap_err().to_string());
}
fn temp_remote_asset(url: &str) -> Result<Asset, Error> {
let dest_dir = TempDir::new("mdbook-epub")?;
Asset::from_url(url::Url::parse(url).unwrap(), dest_dir.path())
}
}
}
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
use super::*;
#[test]
fn test_find_images() {
let parent_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy/src");
let upper_parent_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy");
let src =
"![Image 1](./rust-logo.png)\n[a link](to/nowhere) ![Image 2][2]\n\n[2]: reddit.svg\n\
\n\n<img alt=\"Rust Logo in html\" src=\"reddit.svg\" class=\"center\" style=\"width: 20%;\" />\n\n\
\n\n![Image 4](assets/rust-logo.png)\n[a link](to/nowhere)
![Image 4](../third_party/wikimedia/Epub_logo_color.svg)\n[a link](to/nowhere)";
let should_be = vec![
upper_parent_dir
.join("third_party/wikimedia/Epub_logo_color.svg")
.canonicalize()
.unwrap(),
parent_dir.join("rust-logo.png").canonicalize().unwrap(),
parent_dir
.join("assets/rust-logo.png")
.canonicalize()
.unwrap(),
parent_dir.join("reddit.svg").canonicalize().unwrap(),
];
let got = find_assets_in_markdown(src)
.unwrap()
.into_iter()
.map(|a| parent_dir.join(a).canonicalize().unwrap())
.collect::<Vec<_>>();
assert_eq!(got, should_be);
}
#[test]
fn find_local_asset() {
let link = "./rust-logo.png";
let link2 = "assets/rust-logo.png";
let not_found_link3 = "../third_party/wikimedia/Epub_logo_color.svg";
let temp = tempdir::TempDir::new("mdbook-epub").unwrap();
let dest_dir = temp.path().to_string_lossy().to_string();
let chapters = json!([{
"Chapter": {
"name": "Chapter 1",
"content": format!("# Chapter 1\r\n\r\n![Image]({link})\r\n![Image]({link2})\r\n![Image]({not_found_link3})"),
"number": [1],
"sub_items": [],
"path": "chapter_1.md",
"parent_names": []}
}]);
let ctx = ctx_with_chapters(&chapters, &dest_dir).unwrap();
let mut assets = find(&ctx).unwrap();
assert!(assets.len() == 2);
fn assert_asset(a: Asset, link: &str, ctx: &RenderContext) {
let link_as_path = utils::normalize_path(&PathBuf::from(link));
let mut src_path = PathBuf::from(&ctx.config.book.src);
if link.starts_with(UPPER_PARENT) || link.starts_with(UPPER_PARENT_STARTS_SLASH) {
src_path.pop();
}
let filename = link_as_path.as_path().to_str().unwrap();
let absolute_location = PathBuf::from(&ctx.root)
.join(&src_path)
.join(&link_as_path)
.canonicalize()
.expect("Asset Location is not found");
let source = AssetKind::Local(PathBuf::from(link));
let should_be = Asset::new(filename, absolute_location, source);
assert_eq!(a, should_be);
}
assert_asset(assets.remove(
utils::normalize_path(&PathBuf::from(link)).to_str().unwrap()
).unwrap(), link, &ctx);
assert_asset(assets.remove(
utils::normalize_path(&PathBuf::from(link2)).to_str().unwrap()
).unwrap(), link2, &ctx);
}
#[test]
fn find_remote_asset() {
let link = "https://www.rust-lang.org/static/images/rust-logo-blk.svg";
let link2 = "https://www.rust-lang.org/static/images/rust-logo-blk.png";
let link_parsed = Url::parse(link).unwrap();
let link_parsed2 = Url::parse(link2).unwrap();
let temp = tempdir::TempDir::new("mdbook-epub").unwrap();
let dest_dir = temp.path().to_string_lossy().to_string();
let chapters = json!([
{"Chapter": {
"name": "Chapter 1",
"content": format!("# Chapter 1\r\n\r\n![Image]({link})\r\n<a href=\"\"><img src=\"{link2}\"></a>"),
"number": [1],
"sub_items": [],
"path": "chapter_1.md",
"parent_names": []}}]);
let ctx = ctx_with_chapters(&chapters, &dest_dir).unwrap();
let mut assets = find(&ctx).unwrap();
assert!(assets.len() == 2);
for (key, value) in assets.clone().into_iter() {
trace!("{} / {:?}", key, &value);
match value.source {
AssetKind::Remote(internal_url) => {
let key_to_remove = value.location_on_disk.to_str().unwrap();
let got = assets.remove(key_to_remove).unwrap();
let filename;
if key_to_remove.contains(".svg") {
filename = PathBuf::from("").join(utils::hash_link(&link_parsed));
} else {
filename = PathBuf::from("").join(utils::hash_link(&link_parsed2));
}
let absolute_location = temp.path().join(&filename);
let source = AssetKind::Remote(internal_url);
let should_be = Asset::new(filename, absolute_location, source);
assert_eq!(got, should_be);
}
_ => {
panic!("Should not be here... only remote urls are used here")
}
}
}
}
#[test]
fn find_draft_chapter_without_error() {
let temp = tempdir::TempDir::new("mdbook-epub").unwrap();
let dest_dir = temp.into_path().to_string_lossy().to_string();
let chapters = json!([
{"Chapter": {
"name": "Chapter 1",
"content": "",
"number": [1],
"sub_items": [],
"path": null,
"parent_names": []}}]);
let ctx = ctx_with_chapters(&chapters, &dest_dir).unwrap();
assert!(find(&ctx).unwrap().is_empty());
}
#[test]
#[should_panic(expected = "Asset was not found")]
fn find_asset_fail_when_chapter_dir_not_exist() {
panic!(
"{}",
Asset::from_local("a.png", Path::new("tests\\dummy\\src"), Path::new("ch\\a.md"))
.unwrap_err()
.to_string()
);
}
#[cfg(not(target_os = "windows"))]
#[test]
#[should_panic(expected = "Asset was not found")]
fn find_asset_fail_when_chapter_dir_not_exist_linux() {
panic!(
"{}",
Asset::from_local("a.png", Path::new("tests/dummy/src"), Path::new("ch/a.md"))
.unwrap_err()
.to_string()
);
}
#[cfg(not(target_os = "windows"))]
#[test]
#[should_panic(
expected = "Asset was not found: 'wikimedia' by 'tests/dummy/third_party/a.md/wikimedia', error = No such file or directory (os error 2)"
)]
fn find_asset_fail_when_it_is_a_dir() {
panic!(
"{}",
Asset::from_local(
"wikimedia",
Path::new("tests/dummy"),
Path::new("third_party/a.md")
)
.unwrap_err()
.to_string()
);
}
#[cfg(target_os = "windows")]
#[test]
#[should_panic(
//expected = "Asset was not found: 'wikimedia' by 'tests\\dummy\\third_party\\a.md\\wikimedia', error = Системе не удается найти указанный путь. (os error 3)"
expected = "Asset was not found: 'wikimedia' by 'tests\\dummy\\third_party\\a.md\\wikimedia', error = The system cannot find the path specified. (os error 3)"
)]
fn find_asset_fail_when_it_is_a_dir_windows() {
panic!(
"{}",
Asset::from_local(
"wikimedia",
Path::new("tests\\dummy"),
Path::new("third_party\\a.md")
)
.unwrap_err()
.to_string()
);
}
fn ctx_with_chapters(
chapters: &Value,
destination: &str,
) -> Result<RenderContext, mdbook::errors::Error> {
let json_ctx = json!({
"version": mdbook::MDBOOK_VERSION,
"root": "tests/dummy",
"book": {"sections": chapters, "__non_exhaustive": null},
"config": {
"book": {"authors": [], "language": "en", "multilingual": false,
"src": "src", "title": "DummyBook"},
"output": {"epub": {"curly-quotes": true}}},
"destination": destination
});
RenderContext::from_json(json_ctx.to_string().as_bytes())
}
#[test]
fn test_compute_asset_path_by_src_and_link_to_full_path() {
let book_source_root_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/dummy/src");
let mut book_chapter_dir = PathBuf::from(book_source_root_dir);
book_chapter_dir.push(Path::new("chapter_1.md"));
let link = "./asset1.jpg";
let asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_chapter_dir);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
let full_path = asset_path.join(normalized_link); assert_eq!(
full_path.as_path(),
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/dummy/src")
.join("asset1.jpg")
);
}
#[test]
fn test_remove_prefixes() {
let link_string = String::from("assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets/verify.jpeg", link_string);
let link_string = String::from("/assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets/verify.jpeg", link_string);
let link_string = String::from("../../assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("../assets/verify.jpeg", link_string);
let new_link = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets/verify.jpeg", new_link);
let upper_folder_path = &[UPPER_PARENT_LINUX, UPPER_PARENT, MAIN_SEPARATOR_STR, &"/"];
let link_string = String::from("assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets/verify.jpeg", link_string);
let link_string = String::from("/assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets/verify.jpeg", link_string);
let link_string = String::from("../../assets/verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("../assets/verify.jpeg", link_string);
let new_link = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets/verify.jpeg", new_link);
}
#[cfg(target_os = "windows")]
#[test]
fn test_remove_prefixes_windows() {
let link_string = String::from("assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets\\verify.jpeg", link_string);
let link_string = String::from("\\assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets\\verify.jpeg", link_string);
let link_string = String::from("..\\..\\assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("..\\assets\\verify.jpeg", link_string);
let new_link = Asset::remove_prefixes(link_string, UPPER_FOLDER_PATHS);
assert_eq!("assets\\verify.jpeg", new_link);
let upper_folder_path = &[UPPER_PARENT_LINUX, UPPER_PARENT, MAIN_SEPARATOR_STR, &"/"];
let link_string = String::from("assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets\\verify.jpeg", link_string);
let link_string = String::from("/assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets\\verify.jpeg", link_string);
let link_string = String::from("..\\..\\assets\\verify.jpeg");
let link_string = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("..\\assets\\verify.jpeg", link_string);
let new_link = Asset::remove_prefixes(link_string, upper_folder_path);
assert_eq!("assets\\verify.jpeg", new_link);
}
#[test]
fn test_compute_asset_path_by_src_and_link() {
let mut book_or_chapter_src = ["media", "book", "src"].iter().collect::<PathBuf>();
let mut link = "./asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path().as_os_str(),
(["media", "book", "src", "asset1.jpg"]).iter().collect::<PathBuf>().as_os_str()
);
link = "asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = "../upper/assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "upper", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = "assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = "./assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
book_or_chapter_src = ["media", "book", "src", "chapter1"].iter().collect::<PathBuf>();
link = "../assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
book_or_chapter_src = ["media", "book", "src", "chapter1", "inner"].iter().collect::<PathBuf>();
link = "../../assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
}
#[cfg(target_os = "windows")]
#[test]
fn test_compute_asset_path_by_src_and_link_windows() {
let mut book_or_chapter_src = ["media", "book", "src"].iter().collect::<PathBuf>();
let mut link = ".\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path().as_os_str(),
(["media", "book", "src", "asset1.jpg"]).iter().collect::<PathBuf>().as_os_str()
);
link = "asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = "..\\upper\\assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "upper", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = "assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
link = ".\\assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
book_or_chapter_src = ["media", "book", "src", "chapter1"].iter().collect::<PathBuf>();
link = "..\\assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
book_or_chapter_src = ["media", "book", "src", "chapter1", "inner"].iter().collect::<PathBuf>();
link = "..\\..\\assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(
asset_path.as_path(),
["media", "book", "src", "assets", "asset1.jpg"].iter().collect::<PathBuf>()
);
}
#[cfg(not(target_os = "windows"))]
#[test]
fn incorrect_compute_asset_path_by_src_and_link() {
let book_or_chapter_src = ["media", "book", "src"].iter().collect::<PathBuf>();
let link = "/assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(asset_path.as_path(), Path::new("/assets/asset1.jpg"));
let link = "/../assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(asset_path.as_path(), Path::new("/assets/asset1.jpg"));
}
#[cfg(target_os = "windows")]
#[test]
fn incorrect_compute_asset_path_by_src_and_link_windows() {
let book_or_chapter_src = ["media", "book", "src"].iter().collect::<PathBuf>();
let link = "\\assets\\asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(asset_path.as_path(), Path::new("/assets/asset1.jpg"));
let link = "\\..\\assets/asset1.jpg";
let mut asset_path = Asset::compute_asset_path_by_src_and_link(link, &book_or_chapter_src);
let normalized_link = utils::normalize_path(PathBuf::from(link).as_path());
asset_path = asset_path.join(normalized_link); assert_eq!(asset_path.as_path(), Path::new("/assets/asset1.jpg"));
}
}