diff options
| author | Lars Wrenger <lars@wrenger.net> | 2023-03-31 18:41:17 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-31 18:41:17 +0200 |
| commit | 631ba40e57b4f121fe9335f334a76dd9c81de088 (patch) | |
| tree | 91b53660a23290a67b547f7623e21dcb0e426c11 /library | |
| parent | 4161bad54f690e77f53ca1f0f4426161e60fa4ce (diff) | |
Support for multiple bib files in a bibliography (#296)
Diffstat (limited to 'library')
| -rw-r--r-- | library/src/meta/bibliography.rs | 91 |
1 files changed, 71 insertions, 20 deletions
diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs index e25877b1..808d8026 100644 --- a/library/src/meta/bibliography.rs +++ b/library/src/meta/bibliography.rs @@ -48,13 +48,17 @@ pub struct BibliographyElem { /// Path to a Hayagriva `.yml` or BibLaTeX `.bib` file. #[required] #[parse( - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to bibliography file")?; - let path: EcoString = vm.locate(&path).at(span)?.to_string_lossy().into(); - let _ = load(vm.world(), &path).at(span)?; - path + let Spanned { v: mut paths, span } = + args.expect::<Spanned<BibPaths>>("path to bibliography file")?; + for path in &mut paths.0 { + // resolve paths + *path = vm.locate(&path).at(span)?.to_string_lossy().into(); + } + // check that parsing works + let _ = load(vm.world(), &paths).at(span)?; + paths )] - pub path: EcoString, + pub path: BibPaths, /// The title of the bibliography. /// @@ -70,6 +74,22 @@ pub struct BibliographyElem { pub style: BibliographyStyle, } + +/// A list of bib file paths. +#[derive(Debug, Default, Clone, Hash)] +pub struct BibPaths(Vec<EcoString>); + +cast_from_value! { + BibPaths, + v: EcoString => Self(vec![v]), + v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?), +} + +cast_to_value! { + v: BibPaths => v.0.into() +} + + impl BibliographyElem { /// Find the document's bibliography. pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> { @@ -534,22 +554,53 @@ fn create( /// Load bibliography entries from a path. #[comemo::memoize] -fn load(world: Tracked<dyn World>, path: &str) -> StrResult<EcoVec<hayagriva::Entry>> { - let path = Path::new(path); - let buffer = world.file(path)?; - let src = std::str::from_utf8(&buffer).map_err(|_| "file is not valid utf-8")?; +fn load( + world: Tracked<dyn World>, + paths: &BibPaths, +) -> StrResult<EcoVec<hayagriva::Entry>> { + let mut result = EcoVec::new(); + + // We might have multiple bib/yaml files + for path in &paths.0 { + let buffer = world.file(Path::new(path.as_str()))?; + let src = std::str::from_utf8(&buffer).map_err(|_| "file is not valid utf-8")?; + let entries = parse_bib(path, src)?; + result.extend(entries); + } + + // Biblatex only checks for duplicate keys within files + // -> We have to do this between files again + let mut keys = result.iter().map(|r| r.key()).collect::<Vec<_>>(); + keys.sort_unstable(); + // Waiting for `slice_partition_dedup` #54279 + let mut duplicates = Vec::new(); + for pair in keys.windows(2) { + if pair[0] == pair[1] { + duplicates.push(pair[0]); + } + } + + if !duplicates.is_empty() { + Err(eco_format!("duplicate bibliography keys: {}", duplicates.join(", "))) + } else { + Ok(result) + } +} + +/// Parse a bibliography file (bib/yml) +fn parse_bib(path_str: &str, src: &str) -> StrResult<Vec<hayagriva::Entry>> { + let path = Path::new(path_str); let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default(); - let entries = match ext.to_lowercase().as_str() { - "yml" => hayagriva::io::from_yaml_str(src).map_err(format_hayagriva_error)?, + match ext.to_lowercase().as_str() { + "yml" => hayagriva::io::from_yaml_str(src).map_err(format_hayagriva_error), "bib" => hayagriva::io::from_biblatex_str(src).map_err(|err| { err.into_iter() .next() - .map(|error| format_biblatex_error(src, error)) - .unwrap_or_else(|| "failed to parse biblatex file".into()) - })?, - _ => return Err("unknown bibliography format".into()), - }; - Ok(entries.into_iter().collect()) + .map(|error| format_biblatex_error(path_str, src, error)) + .unwrap_or_else(|| eco_format!("failed to parse {path_str}")) + }), + _ => Err("unknown bibliography format".into()), + } } /// Format a Hayagriva loading error. @@ -558,13 +609,13 @@ fn format_hayagriva_error(error: YamlBibliographyError) -> EcoString { } /// Format a BibLaTeX loading error. -fn format_biblatex_error(src: &str, error: BibLaTeXError) -> EcoString { +fn format_biblatex_error(path: &str, src: &str, error: BibLaTeXError) -> EcoString { let (span, msg) = match error { BibLaTeXError::Parse(error) => (error.span, error.kind.to_string()), BibLaTeXError::Type(error) => (error.span, error.kind.to_string()), }; let line = src.get(..span.start).unwrap_or_default().lines().count(); - eco_format!("failed to parse biblatex file: {msg} in line {line}") + eco_format!("parsing failed at {path}:{line}: {msg}") } /// Hayagriva only supports strings, but we have a content supplement. To deal |
