Skip to content

Commit d605938

Browse files
Mathieu Davidehuss
Mathieu David
authored andcommitted
Bring back draft chapters
1 parent 7e11d37 commit d605938

File tree

10 files changed

+267
-182
lines changed

10 files changed

+267
-182
lines changed

book-example/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [clean](cli/clean.md)
1111
- [Format](format/README.md)
1212
- [SUMMARY.md](format/summary.md)
13+
- [Draft chapter]()
1314
- [Configuration](format/config.md)
1415
- [Theme](format/theme/README.md)
1516
- [index.hbs](format/theme/index-hbs.md)

book-example/src/format/summary.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ are. Without this file, there is no book.
77
Even though `SUMMARY.md` is a markdown file, the formatting is very strict to
88
allow for easy parsing. Let's see how you should format your `SUMMARY.md` file.
99

10-
#### Allowed elements
10+
#### Structure
1111

1212
1. ***Title*** It's common practice to begin with a title, generally <code
1313
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.
3636

3737
All other elements are unsupported and will be ignored at best or result in an
3838
error.
39+
40+
#### Other elements
41+
42+
- ***Separators*** In between chapters you can add a separator. In the HTML renderer
43+
this will result in a line being rendered in the table of contents. A separator is
44+
a line containing exclusively dashes and at least three of them: `---`.
45+
- ***Draft chapters*** Draft chapters are chapters without a file and thus content.
46+
The purpose of a draft chapter is to signal future chapters still to be written.
47+
Or when still laying out the structure of the book to avoid creating the files
48+
while you are still changing the structure of the book a lot.
49+
Draft chapters will be rendered in the HTML renderer as disabled links in the table
50+
of contents, as you can see for the next chapter in the table of contents on the left.
51+
Draft chapters are written like normal chapters but without writing the path to the file
52+
```markdown
53+
- [Draft chapter]()
54+
```

src/book/book.rs

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,19 @@ fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> {
3939
let next = items.pop().expect("already checked");
4040

4141
if let SummaryItem::Link(ref link) = *next {
42-
let filename = src_dir.join(&link.location);
43-
if !filename.exists() {
44-
if let Some(parent) = filename.parent() {
45-
if !parent.exists() {
46-
fs::create_dir_all(parent)?;
42+
if let Some(ref location) = link.location {
43+
let filename = src_dir.join(location);
44+
if !filename.exists() {
45+
if let Some(parent) = filename.parent() {
46+
if !parent.exists() {
47+
fs::create_dir_all(parent)?;
48+
}
4749
}
48-
}
49-
debug!("Creating missing file {}", filename.display());
50+
debug!("Creating missing file {}", filename.display());
5051

51-
let mut f = File::create(&filename)?;
52-
writeln!(f, "# {}", link.name)?;
52+
let mut f = File::create(&filename)?;
53+
writeln!(f, "# {}", link.name)?;
54+
}
5355
}
5456

5557
items.extend(&link.nested_items);
@@ -152,7 +154,7 @@ pub struct Chapter {
152154
/// Nested items.
153155
pub sub_items: Vec<BookItem>,
154156
/// The chapter's location, relative to the `SUMMARY.md` file.
155-
pub path: PathBuf,
157+
pub path: Option<PathBuf>,
156158
/// An ordered list of the names of each chapter above this one, in the hierarchy.
157159
pub parent_names: Vec<String>,
158160
}
@@ -168,11 +170,31 @@ impl Chapter {
168170
Chapter {
169171
name: name.to_string(),
170172
content,
171-
path: path.into(),
173+
path: Some(path.into()),
174+
parent_names,
175+
..Default::default()
176+
}
177+
}
178+
179+
/// Create a new draft chapter that is not attached to a source markdown file and has
180+
/// thus no content.
181+
pub fn new_draft(name: &str, parent_names: Vec<String>) -> Self {
182+
Chapter {
183+
name: name.to_string(),
184+
content: String::new(),
185+
path: None,
172186
parent_names,
173187
..Default::default()
174188
}
175189
}
190+
191+
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file
192+
pub fn is_draft_chapter(&self) -> bool {
193+
match self.path {
194+
Some(_) => false,
195+
None => true,
196+
}
197+
}
176198
}
177199

178200
/// 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)
202224
})
203225
}
204226

205-
fn load_summary_item<P: AsRef<Path>>(
227+
fn load_summary_item<P: AsRef<Path> + Clone>(
206228
item: &SummaryItem,
207229
src_dir: P,
208230
parent_names: Vec<String>,
@@ -215,40 +237,46 @@ fn load_summary_item<P: AsRef<Path>>(
215237
}
216238
}
217239

218-
fn load_chapter<P: AsRef<Path>>(
240+
fn load_chapter<P: AsRef<Path> + Clone>(
219241
link: &Link,
220242
src_dir: P,
221243
parent_names: Vec<String>,
222244
) -> Result<Chapter> {
223-
debug!("Loading {} ({})", link.name, link.location.display());
224-
let src_dir = src_dir.as_ref();
245+
let mut ch = if let Some(ref link_location) = link.location {
246+
debug!("Loading {} ({})", link.name, link_location.display());
247+
let src_dir = src_dir.as_ref();
248+
249+
let location = if link_location.is_absolute() {
250+
link_location.clone()
251+
} else {
252+
src_dir.join(link_location)
253+
};
225254

226-
let location = if link.location.is_absolute() {
227-
link.location.clone()
228-
} else {
229-
src_dir.join(&link.location)
230-
};
255+
let mut f = File::open(&location)
256+
.chain_err(|| format!("Chapter file not found, {}", link_location.display()))?;
231257

232-
let mut f = File::open(&location)
233-
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
258+
let mut content = String::new();
259+
f.read_to_string(&mut content)
260+
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
234261

235-
let mut content = String::new();
236-
f.read_to_string(&mut content)
237-
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
262+
let stripped = location
263+
.strip_prefix(&src_dir)
264+
.expect("Chapters are always inside a book");
238265

239-
let stripped = location
240-
.strip_prefix(&src_dir)
241-
.expect("Chapters are always inside a book");
266+
Chapter::new(&link.name, content, stripped, parent_names.clone())
267+
} else {
268+
Chapter::new_draft(&link.name, parent_names.clone())
269+
};
242270

243271
let mut sub_item_parents = parent_names.clone();
244-
let mut ch = Chapter::new(&link.name, content, stripped, parent_names);
272+
245273
ch.number = link.number.clone();
246274

247275
sub_item_parents.push(link.name.clone());
248276
let sub_items = link
249277
.nested_items
250278
.iter()
251-
.map(|i| load_summary_item(i, src_dir, sub_item_parents.clone()))
279+
.map(|i| load_summary_item(i, src_dir.clone(), sub_item_parents.clone()))
252280
.collect::<Result<Vec<_>>>()?;
253281

254282
ch.sub_items = sub_items;
@@ -376,15 +404,15 @@ And here is some \
376404
name: String::from("Nested Chapter 1"),
377405
content: String::from("Hello World!"),
378406
number: Some(SectionNumber(vec![1, 2])),
379-
path: PathBuf::from("second.md"),
407+
path: Some(PathBuf::from("second.md")),
380408
parent_names: vec![String::from("Chapter 1")],
381409
sub_items: Vec::new(),
382410
};
383411
let should_be = BookItem::Chapter(Chapter {
384412
name: String::from("Chapter 1"),
385413
content: String::from(DUMMY_SRC),
386414
number: None,
387-
path: PathBuf::from("chapter_1.md"),
415+
path: Some(PathBuf::from("chapter_1.md")),
388416
parent_names: Vec::new(),
389417
sub_items: vec![
390418
BookItem::Chapter(nested.clone()),
@@ -408,7 +436,7 @@ And here is some \
408436
sections: vec![BookItem::Chapter(Chapter {
409437
name: String::from("Chapter 1"),
410438
content: String::from(DUMMY_SRC),
411-
path: PathBuf::from("chapter_1.md"),
439+
path: Some(PathBuf::from("chapter_1.md")),
412440
..Default::default()
413441
})],
414442
..Default::default()
@@ -448,7 +476,7 @@ And here is some \
448476
name: String::from("Chapter 1"),
449477
content: String::from(DUMMY_SRC),
450478
number: None,
451-
path: PathBuf::from("Chapter_1/index.md"),
479+
path: Some(PathBuf::from("Chapter_1/index.md")),
452480
parent_names: Vec::new(),
453481
sub_items: vec![
454482
BookItem::Chapter(Chapter::new(
@@ -500,7 +528,7 @@ And here is some \
500528
name: String::from("Chapter 1"),
501529
content: String::from(DUMMY_SRC),
502530
number: None,
503-
path: PathBuf::from("Chapter_1/index.md"),
531+
path: Some(PathBuf::from("Chapter_1/index.md")),
504532
parent_names: Vec::new(),
505533
sub_items: vec![
506534
BookItem::Chapter(Chapter::new(
@@ -537,7 +565,7 @@ And here is some \
537565
let summary = Summary {
538566
numbered_chapters: vec![SummaryItem::Link(Link {
539567
name: String::from("Empty"),
540-
location: PathBuf::from(""),
568+
location: Some(PathBuf::from("")),
541569
..Default::default()
542570
})],
543571
..Default::default()
@@ -556,7 +584,7 @@ And here is some \
556584
let summary = Summary {
557585
numbered_chapters: vec![SummaryItem::Link(Link {
558586
name: String::from("nested"),
559-
location: dir,
587+
location: Some(dir),
560588
..Default::default()
561589
})],
562590
..Default::default()

src/book/mod.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -251,36 +251,38 @@ impl MDBook {
251251

252252
for item in book.iter() {
253253
if let BookItem::Chapter(ref ch) = *item {
254-
if !ch.path.as_os_str().is_empty() {
255-
let path = self.source_dir().join(&ch.path);
256-
info!("Testing file: {:?}", path);
257-
258-
// write preprocessed file to tempdir
259-
let path = temp_dir.path().join(&ch.path);
260-
let mut tmpf = utils::fs::create_file(&path)?;
261-
tmpf.write_all(ch.content.as_bytes())?;
262-
263-
let mut cmd = Command::new("rustdoc");
264-
cmd.arg(&path).arg("--test").args(&library_args);
265-
266-
if let Some(edition) = self.config.rust.edition {
267-
match edition {
268-
RustEdition::E2015 => {
269-
cmd.args(&["--edition", "2015"]);
270-
}
271-
RustEdition::E2018 => {
272-
cmd.args(&["--edition", "2018"]);
254+
if let Some(ref chapter_path) = ch.path {
255+
if !chapter_path.as_os_str().is_empty() {
256+
let path = self.source_dir().join(&chapter_path);
257+
info!("Testing file: {:?}", path);
258+
259+
// write preprocessed file to tempdir
260+
let path = temp_dir.path().join(&chapter_path);
261+
let mut tmpf = utils::fs::create_file(&path)?;
262+
tmpf.write_all(ch.content.as_bytes())?;
263+
264+
let mut cmd = Command::new("rustdoc");
265+
cmd.arg(&path).arg("--test").args(&library_args);
266+
267+
if let Some(edition) = self.config.rust.edition {
268+
match edition {
269+
RustEdition::E2015 => {
270+
cmd.args(&["--edition", "2015"]);
271+
}
272+
RustEdition::E2018 => {
273+
cmd.args(&["--edition", "2018"]);
274+
}
273275
}
274276
}
275-
}
276277

277-
let output = cmd.output()?;
278+
let output = cmd.output()?;
278279

279-
if !output.status.success() {
280-
bail!(ErrorKind::Subprocess(
281-
"Rustdoc returned an error".to_string(),
282-
output
283-
));
280+
if !output.status.success() {
281+
bail!(ErrorKind::Subprocess(
282+
"Rustdoc returned an error".to_string(),
283+
output
284+
));
285+
}
284286
}
285287
}
286288
}

0 commit comments

Comments
 (0)