Skip to content

Commit bd8e01e

Browse files
committed
feat(data_structure): ChapterMutThin for accessing in parallel
1 parent 8412c5a commit bd8e01e

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

crates/mdbook-core/src/book.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ impl Book {
8989
);
9090
}
9191

92+
/// Recursively collect all chapters in the book as mutable thin references,
93+
/// allowing you to mutate them in parallel.
94+
pub fn chapters_mut_thin(&mut self) -> Vec<ChapterMutThin<'_>> {
95+
let mut chapters_thin = Vec::new();
96+
chapters_mut_thin(&mut self.items, &mut chapters_thin);
97+
chapters_thin
98+
}
99+
92100
/// Append a `BookItem` to the `Book`.
93101
pub fn push_item<I: Into<BookItem>>(&mut self, item: I) -> &mut Self {
94102
self.items.push(item.into());
@@ -110,6 +118,32 @@ where
110118
}
111119
}
112120

121+
/// Collect all chapters in the book.
122+
pub fn chapters_mut_thin<'a>(items: &'a mut [BookItem], accumulator: &mut Vec<ChapterMutThin<'a>>) {
123+
items.iter_mut().for_each(move |item| {
124+
if let BookItem::Chapter(Chapter {
125+
name,
126+
content,
127+
number,
128+
sub_items,
129+
path,
130+
source_path,
131+
parent_names,
132+
}) = item
133+
{
134+
accumulator.push(ChapterMutThin {
135+
name,
136+
content,
137+
number,
138+
path,
139+
source_path,
140+
parent_names,
141+
});
142+
chapters_mut_thin(sub_items, accumulator);
143+
}
144+
})
145+
}
146+
113147
/// Enum representing any type of item which can be added to a book.
114148
#[allow(
115149
clippy::exhaustive_enums,
@@ -206,6 +240,30 @@ impl Chapter {
206240
}
207241
}
208242

243+
/// A thin mutable reference to a chapter for parallel book patching.
244+
#[non_exhaustive]
245+
pub struct ChapterMutThin<'a> {
246+
/// The chapter's name.
247+
pub name: &'a mut String,
248+
/// The chapter's contents.
249+
pub content: &'a mut String,
250+
/// The chapter's section number, if it has one.
251+
pub number: &'a mut Option<SectionNumber>,
252+
/// The chapter's location, relative to the `SUMMARY.md` file.
253+
pub path: &'a mut Option<PathBuf>,
254+
/// The chapter's source file, relative to the `SUMMARY.md` file.
255+
pub source_path: &'a mut Option<PathBuf>,
256+
/// An ordered list of the names of each chapter above this one in the hierarchy.
257+
pub parent_names: &'a mut Vec<String>,
258+
}
259+
260+
impl ChapterMutThin<'_> {
261+
/// Check if the chapter is a draft chapter, meaning it has no path to a source markdown file.
262+
pub fn is_draft_chapter(&self) -> bool {
263+
self.path.is_none()
264+
}
265+
}
266+
209267
impl Display for Chapter {
210268
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211269
if let Some(ref section_number) = self.number {

crates/mdbook-core/src/book/tests.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,56 @@ fn iterate_over_nested_book_items() {
121121

122122
assert_eq!(chapter_names, should_be);
123123
}
124+
125+
#[test]
126+
fn chapters_mut_thin_visits_nested_chapters() {
127+
let items = vec![BookItem::Chapter(Chapter {
128+
name: String::from("Chapter 1"),
129+
content: String::from("# Chapter 1"),
130+
number: None,
131+
path: Some(PathBuf::from("Chapter_1/index.md")),
132+
source_path: Some(PathBuf::from("Chapter_1/index.md")),
133+
parent_names: Vec::new(),
134+
sub_items: vec![
135+
BookItem::Chapter(Chapter::new(
136+
"Hello World",
137+
String::from("hello"),
138+
"Chapter_1/hello.md",
139+
Vec::new(),
140+
)),
141+
BookItem::Chapter(Chapter::new(
142+
"Goodbye World",
143+
String::from("goodbye"),
144+
"Chapter_1/goodbye.md",
145+
Vec::new(),
146+
)),
147+
],
148+
})];
149+
let mut book = Book::new_with_items(items);
150+
151+
let mut chapters = book.chapters_mut_thin();
152+
assert_eq!(chapters.len(), 3);
153+
154+
chapters[0].name.push_str(" updated");
155+
chapters[1].content.push_str(" world");
156+
157+
drop(chapters);
158+
159+
let chapter_names: Vec<_> = book
160+
.iter()
161+
.filter_map(|item| match item {
162+
BookItem::Chapter(chapter) => Some(chapter.name.clone()),
163+
_ => None,
164+
})
165+
.collect();
166+
assert_eq!(chapter_names[0], "Chapter 1 updated");
167+
168+
let chapter_contents: Vec<_> = book
169+
.iter()
170+
.filter_map(|item| match item {
171+
BookItem::Chapter(chapter) => Some(chapter.content.clone()),
172+
_ => None,
173+
})
174+
.collect();
175+
assert_eq!(chapter_contents[1], "hello world");
176+
}

0 commit comments

Comments
 (0)