Skip to content

Commit 578e4da

Browse files
authored
Merge pull request #1153 from azerupi/draft-chapters
Bring back draft chapters
2 parents 7e11d37 + 43008ef commit 578e4da

File tree

10 files changed

+262
-183
lines changed

10 files changed

+262
-183
lines changed

book-example/src/SUMMARY.md

+1
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

+17-1
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

+63-34
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>,
@@ -220,28 +242,35 @@ fn load_chapter<P: AsRef<Path>>(
220242
src_dir: P,
221243
parent_names: Vec<String>,
222244
) -> Result<Chapter> {
223-
debug!("Loading {} ({})", link.name, link.location.display());
224245
let src_dir = src_dir.as_ref();
225246

226-
let location = if link.location.is_absolute() {
227-
link.location.clone()
228-
} else {
229-
src_dir.join(&link.location)
230-
};
247+
let mut ch = if let Some(ref link_location) = link.location {
248+
debug!("Loading {} ({})", link.name, link_location.display());
249+
250+
let location = if link_location.is_absolute() {
251+
link_location.clone()
252+
} else {
253+
src_dir.join(link_location)
254+
};
231255

232-
let mut f = File::open(&location)
233-
.chain_err(|| format!("Chapter file not found, {}", link.location.display()))?;
256+
let mut f = File::open(&location)
257+
.chain_err(|| format!("Chapter file not found, {}", link_location.display()))?;
234258

235-
let mut content = String::new();
236-
f.read_to_string(&mut content)
237-
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
259+
let mut content = String::new();
260+
f.read_to_string(&mut content)
261+
.chain_err(|| format!("Unable to read \"{}\" ({})", link.name, location.display()))?;
238262

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

243272
let mut sub_item_parents = parent_names.clone();
244-
let mut ch = Chapter::new(&link.name, content, stripped, parent_names);
273+
245274
ch.number = link.number.clone();
246275

247276
sub_item_parents.push(link.name.clone());
@@ -376,15 +405,15 @@ And here is some \
376405
name: String::from("Nested Chapter 1"),
377406
content: String::from("Hello World!"),
378407
number: Some(SectionNumber(vec![1, 2])),
379-
path: PathBuf::from("second.md"),
408+
path: Some(PathBuf::from("second.md")),
380409
parent_names: vec![String::from("Chapter 1")],
381410
sub_items: Vec::new(),
382411
};
383412
let should_be = BookItem::Chapter(Chapter {
384413
name: String::from("Chapter 1"),
385414
content: String::from(DUMMY_SRC),
386415
number: None,
387-
path: PathBuf::from("chapter_1.md"),
416+
path: Some(PathBuf::from("chapter_1.md")),
388417
parent_names: Vec::new(),
389418
sub_items: vec![
390419
BookItem::Chapter(nested.clone()),
@@ -408,7 +437,7 @@ And here is some \
408437
sections: vec![BookItem::Chapter(Chapter {
409438
name: String::from("Chapter 1"),
410439
content: String::from(DUMMY_SRC),
411-
path: PathBuf::from("chapter_1.md"),
440+
path: Some(PathBuf::from("chapter_1.md")),
412441
..Default::default()
413442
})],
414443
..Default::default()
@@ -448,7 +477,7 @@ And here is some \
448477
name: String::from("Chapter 1"),
449478
content: String::from(DUMMY_SRC),
450479
number: None,
451-
path: PathBuf::from("Chapter_1/index.md"),
480+
path: Some(PathBuf::from("Chapter_1/index.md")),
452481
parent_names: Vec::new(),
453482
sub_items: vec![
454483
BookItem::Chapter(Chapter::new(
@@ -500,7 +529,7 @@ And here is some \
500529
name: String::from("Chapter 1"),
501530
content: String::from(DUMMY_SRC),
502531
number: None,
503-
path: PathBuf::from("Chapter_1/index.md"),
532+
path: Some(PathBuf::from("Chapter_1/index.md")),
504533
parent_names: Vec::new(),
505534
sub_items: vec![
506535
BookItem::Chapter(Chapter::new(
@@ -537,7 +566,7 @@ And here is some \
537566
let summary = Summary {
538567
numbered_chapters: vec![SummaryItem::Link(Link {
539568
name: String::from("Empty"),
540-
location: PathBuf::from(""),
569+
location: Some(PathBuf::from("")),
541570
..Default::default()
542571
})],
543572
..Default::default()
@@ -556,7 +585,7 @@ And here is some \
556585
let summary = Summary {
557586
numbered_chapters: vec![SummaryItem::Link(Link {
558587
name: String::from("nested"),
559-
location: dir,
588+
location: Some(dir),
560589
..Default::default()
561590
})],
562591
..Default::default()

src/book/mod.rs

+30-27
Original file line numberDiff line numberDiff line change
@@ -251,37 +251,40 @@ 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"]);
273-
}
254+
let chapter_path = match ch.path {
255+
Some(ref path) if !path.as_os_str().is_empty() => path,
256+
_ => continue,
257+
};
258+
259+
let path = self.source_dir().join(&chapter_path);
260+
info!("Testing file: {:?}", path);
261+
262+
// write preprocessed file to tempdir
263+
let path = temp_dir.path().join(&chapter_path);
264+
let mut tmpf = utils::fs::create_file(&path)?;
265+
tmpf.write_all(ch.content.as_bytes())?;
266+
267+
let mut cmd = Command::new("rustdoc");
268+
cmd.arg(&path).arg("--test").args(&library_args);
269+
270+
if let Some(edition) = self.config.rust.edition {
271+
match edition {
272+
RustEdition::E2015 => {
273+
cmd.args(&["--edition", "2015"]);
274+
}
275+
RustEdition::E2018 => {
276+
cmd.args(&["--edition", "2018"]);
274277
}
275278
}
279+
}
276280

277-
let output = cmd.output()?;
281+
let output = cmd.output()?;
278282

279-
if !output.status.success() {
280-
bail!(ErrorKind::Subprocess(
281-
"Rustdoc returned an error".to_string(),
282-
output
283-
));
284-
}
283+
if !output.status.success() {
284+
bail!(ErrorKind::Subprocess(
285+
"Rustdoc returned an error".to_string(),
286+
output
287+
));
285288
}
286289
}
287290
}

0 commit comments

Comments
 (0)