Convert project to use RelativePath where appropriate.

This commit is contained in:
John-John Tedro 2018-01-26 21:40:28 +01:00 committed by John-John Tedro
parent 3ba71c570c
commit dcb7946110
8 changed files with 86 additions and 136 deletions

View File

@ -39,6 +39,7 @@ tempdir = "0.3.4"
itertools = "0.7"
shlex = "0.1"
toml-query = "0.6"
relative-path = { version = "0.3", features = ["serde"] }
# Watch feature
notify = { version = "4.0", optional = true }

View File

@ -1,5 +1,5 @@
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};
use std::path::{Path};
use std::collections::VecDeque;
use std::fs::{self, File};
use std::io::{Read, Write};
@ -7,6 +7,7 @@ use std::io::{Read, Write};
use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
use config::BuildConfig;
use errors::*;
use relative_path::RelativePathBuf;
/// Load a book into memory from its `src/` directory.
pub fn load_book<P: AsRef<Path>>(src_dir: P, cfg: &BuildConfig) -> Result<Book> {
@ -39,7 +40,8 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let next = items.pop().expect("already checked");
if let SummaryItem::Link(ref link) = *next {
let filename = src_dir.join(&link.location);
let filename = link.location.to_path(src_dir);
if !filename.exists() {
if let Some(parent) = filename.parent() {
if !parent.exists() {
@ -150,13 +152,15 @@ pub struct Chapter {
pub number: Option<SectionNumber>,
/// Nested items.
pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file.
pub path: PathBuf,
/// The chapter's relative location.
pub path: RelativePathBuf,
}
impl Chapter {
/// Create a new chapter with the provided content.
pub fn new<P: Into<PathBuf>>(name: &str, content: String, path: P) -> Chapter {
pub fn new<P: Into<RelativePathBuf>>(
name: &str, content: String, path: P,
) -> Chapter {
Chapter {
name: name.to_string(),
content: content,
@ -201,24 +205,16 @@ fn load_chapter<P: AsRef<Path>>(link: &Link, src_dir: P) -> Result<Chapter> {
debug!("Loading {} ({})", link.name, link.location.display());
let src_dir = src_dir.as_ref();
let location = if link.location.is_absolute() {
link.location.clone()
} else {
src_dir.join(&link.location)
};
let location = &link.location;
let mut f = File::open(&location)
let mut f = File::open(location.to_path(src_dir))
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
let mut content = String::new();
f.read_to_string(&mut content)
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
let stripped = location
.strip_prefix(&src_dir)
.expect("Chapters are always inside a book");
let mut ch = Chapter::new(&link.name, content, stripped);
let mut ch = Chapter::new(&link.name, content, &link.location);
ch.number = link.number.clone();
let sub_items = link.nested_items
@ -289,8 +285,9 @@ And here is some \
fn dummy_link() -> (Link, TempDir) {
let temp = TempDir::new("book").unwrap();
let chapter_path = temp.path().join("chapter_1.md");
File::create(&chapter_path)
let chapter_path = RelativePathBuf::from("chapter_1.md");
File::create(&chapter_path.to_path(temp.path()))
.unwrap()
.write(DUMMY_SRC.as_bytes())
.unwrap();
@ -304,9 +301,9 @@ And here is some \
fn nested_links() -> (Link, TempDir) {
let (mut root, temp_dir) = dummy_link();
let second_path = temp_dir.path().join("second.md");
let second_path = RelativePathBuf::from("second.md");
File::create(&second_path)
File::create(&second_path.to_path(&temp_dir))
.unwrap()
.write_all("Hello World!".as_bytes())
.unwrap();
@ -332,7 +329,7 @@ And here is some \
#[test]
fn cant_load_a_nonexistent_chapter() {
let link = Link::new("Chapter 1", "/foo/bar/baz.md");
let link = Link::new("Chapter 1", "foo/bar/baz.md");
let got = load_chapter(&link, "");
assert!(got.is_err());
@ -346,14 +343,14 @@ And here is some \
name: String::from("Nested Chapter 1"),
content: String::from("Hello World!"),
number: Some(SectionNumber(vec![1, 2])),
path: PathBuf::from("second.md"),
path: RelativePathBuf::from("second.md"),
sub_items: Vec::new(),
};
let should_be = BookItem::Chapter(Chapter {
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("chapter_1.md"),
path: RelativePathBuf::from("chapter_1.md"),
sub_items: vec![
BookItem::Chapter(nested.clone()),
BookItem::Separator,
@ -377,7 +374,7 @@ And here is some \
BookItem::Chapter(Chapter {
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
path: PathBuf::from("chapter_1.md"),
path: RelativePathBuf::from("chapter_1.md"),
..Default::default()
}),
],
@ -416,7 +413,7 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("Chapter_1/index.md"),
path: RelativePathBuf::from("Chapter_1/index.md"),
sub_items: vec![
BookItem::Chapter(Chapter::new(
"Hello World",
@ -463,7 +460,7 @@ And here is some \
name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC),
number: None,
path: PathBuf::from("Chapter_1/index.md"),
path: RelativePathBuf::from("Chapter_1/index.md"),
sub_items: vec![
BookItem::Chapter(Chapter::new(
"Hello World",
@ -497,7 +494,7 @@ And here is some \
numbered_chapters: vec![
SummaryItem::Link(Link {
name: String::from("Empty"),
location: PathBuf::from(""),
location: RelativePathBuf::from(""),
..Default::default()
}),
],
@ -511,14 +508,13 @@ And here is some \
#[test]
fn cant_load_chapters_when_the_link_is_a_directory() {
let (_, temp) = dummy_link();
let dir = temp.path().join("nested");
fs::create_dir(&dir).unwrap();
fs::create_dir(temp.path().join("nested")).unwrap();
let summary = Summary {
numbered_chapters: vec![
SummaryItem::Link(Link {
name: String::from("nested"),
location: dir,
location: RelativePathBuf::from("nested"),
..Default::default()
}),
],

View File

@ -221,13 +221,13 @@ impl MDBook {
for item in self.iter() {
if let BookItem::Chapter(ref ch) = *item {
if !ch.path.as_os_str().is_empty() {
let path = self.source_dir().join(&ch.path);
if !ch.path.as_str().is_empty() {
let path = ch.path.to_path(self.source_dir());
let content = utils::fs::file_to_string(&path)?;
info!("Testing file: {:?}", path);
// write preprocessed file to tempdir
let path = temp_dir.path().join(&ch.path);
let path = ch.path.to_path(temp_dir.path());
let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(content.as_bytes())?;

View File

@ -1,9 +1,9 @@
use std::fmt::{self, Display, Formatter};
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use memchr::{self, Memchr};
use pulldown_cmark::{self, Alignment, Event, Tag};
use relative_path::{RelativePath, RelativePathBuf};
use errors::*;
/// Parse the text from a `SUMMARY.md` file into a sort of "recipe" to be
@ -71,7 +71,7 @@ pub struct Link {
pub name: String,
/// The location of the chapter's source file, taking the book's `src`
/// directory as the root.
pub location: PathBuf,
pub location: RelativePathBuf,
/// The section number, if this chapter is in the numbered section.
pub number: Option<SectionNumber>,
/// Any nested items this chapter may contain.
@ -80,10 +80,10 @@ pub struct Link {
impl Link {
/// Create a new link with no nested items.
pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
pub fn new<S: Into<String>, P: AsRef<RelativePath>>(name: S, location: P) -> Link {
Link {
name: name.into(),
location: location.as_ref().to_path_buf(),
location: location.as_ref().to_relative_path_buf(),
number: None,
nested_items: Vec::new(),
}
@ -94,7 +94,7 @@ impl Default for Link {
fn default() -> Self {
Link {
name: String::new(),
location: PathBuf::new(),
location: RelativePathBuf::new(),
number: None,
nested_items: Vec::new(),
}
@ -277,7 +277,7 @@ impl<'a> SummaryParser<'a> {
} else {
Ok(Link {
name: name,
location: PathBuf::from(href.to_string()),
location: RelativePathBuf::from(href.to_string()),
number: None,
nested_items: Vec::new(),
})
@ -617,12 +617,12 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: RelativePathBuf::from("./first.md"),
..Default::default()
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: RelativePathBuf::from("./second.md"),
..Default::default()
}),
];
@ -661,7 +661,7 @@ mod tests {
let src = "[First](./first.md)";
let should_be = Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: RelativePathBuf::from("./first.md"),
..Default::default()
};
@ -682,7 +682,7 @@ mod tests {
let src = "- [First](./first.md)\n";
let link = Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: RelativePathBuf::from("./first.md"),
number: Some(SectionNumber(vec![1])),
..Default::default()
};
@ -703,12 +703,12 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: RelativePathBuf::from("./first.md"),
number: Some(SectionNumber(vec![1])),
nested_items: vec![
SummaryItem::Link(Link {
name: String::from("Nested"),
location: PathBuf::from("./nested.md"),
location: RelativePathBuf::from("./nested.md"),
number: Some(SectionNumber(vec![1, 1])),
nested_items: Vec::new(),
}),
@ -716,7 +716,7 @@ mod tests {
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: RelativePathBuf::from("./second.md"),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),
@ -740,13 +740,13 @@ mod tests {
let should_be = vec![
SummaryItem::Link(Link {
name: String::from("First"),
location: PathBuf::from("./first.md"),
location: RelativePathBuf::from("./first.md"),
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
}),
SummaryItem::Link(Link {
name: String::from("Second"),
location: PathBuf::from("./second.md"),
location: RelativePathBuf::from("./second.md"),
number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(),
}),

View File

@ -95,6 +95,7 @@ extern crate shlex;
extern crate tempdir;
extern crate toml;
extern crate toml_query;
extern crate relative_path;
#[cfg(test)]
#[macro_use]
@ -114,7 +115,7 @@ pub use config::Config;
/// The error types used through out this crate.
pub mod errors {
use std::path::PathBuf;
use relative_path::RelativePathBuf;
error_chain!{
foreign_links {
@ -142,7 +143,7 @@ pub mod errors {
}
/// The user tried to use a reserved filename.
ReservedFilenameError(filename: PathBuf) {
ReservedFilenameError(filename: RelativePathBuf) {
description("Reserved Filename")
display("{} is reserved for internal use", filename.display())
}

View File

@ -31,10 +31,10 @@ impl Preprocessor for LinkPreprocessor {
book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section {
let base = ch.path
let base = ch.path.to_path(&src_dir)
.parent()
.map(|dir| src_dir.join(dir))
.expect("All book items have a parent");
.expect("All book items have a parent")
.to_owned();
let content = replace_all(&ch.content, base);
ch.content = content;

View File

@ -15,6 +15,7 @@ use std::collections::BTreeMap;
use std::collections::HashMap;
use handlebars::Handlebars;
use relative_path::RelativePath;
use serde_json;
@ -26,13 +27,13 @@ impl HtmlHandlebars {
HtmlHandlebars
}
fn write_file<P: AsRef<Path>>(
fn write_file<P: AsRef<RelativePath>>(
&self,
build_dir: &Path,
filename: P,
path: P,
content: &[u8],
) -> Result<()> {
let path = build_dir.join(filename);
let path = path.as_ref().to_path(build_dir);
utils::fs::create_file(&path)?
.write_all(content)
@ -41,8 +42,8 @@ impl HtmlHandlebars {
fn render_item(
&self,
item: &BookItem,
mut ctx: RenderItemContext,
item: &BookItem,
mut ctx: RenderItemContext,
print_content: &mut String,
) -> Result<()> {
// FIXME: This should be made DRY-er and rely less on mutable state
@ -50,15 +51,11 @@ impl HtmlHandlebars {
BookItem::Chapter(ref ch) => {
let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
print_content.push_str(&content);
// Update the context with data for this file
let path = ch.path
.to_str()
.chain_err(|| "Could not convert path to str")?;
// "print.html" is used for the print page.
if ch.path == Path::new("print.md") {
if ch.path == RelativePath::new("print.md") {
bail!(ErrorKind::ReservedFilenameError(ch.path.clone()));
};
@ -72,23 +69,35 @@ impl HtmlHandlebars {
title = ch.name.clone() + " - " + book_title;
}
ctx.data.insert("path".to_owned(), json!(path));
// NB: normalize would translate something like: `a/.././b/c/d` to
// `b/c/d`, if we then skip one and translate we get `../..`.
let path_to_root = ch.path
.normalize().components().skip(1).map(|_| "..").collect::<Vec<_>>()
.join("/");
// TODO: remove trailing slash (and this block), it is only needed to have
// string-perfect backwards compatibility, but `<base>` works the same without it.
let path_to_root = if !path_to_root.is_empty() {
format!("{}/", path_to_root)
} else {
path_to_root
};
ctx.data.insert("path".to_owned(), json!(ch.path));
ctx.data.insert("content".to_owned(), json!(content));
ctx.data.insert("chapter_title".to_owned(), json!(ch.name));
ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert("path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)));
ctx.data.insert("path_to_root".to_owned(), json!(path_to_root));
// Render the handlebars template with the data
debug!("Render template");
let rendered = ctx.handlebars.render("index", &ctx.data)?;
let filepath = Path::new(&ch.path).with_extension("html");
let filepath = ch.path.with_extension("html");
let rendered = self.post_process(
rendered,
&normalize_path(filepath.to_str().ok_or_else(|| {
Error::from(format!("Bad file name: {}", filepath.display()))
})?),
filepath.as_str(),
&ctx.html_config.playpen,
);
@ -112,8 +121,9 @@ impl HtmlHandlebars {
let mut content = String::new();
File::open(destination.join(&ch.path.with_extension("html")))?
.read_to_string(&mut content)?;
let path = ch.path.with_extension("html").to_path(destination);
File::open(&path)?.read_to_string(&mut content)?;
// This could cause a problem when someone displays
// code containing <base href=...>
@ -125,10 +135,7 @@ impl HtmlHandlebars {
self.write_file(destination, "index.html", content.as_bytes())?;
debug!(
"Creating index.html from {} ✓",
destination.join(&ch.path.with_extension("html")).display()
);
debug!("Creating index.html from {} ✓", path.display());
Ok(())
}
@ -226,8 +233,7 @@ impl HtmlHandlebars {
data.insert("is_print".to_owned(), json!(true));
data.insert("path".to_owned(), json!("print.md"));
data.insert("content".to_owned(), json!(print_content));
data.insert("path_to_root".to_owned(),
json!(utils::fs::path_to_root(Path::new("print.md"))));
data.insert("path_to_root".to_owned(), json!(""));
}
fn register_hbs_helpers(&self, handlebars: &mut Handlebars, html_config: &HtmlConfig) {
@ -329,9 +335,7 @@ impl Renderer for HtmlHandlebars {
let rendered = handlebars.render("index", &data)?;
let rendered = self.post_process(rendered,
"print.html",
&html_config.playpen);
let rendered = self.post_process(rendered, "print.html", &html_config.playpen);
self.write_file(&destination, "print.html", &rendered.into_bytes())?;
debug!("Creating print.html ✓");
@ -428,10 +432,7 @@ fn make_data(root: &Path, book: &Book, config: &Config, html_config: &HtmlConfig
}
chapter.insert("name".to_owned(), json!(ch.name));
let path = ch.path
.to_str()
.chain_err(|| "Could not convert path to str")?;
chapter.insert("path".to_owned(), json!(path));
chapter.insert("path".to_owned(), json!(ch.path));
}
BookItem::Separator => {
chapter.insert("spacer".to_owned(), json!("_spacer_"));
@ -624,13 +625,6 @@ struct RenderItemContext<'a> {
html_config: HtmlConfig,
}
pub fn normalize_path(path: &str) -> String {
use std::path::is_separator;
path.chars()
.map(|ch| if is_separator(ch) { '/' } else { ch })
.collect::<String>()
}
pub fn normalize_id(content: &str) -> String {
content.chars()
.filter_map(|ch| if ch.is_alphanumeric() || ch == '_' || ch == '-' {

View File

@ -1,4 +1,4 @@
use std::path::{Component, Path, PathBuf};
use std::path::Path;
use errors::*;
use std::io::Read;
use std::fs::{self, File};
@ -16,48 +16,6 @@ pub fn file_to_string<P: AsRef<Path>>(path: P) -> Result<String> {
Ok(content)
}
/// Takes a path and returns a path containing just enough `../` to point to
/// the root of the given path.
///
/// This is mostly interesting for a relative path to point back to the
/// directory from where the path starts.
///
/// ```rust
/// # extern crate mdbook;
/// #
/// # use std::path::Path;
/// # use mdbook::utils::fs::path_to_root;
/// #
/// # fn main() {
/// let path = Path::new("some/relative/path");
/// assert_eq!(path_to_root(path), "../../");
/// # }
/// ```
///
/// **note:** it's not very fool-proof, if you find a situation where
/// it doesn't return the correct path.
/// Consider [submitting a new issue](https://github.com/rust-lang-nursery/mdBook/issues)
/// or a [pull-request](https://github.com/rust-lang-nursery/mdBook/pulls) to improve it.
pub fn path_to_root<P: Into<PathBuf>>(path: P) -> String {
debug!("path_to_root");
// Remove filename and add "../" for every directory
path.into()
.parent()
.expect("")
.components()
.fold(String::new(), |mut s, c| {
match c {
Component::Normal(_) => s.push_str("../"),
_ => {
debug!("Other path component... {:?}", c);
}
}
s
})
}
/// This function creates a file and returns it. But before creating the file
/// it checks every directory in the path to see if it exists,
/// and if it does not it will be created.