Bring back draft chapters
This commit is contained in:
parent
7e11d37e49
commit
d605938886
|
@ -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)
|
||||||
|
|
|
@ -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]()
|
||||||
|
```
|
|
@ -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()
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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_"));
|
||||||
|
|
|
@ -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")?;
|
||||||
|
|
|
@ -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(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue