Bring back draft chapters

This commit is contained in:
Mathieu David 2020-02-29 17:55:45 +01:00 committed by Eric Huss
parent 7e11d37e49
commit d605938886
10 changed files with 258 additions and 173 deletions

View File

@ -10,6 +10,7 @@
- [clean](cli/clean.md) - [clean](cli/clean.md)
- [Format](format/README.md) - [Format](format/README.md)
- [SUMMARY.md](format/summary.md) - [SUMMARY.md](format/summary.md)
- [Draft chapter]()
- [Configuration](format/config.md) - [Configuration](format/config.md)
- [Theme](format/theme/README.md) - [Theme](format/theme/README.md)
- [index.hbs](format/theme/index-hbs.md) - [index.hbs](format/theme/index-hbs.md)

View File

@ -7,7 +7,7 @@ are. Without this file, there is no book.
Even though `SUMMARY.md` is a markdown file, the formatting is very strict to Even though `SUMMARY.md` is a markdown file, the formatting is very strict to
allow for easy parsing. Let's see how you should format your `SUMMARY.md` file. allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
#### Allowed elements #### Structure
1. ***Title*** It's common practice to begin with a title, generally <code 1. ***Title*** It's common practice to begin with a title, generally <code
class="language-markdown"># Summary</code>. But it is not mandatory, the class="language-markdown"># Summary</code>. But it is not mandatory, the
@ -36,3 +36,19 @@ allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
All other elements are unsupported and will be ignored at best or result in an All other elements are unsupported and will be ignored at best or result in an
error. error.
#### Other elements
- ***Separators*** In between chapters you can add a separator. In the HTML renderer
this will result in a line being rendered in the table of contents. A separator is
a line containing exclusively dashes and at least three of them: `---`.
- ***Draft chapters*** Draft chapters are chapters without a file and thus content.
The purpose of a draft chapter is to signal future chapters still to be written.
Or when still laying out the structure of the book to avoid creating the files
while you are still changing the structure of the book a lot.
Draft chapters will be rendered in the HTML renderer as disabled links in the table
of contents, as you can see for the next chapter in the table of contents on the left.
Draft chapters are written like normal chapters but without writing the path to the file
```markdown
- [Draft chapter]()
```

View File

@ -39,7 +39,8 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let next = items.pop().expect("already checked"); let next = items.pop().expect("already checked");
if let SummaryItem::Link(ref link) = *next { if let SummaryItem::Link(ref link) = *next {
let filename = src_dir.join(&link.location); if let Some(ref location) = link.location {
let filename = src_dir.join(location);
if !filename.exists() { if !filename.exists() {
if let Some(parent) = filename.parent() { if let Some(parent) = filename.parent() {
if !parent.exists() { if !parent.exists() {
@ -51,6 +52,7 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
let mut f = File::create(&filename)?; let mut f = File::create(&filename)?;
writeln!(f, "# {}", link.name)?; writeln!(f, "# {}", link.name)?;
} }
}
items.extend(&link.nested_items); items.extend(&link.nested_items);
} }
@ -152,7 +154,7 @@ pub struct Chapter {
/// Nested items. /// Nested items.
pub sub_items: Vec<BookItem>, pub sub_items: Vec<BookItem>,
/// The chapter's location, relative to the `SUMMARY.md` file. /// The chapter's location, relative to the `SUMMARY.md` file.
pub path: PathBuf, pub path: Option<PathBuf>,
/// An ordered list of the names of each chapter above this one, in the hierarchy. /// An ordered list of the names of each chapter above this one, in the hierarchy.
pub parent_names: Vec<String>, pub parent_names: Vec<String>,
} }
@ -168,11 +170,31 @@ impl Chapter {
Chapter { Chapter {
name: name.to_string(), name: name.to_string(),
content, content,
path: path.into(), path: Some(path.into()),
parent_names, parent_names,
..Default::default() ..Default::default()
} }
} }
/// Create a new draft chapter that is not attached to a source markdown file and has
/// thus no content.
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
Chapter {
name: name.to_string(),
content: String::new(),
path: None,
parent_names,
..Default::default()
}
}
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file
pub fn is_draft_chapter(&self) -> bool {
match self.path {
Some(_) => false,
None => true,
}
}
} }
/// Use the provided `Summary` to load a `Book` from disk. /// Use the provided `Summary` to load a `Book` from disk.
@ -202,7 +224,7 @@ pub(crate) fn load_book_from_disk<P: AsRef<Path>>(summary: &Summary, src_dir: P)
}) })
} }
fn load_summary_item<P: AsRef<Path>>( fn load_summary_item<P: AsRef<Path> + Clone>(
item: &SummaryItem, item: &SummaryItem,
src_dir: P, src_dir: P,
parent_names: Vec<String>, parent_names: Vec<String>,
@ -215,22 +237,23 @@ fn load_summary_item<P: AsRef<Path>>(
} }
} }
fn load_chapter<P: AsRef<Path>>( fn load_chapter<P: AsRef<Path> + Clone>(
link: &Link, link: &Link,
src_dir: P, src_dir: P,
parent_names: Vec<String>, parent_names: Vec<String>,
) -> Result<Chapter> { ) -> Result<Chapter> {
debug!("Loading {} ({})", link.name, link.location.display()); let mut ch = if let Some(ref link_location) = link.location {
debug!("Loading {} ({})", link.name, link_location.display());
let src_dir = src_dir.as_ref(); let src_dir = src_dir.as_ref();
let location = if link.location.is_absolute() { let location = if link_location.is_absolute() {
link.location.clone() link_location.clone()
} else { } else {
src_dir.join(&link.location) src_dir.join(link_location)
}; };
let mut f = File::open(&location) let mut f = File::open(&location)
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?; .chain_err(|| format!("Chapter file not found, {}", link_location.display()))?;
let mut content = String::new(); let mut content = String::new();
f.read_to_string(&mut content) f.read_to_string(&mut content)
@ -240,15 +263,20 @@ fn load_chapter<P: AsRef<Path>>(
.strip_prefix(&src_dir) .strip_prefix(&src_dir)
.expect("Chapters are always inside a book"); .expect("Chapters are always inside a book");
Chapter::new(&link.name, content, stripped, parent_names.clone())
} else {
Chapter::new_draft(&link.name, parent_names.clone())
};
let mut sub_item_parents = parent_names.clone(); let mut sub_item_parents = parent_names.clone();
let mut ch = Chapter::new(&link.name, content, stripped, parent_names);
ch.number = link.number.clone(); ch.number = link.number.clone();
sub_item_parents.push(link.name.clone()); sub_item_parents.push(link.name.clone());
let sub_items = link let sub_items = link
.nested_items .nested_items
.iter() .iter()
.map(|i| load_summary_item(i, src_dir, sub_item_parents.clone())) .map(|i| load_summary_item(i, src_dir.clone(), sub_item_parents.clone()))
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
ch.sub_items = sub_items; ch.sub_items = sub_items;
@ -376,7 +404,7 @@ And here is some \
name: String::from("Nested Chapter 1"), name: String::from("Nested Chapter 1"),
content: String::from("Hello World!"), content: String::from("Hello World!"),
number: Some(SectionNumber(vec![1, 2])), number: Some(SectionNumber(vec![1, 2])),
path: PathBuf::from("second.md"), path: Some(PathBuf::from("second.md")),
parent_names: vec![String::from("Chapter 1")], parent_names: vec![String::from("Chapter 1")],
sub_items: Vec::new(), sub_items: Vec::new(),
}; };
@ -384,7 +412,7 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("chapter_1.md"), path: Some(PathBuf::from("chapter_1.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(nested.clone()), BookItem::Chapter(nested.clone()),
@ -408,7 +436,7 @@ And here is some \
sections: vec![BookItem::Chapter(Chapter { sections: vec![BookItem::Chapter(Chapter {
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
path: PathBuf::from("chapter_1.md"), path: Some(PathBuf::from("chapter_1.md")),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()
@ -448,7 +476,7 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("Chapter_1/index.md"), path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(Chapter::new( BookItem::Chapter(Chapter::new(
@ -500,7 +528,7 @@ And here is some \
name: String::from("Chapter 1"), name: String::from("Chapter 1"),
content: String::from(DUMMY_SRC), content: String::from(DUMMY_SRC),
number: None, number: None,
path: PathBuf::from("Chapter_1/index.md"), path: Some(PathBuf::from("Chapter_1/index.md")),
parent_names: Vec::new(), parent_names: Vec::new(),
sub_items: vec![ sub_items: vec![
BookItem::Chapter(Chapter::new( BookItem::Chapter(Chapter::new(
@ -537,7 +565,7 @@ And here is some \
let summary = Summary { let summary = Summary {
numbered_chapters: vec![SummaryItem::Link(Link { numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("Empty"), name: String::from("Empty"),
location: PathBuf::from(""), location: Some(PathBuf::from("")),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()
@ -556,7 +584,7 @@ And here is some \
let summary = Summary { let summary = Summary {
numbered_chapters: vec![SummaryItem::Link(Link { numbered_chapters: vec![SummaryItem::Link(Link {
name: String::from("nested"), name: String::from("nested"),
location: dir, location: Some(dir),
..Default::default() ..Default::default()
})], })],
..Default::default() ..Default::default()

View File

@ -251,12 +251,13 @@ impl MDBook {
for item in book.iter() { for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item { if let BookItem::Chapter(ref ch) = *item {
if !ch.path.as_os_str().is_empty() { if let Some(ref chapter_path) = ch.path {
let path = self.source_dir().join(&ch.path); if !chapter_path.as_os_str().is_empty() {
let path = self.source_dir().join(&chapter_path);
info!("Testing file: {:?}", path); info!("Testing file: {:?}", path);
// write preprocessed file to tempdir // write preprocessed file to tempdir
let path = temp_dir.path().join(&ch.path); let path = temp_dir.path().join(&chapter_path);
let mut tmpf = utils::fs::create_file(&path)?; let mut tmpf = utils::fs::create_file(&path)?;
tmpf.write_all(ch.content.as_bytes())?; tmpf.write_all(ch.content.as_bytes())?;
@ -285,6 +286,7 @@ impl MDBook {
} }
} }
} }
}
Ok(()) Ok(())
} }

View File

@ -71,7 +71,7 @@ pub struct Link {
pub name: String, pub name: String,
/// The location of the chapter's source file, taking the book's `src` /// The location of the chapter's source file, taking the book's `src`
/// directory as the root. /// directory as the root.
pub location: PathBuf, pub location: Option<PathBuf>,
/// The section number, if this chapter is in the numbered section. /// The section number, if this chapter is in the numbered section.
pub number: Option<SectionNumber>, pub number: Option<SectionNumber>,
/// Any nested items this chapter may contain. /// Any nested items this chapter may contain.
@ -83,7 +83,7 @@ impl Link {
pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link { pub fn new<S: Into<String>, P: AsRef<Path>>(name: S, location: P) -> Link {
Link { Link {
name: name.into(), name: name.into(),
location: location.as_ref().to_path_buf(), location: Some(location.as_ref().to_path_buf()),
number: None, number: None,
nested_items: Vec::new(), nested_items: Vec::new(),
} }
@ -94,7 +94,7 @@ impl Default for Link {
fn default() -> Self { fn default() -> Self {
Link { Link {
name: String::new(), name: String::new(),
location: PathBuf::new(), location: Some(PathBuf::new()),
number: None, number: None,
nested_items: Vec::new(), nested_items: Vec::new(),
} }
@ -260,7 +260,7 @@ impl<'a> SummaryParser<'a> {
} }
} }
Some(Event::Start(Tag::Link(_type, href, _title))) => { Some(Event::Start(Tag::Link(_type, href, _title))) => {
let link = self.parse_link(href.to_string())?; let link = self.parse_link(href.to_string());
items.push(SummaryItem::Link(link)); items.push(SummaryItem::Link(link));
} }
Some(Event::Rule) => items.push(SummaryItem::Separator), Some(Event::Rule) => items.push(SummaryItem::Separator),
@ -272,19 +272,21 @@ impl<'a> SummaryParser<'a> {
Ok(items) Ok(items)
} }
fn parse_link(&mut self, href: String) -> Result<Link> { fn parse_link(&mut self, href: String) -> Link {
let link_content = collect_events!(self.stream, end Tag::Link(..)); let link_content = collect_events!(self.stream, end Tag::Link(..));
let name = stringify_events(link_content); let name = stringify_events(link_content);
if href.is_empty() { let path = if href.is_empty() {
Err(self.parse_error("You can't have an empty link.")) None
} else { } else {
Ok(Link { Some(PathBuf::from(href))
};
Link {
name, name,
location: PathBuf::from(href), location: path,
number: None, number: None,
nested_items: Vec::new(), nested_items: Vec::new(),
})
} }
} }
@ -410,7 +412,7 @@ impl<'a> SummaryParser<'a> {
match self.next_event() { match self.next_event() {
Some(Event::Start(Tag::Paragraph)) => continue, Some(Event::Start(Tag::Paragraph)) => continue,
Some(Event::Start(Tag::Link(_type, href, _title))) => { Some(Event::Start(Tag::Link(_type, href, _title))) => {
let mut link = self.parse_link(href.to_string())?; let mut link = self.parse_link(href.to_string());
let mut number = parent.clone(); let mut number = parent.clone();
number.0.push(num_existing_items as u32 + 1); number.0.push(num_existing_items as u32 + 1);
@ -418,7 +420,10 @@ impl<'a> SummaryParser<'a> {
"Found chapter: {} {} ({})", "Found chapter: {} {} ({})",
number, number,
link.name, link.name,
link.location.display() link.location
.as_ref()
.map(|p| p.to_str().unwrap_or(""))
.unwrap_or("[draft]")
); );
link.number = Some(number); link.number = Some(number);
@ -589,12 +594,12 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
..Default::default() ..Default::default()
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
..Default::default() ..Default::default()
}), }),
]; ];
@ -633,7 +638,7 @@ mod tests {
let src = "[First](./first.md)"; let src = "[First](./first.md)";
let should_be = Link { let should_be = Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
..Default::default() ..Default::default()
}; };
@ -645,7 +650,7 @@ mod tests {
other => panic!("Unreachable, {:?}", other), other => panic!("Unreachable, {:?}", other),
}; };
let got = parser.parse_link(href).unwrap(); let got = parser.parse_link(href);
assert_eq!(got, should_be); assert_eq!(got, should_be);
} }
@ -654,7 +659,7 @@ mod tests {
let src = "- [First](./first.md)\n"; let src = "- [First](./first.md)\n";
let link = Link { let link = Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
..Default::default() ..Default::default()
}; };
@ -675,18 +680,18 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: vec![SummaryItem::Link(Link { nested_items: vec![SummaryItem::Link(Link {
name: String::from("Nested"), name: String::from("Nested"),
location: PathBuf::from("./nested.md"), location: Some(PathBuf::from("./nested.md")),
number: Some(SectionNumber(vec![1, 1])), number: Some(SectionNumber(vec![1, 1])),
nested_items: Vec::new(), nested_items: Vec::new(),
})], })],
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
@ -707,13 +712,13 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
@ -737,13 +742,13 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
@ -758,13 +763,21 @@ mod tests {
} }
#[test] #[test]
fn an_empty_link_location_is_an_error() { fn an_empty_link_location_is_a_draft_chapter() {
let src = "- [Empty]()\n"; let src = "- [Empty]()\n";
let mut parser = SummaryParser::new(src); let mut parser = SummaryParser::new(src);
parser.stream.next(); parser.stream.next();
let got = parser.parse_numbered(); let got = parser.parse_numbered();
assert!(got.is_err()); let should_be = vec![SummaryItem::Link(Link {
name: String::from("Empty"),
location: None,
number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(),
})];
assert!(got.is_ok());
assert_eq!(got.unwrap(), should_be);
} }
/// Regression test for https://github.com/rust-lang/mdBook/issues/779 /// Regression test for https://github.com/rust-lang/mdBook/issues/779
@ -776,21 +789,21 @@ mod tests {
let should_be = vec![ let should_be = vec![
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("First"), name: String::from("First"),
location: PathBuf::from("./first.md"), location: Some(PathBuf::from("./first.md")),
number: Some(SectionNumber(vec![1])), number: Some(SectionNumber(vec![1])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
SummaryItem::Separator, SummaryItem::Separator,
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Second"), name: String::from("Second"),
location: PathBuf::from("./second.md"), location: Some(PathBuf::from("./second.md")),
number: Some(SectionNumber(vec![2])), number: Some(SectionNumber(vec![2])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),
SummaryItem::Separator, SummaryItem::Separator,
SummaryItem::Link(Link { SummaryItem::Link(Link {
name: String::from("Third"), name: String::from("Third"),
location: PathBuf::from("./third.md"), location: Some(PathBuf::from("./third.md")),
number: Some(SectionNumber(vec![3])), number: Some(SectionNumber(vec![3])),
nested_items: Vec::new(), nested_items: Vec::new(),
}), }),

View File

@ -29,13 +29,15 @@ impl Preprocessor for IndexPreprocessor {
let source_dir = ctx.root.join(&ctx.config.book.src); let source_dir = ctx.root.join(&ctx.config.book.src);
book.for_each_mut(|section: &mut BookItem| { book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section { if let BookItem::Chapter(ref mut ch) = *section {
if is_readme_file(&ch.path) { if let Some(ref mut path) = ch.path {
let index_md = source_dir.join(ch.path.with_file_name("index.md")); if is_readme_file(&path) {
let mut index_md = source_dir.join(path.with_file_name("index.md"));
if index_md.exists() { if index_md.exists() {
warn_readme_name_conflict(&ch.path, &index_md); warn_readme_name_conflict(&path, &&mut index_md);
} }
ch.path.set_file_name("index.md"); path.set_file_name("index.md");
}
} }
} }
}); });

View File

@ -45,15 +45,16 @@ impl Preprocessor for LinkPreprocessor {
book.for_each_mut(|section: &mut BookItem| { book.for_each_mut(|section: &mut BookItem| {
if let BookItem::Chapter(ref mut ch) = *section { if let BookItem::Chapter(ref mut ch) = *section {
let base = ch if let Some(ref chapter_path) = ch.path {
.path let base = chapter_path
.parent() .parent()
.map(|dir| src_dir.join(dir)) .map(|dir| src_dir.join(dir))
.expect("All book items have a parent"); .expect("All book items have a parent");
let content = replace_all(&ch.content, base, &ch.path, 0); let content = replace_all(&ch.content, base, chapter_path, 0);
ch.content = content; ch.content = content;
} }
}
}); });
Ok(book) Ok(book)

View File

@ -31,26 +31,26 @@ impl HtmlHandlebars {
) -> Result<()> { ) -> Result<()> {
// FIXME: This should be made DRY-er and rely less on mutable state // FIXME: This should be made DRY-er and rely less on mutable state
if let BookItem::Chapter(ref ch) = *item { if let BookItem::Chapter(ref ch) = *item {
if let Some(ref path) = ch.path {
let content = ch.content.clone(); let content = ch.content.clone();
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes); let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
let fixed_content = utils::render_markdown_with_path( let fixed_content = utils::render_markdown_with_path(
&ch.content, &ch.content,
ctx.html_config.curly_quotes, ctx.html_config.curly_quotes,
Some(&ch.path), Some(&path),
); );
print_content.push_str(&fixed_content); print_content.push_str(&fixed_content);
// Update the context with data for this file // Update the context with data for this file
let path = ch let ctx_path = path
.path
.to_str() .to_str()
.chain_err(|| "Could not convert path to str")?; .chain_err(|| "Could not convert path to str")?;
let filepath = Path::new(&ch.path).with_extension("html"); let filepath = Path::new(&ctx_path).with_extension("html");
// "print.html" is used for the print page. // "print.html" is used for the print page.
if ch.path == Path::new("print.md") { if path == Path::new("print.md") {
bail!(ErrorKind::ReservedFilenameError(ch.path.clone())); bail!(ErrorKind::ReservedFilenameError(path.clone()));
}; };
// Non-lexical lifetimes needed :'( // Non-lexical lifetimes needed :'(
@ -74,7 +74,7 @@ impl HtmlHandlebars {
ctx.data.insert("title".to_owned(), json!(title)); ctx.data.insert("title".to_owned(), json!(title));
ctx.data.insert( ctx.data.insert(
"path_to_root".to_owned(), "path_to_root".to_owned(),
json!(utils::fs::path_to_root(&ch.path)), json!(utils::fs::path_to_root(&path)),
); );
if let Some(ref section) = ch.number { if let Some(ref section) = ch.number {
ctx.data ctx.data
@ -98,8 +98,13 @@ impl HtmlHandlebars {
let rendered_index = ctx.handlebars.render("index", &ctx.data)?; let rendered_index = ctx.handlebars.render("index", &ctx.data)?;
let rendered_index = let rendered_index =
self.post_process(rendered_index, &ctx.html_config.playpen, ctx.edition); self.post_process(rendered_index, &ctx.html_config.playpen, ctx.edition);
debug!("Creating index.html from {}", path); debug!("Creating index.html from {}", ctx_path);
utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?; utils::fs::write_file(
&ctx.destination,
"index.html",
rendered_index.as_bytes(),
)?;
}
} }
} }
@ -526,11 +531,12 @@ fn make_data(
); );
chapter.insert("name".to_owned(), json!(ch.name)); chapter.insert("name".to_owned(), json!(ch.name));
let path = ch if let Some(ref path) = ch.path {
.path let p = path
.to_str() .to_str()
.chain_err(|| "Could not convert path to str")?; .chain_err(|| "Could not convert path to str")?;
chapter.insert("path".to_owned(), json!(path)); chapter.insert("path".to_owned(), json!(p));
}
} }
BookItem::Separator => { BookItem::Separator => {
chapter.insert("spacer".to_owned(), json!("_spacer_")); chapter.insert("spacer".to_owned(), json!("_spacer_"));

View File

@ -71,11 +71,21 @@ fn render_item(
item: &BookItem, item: &BookItem,
) -> Result<()> { ) -> Result<()> {
let chapter = match *item { let chapter = match *item {
BookItem::Chapter(ref ch) => ch, BookItem::Chapter(ref ch) => {
if let Some(_) = ch.path {
ch
} else {
return Ok(());
}
}
_ => return Ok(()), _ => return Ok(()),
}; };
let filepath = Path::new(&chapter.path).with_extension("html"); let chapter_path = chapter
.path
.as_ref()
.expect("Checked that path exists above");
let filepath = Path::new(&chapter_path).with_extension("html");
let filepath = filepath let filepath = filepath
.to_str() .to_str()
.chain_err(|| "Could not convert HTML path to str")?; .chain_err(|| "Could not convert HTML path to str")?;

View File

@ -34,7 +34,13 @@ impl Renderer for MarkdownRenderer {
trace!("markdown render"); trace!("markdown render");
for item in book.iter() { for item in book.iter() {
if let BookItem::Chapter(ref ch) = *item { if let BookItem::Chapter(ref ch) = *item {
utils::fs::write_file(&ctx.destination, &ch.path, ch.content.as_bytes())?; if !ch.is_draft_chapter() {
utils::fs::write_file(
&ctx.destination,
&ch.path.as_ref().expect("Checked path exists before"),
ch.content.as_bytes(),
)?;
}
} }
} }