parent
43281c85c5
commit
67b4260021
1 changed files with 108 additions and 0 deletions
|
|
@ -3,6 +3,7 @@ use log::{debug, trace, warn};
|
||||||
use memchr::Memchr;
|
use memchr::Memchr;
|
||||||
use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd};
|
use pulldown_cmark::{DefaultBrokenLinkCallback, Event, HeadingLevel, Tag, TagEnd};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
@ -245,6 +246,11 @@ impl<'a> SummaryParser<'a> {
|
||||||
.parse_affix(false)
|
.parse_affix(false)
|
||||||
.with_context(|| "There was an error parsing the suffix chapters")?;
|
.with_context(|| "There was an error parsing the suffix chapters")?;
|
||||||
|
|
||||||
|
let mut files = HashSet::new();
|
||||||
|
for part in [&prefix_chapters, &numbered_chapters, &suffix_chapters] {
|
||||||
|
self.check_for_duplicates(&part, &mut files)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Summary {
|
Ok(Summary {
|
||||||
title,
|
title,
|
||||||
prefix_chapters,
|
prefix_chapters,
|
||||||
|
|
@ -253,6 +259,29 @@ impl<'a> SummaryParser<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively check for duplicate files in the summary items.
|
||||||
|
fn check_for_duplicates<'b>(
|
||||||
|
&self,
|
||||||
|
items: &'b [SummaryItem],
|
||||||
|
files: &mut HashSet<&'b PathBuf>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for item in items {
|
||||||
|
if let SummaryItem::Link(link) = item {
|
||||||
|
if let Some(location) = &link.location {
|
||||||
|
if !files.insert(location) {
|
||||||
|
bail!(anyhow::anyhow!(
|
||||||
|
"Duplicate file in SUMMARY.md: {:?}",
|
||||||
|
location
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Recursively check nested items
|
||||||
|
self.check_for_duplicates(&link.nested_items, files)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the affix chapters.
|
/// Parse the affix chapters.
|
||||||
fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {
|
fn parse_affix(&mut self, is_prefix: bool) -> Result<Vec<SummaryItem>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|
@ -1127,4 +1156,83 @@ mod tests {
|
||||||
let got = parser.parse_affix(false).unwrap();
|
let got = parser.parse_affix(false).unwrap();
|
||||||
assert_eq!(got, should_be);
|
assert_eq!(got, should_be);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicate_entries_1() {
|
||||||
|
let src = r#"
|
||||||
|
# Summary
|
||||||
|
- [A](./a.md)
|
||||||
|
- [A](./a.md)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let res = parse_summary(src);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let error_message = res.err().unwrap().to_string();
|
||||||
|
assert_eq!(error_message, r#"Duplicate file in SUMMARY.md: "./a.md""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicate_entries_2() {
|
||||||
|
let src = r#"
|
||||||
|
# Summary
|
||||||
|
- [A](./a.md)
|
||||||
|
- [A](./a.md)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let res = parse_summary(src);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let error_message = res.err().unwrap().to_string();
|
||||||
|
assert_eq!(error_message, r#"Duplicate file in SUMMARY.md: "./a.md""#);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn duplicate_entries_3() {
|
||||||
|
let src = r#"
|
||||||
|
# Summary
|
||||||
|
- [A](./a.md)
|
||||||
|
- [B](./b.md)
|
||||||
|
- [A](./a.md)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let res = parse_summary(src);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let error_message = res.err().unwrap().to_string();
|
||||||
|
assert_eq!(error_message, r#"Duplicate file in SUMMARY.md: "./a.md""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicate_entries_4() {
|
||||||
|
let src = r#"
|
||||||
|
# Summary
|
||||||
|
[A](./a.md)
|
||||||
|
- [B](./b.md)
|
||||||
|
- [A](./a.md)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let res = parse_summary(src);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let error_message = res.err().unwrap().to_string();
|
||||||
|
assert_eq!(error_message, r#"Duplicate file in SUMMARY.md: "./a.md""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicate_entries_5() {
|
||||||
|
let src = r#"
|
||||||
|
# Summary
|
||||||
|
[A](./a.md)
|
||||||
|
|
||||||
|
# hi
|
||||||
|
- [B](./b.md)
|
||||||
|
|
||||||
|
# bye
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[A](./a.md)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let res = parse_summary(src);
|
||||||
|
assert!(res.is_err());
|
||||||
|
let error_message = res.err().unwrap().to_string();
|
||||||
|
assert_eq!(error_message, r#"Duplicate file in SUMMARY.md: "./a.md""#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue