|
1 | 1 | use std::borrow::Cow; |
2 | 2 | use std::collections::{HashMap, HashSet}; |
3 | | -use std::path::Path; |
| 3 | +use std::path::{Path, PathBuf}; |
4 | 4 |
|
5 | 5 | use elasticlunr::{Index, IndexBuilder}; |
6 | 6 | use once_cell::sync::Lazy; |
7 | 7 | use pulldown_cmark::*; |
8 | 8 |
|
9 | | -use crate::book::{Book, BookItem}; |
10 | | -use crate::config::Search; |
| 9 | +use crate::book::{Book, BookItem, Chapter}; |
| 10 | +use crate::config::{Search, SearchChapterSettings}; |
11 | 11 | use crate::errors::*; |
12 | 12 | use crate::theme::searcher; |
13 | 13 | use crate::utils; |
@@ -35,8 +35,20 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> |
35 | 35 |
|
36 | 36 | let mut doc_urls = Vec::with_capacity(book.sections.len()); |
37 | 37 |
|
| 38 | + let chapter_configs = sort_search_config(&search_config.chapter); |
| 39 | + validate_chapter_config(&chapter_configs, book)?; |
| 40 | + |
38 | 41 | for item in book.iter() { |
39 | | - render_item(&mut index, search_config, &mut doc_urls, item)?; |
| 42 | + let chapter = match item { |
| 43 | + BookItem::Chapter(ch) if !ch.is_draft_chapter() => ch, |
| 44 | + _ => continue, |
| 45 | + }; |
| 46 | + let chapter_settings = |
| 47 | + get_chapter_settings(&chapter_configs, chapter.source_path.as_ref().unwrap()); |
| 48 | + if !chapter_settings.enable.unwrap_or(true) { |
| 49 | + continue; |
| 50 | + } |
| 51 | + render_item(&mut index, search_config, &mut doc_urls, chapter)?; |
40 | 52 | } |
41 | 53 |
|
42 | 54 | let index = write_to_json(index, search_config, doc_urls)?; |
@@ -100,13 +112,8 @@ fn render_item( |
100 | 112 | index: &mut Index, |
101 | 113 | search_config: &Search, |
102 | 114 | doc_urls: &mut Vec<String>, |
103 | | - item: &BookItem, |
| 115 | + chapter: &Chapter, |
104 | 116 | ) -> Result<()> { |
105 | | - let chapter = match *item { |
106 | | - BookItem::Chapter(ref ch) if !ch.is_draft_chapter() => ch, |
107 | | - _ => return Ok(()), |
108 | | - }; |
109 | | - |
110 | 117 | let chapter_path = chapter |
111 | 118 | .path |
112 | 119 | .as_ref() |
@@ -313,3 +320,81 @@ fn clean_html(html: &str) -> String { |
313 | 320 | }); |
314 | 321 | AMMONIA.clean(html).to_string() |
315 | 322 | } |
| 323 | + |
| 324 | +fn validate_chapter_config( |
| 325 | + chapter_configs: &[(PathBuf, SearchChapterSettings)], |
| 326 | + book: &Book, |
| 327 | +) -> Result<()> { |
| 328 | + for (path, _) in chapter_configs { |
| 329 | + let found = book |
| 330 | + .iter() |
| 331 | + .filter_map(|item| match item { |
| 332 | + BookItem::Chapter(ch) if !ch.is_draft_chapter() => Some(ch), |
| 333 | + _ => None, |
| 334 | + }) |
| 335 | + .any(|chapter| { |
| 336 | + let ch_path = chapter.source_path.as_ref().unwrap(); |
| 337 | + ch_path.starts_with(path) |
| 338 | + }); |
| 339 | + if !found { |
| 340 | + bail!( |
| 341 | + "[output.html.search.chapter] key `{}` does not match any chapter paths", |
| 342 | + path.display() |
| 343 | + ); |
| 344 | + } |
| 345 | + } |
| 346 | + Ok(()) |
| 347 | +} |
| 348 | + |
| 349 | +fn sort_search_config( |
| 350 | + map: &HashMap<String, SearchChapterSettings>, |
| 351 | +) -> Vec<(PathBuf, SearchChapterSettings)> { |
| 352 | + let mut settings: Vec<_> = map |
| 353 | + .iter() |
| 354 | + .map(|(key, value)| (PathBuf::from(key), value.clone())) |
| 355 | + .collect(); |
| 356 | + // Note: This is case-sensitive, and assumes the author uses the same case |
| 357 | + // as the actual filename. |
| 358 | + settings.sort_by(|a, b| a.0.cmp(&b.0)); |
| 359 | + settings |
| 360 | +} |
| 361 | + |
| 362 | +fn get_chapter_settings( |
| 363 | + chapter_configs: &[(PathBuf, SearchChapterSettings)], |
| 364 | + source_path: &Path, |
| 365 | +) -> SearchChapterSettings { |
| 366 | + let mut result = SearchChapterSettings::default(); |
| 367 | + for (path, config) in chapter_configs { |
| 368 | + if source_path.starts_with(path) { |
| 369 | + result.enable = config.enable.or(result.enable); |
| 370 | + } |
| 371 | + } |
| 372 | + result |
| 373 | +} |
| 374 | + |
| 375 | +#[test] |
| 376 | +fn chapter_settings_priority() { |
| 377 | + let cfg = r#" |
| 378 | + [output.html.search.chapter] |
| 379 | + "cli/watch.md" = { enable = true } |
| 380 | + "cli" = { enable = false } |
| 381 | + "cli/inner/foo.md" = { enable = false } |
| 382 | + "cli/inner" = { enable = true } |
| 383 | + "foo" = {} # Just to make sure empty table is allowed. |
| 384 | + "#; |
| 385 | + let cfg: crate::Config = toml::from_str(cfg).unwrap(); |
| 386 | + let html = cfg.html_config().unwrap(); |
| 387 | + let chapter_configs = sort_search_config(&html.search.unwrap().chapter); |
| 388 | + for (path, enable) in [ |
| 389 | + ("foo.md", None), |
| 390 | + ("cli/watch.md", Some(true)), |
| 391 | + ("cli/index.md", Some(false)), |
| 392 | + ("cli/inner/index.md", Some(true)), |
| 393 | + ("cli/inner/foo.md", Some(false)), |
| 394 | + ] { |
| 395 | + assert_eq!( |
| 396 | + get_chapter_settings(&chapter_configs, Path::new(path)), |
| 397 | + SearchChapterSettings { enable } |
| 398 | + ); |
| 399 | + } |
| 400 | +} |
0 commit comments