use super::walk::{owner_child_step, visit_child_entry_locations};
use crate::{Block, ContentBlockStep, EntryOwner, SourceSpan};
pub struct ContentEntry<'a> {
item: EntryOwner<'a>,
names: &'a [String],
source: Option<SourceSpan>,
indices: Vec<usize>,
ancestors: Vec<EntryOwner<'a>>,
container: &'a Block,
item_index: usize,
block_path: Vec<ContentBlockStep>,
}
impl<'a> ContentEntry<'a> {
#[must_use]
pub const fn owner(&self) -> EntryOwner<'a> {
self.item
}
#[must_use]
pub const fn names(&self) -> &'a [String] {
self.names
}
#[must_use]
pub const fn source(&self) -> Option<SourceSpan> {
self.source
}
#[must_use]
pub fn indices(&self) -> &[usize] {
&self.indices
}
#[must_use]
pub fn ancestors(&self) -> &[EntryOwner<'a>] {
&self.ancestors
}
#[must_use]
pub const fn item_index(&self) -> usize {
self.item_index
}
#[must_use]
pub fn block_path(&self) -> &[ContentBlockStep] {
&self.block_path
}
#[must_use]
pub fn content(&self) -> Block {
match self.container {
Block::List {
kind,
compact,
items,
layout,
source,
} => Block::List {
kind: kind.for_excerpt(self.item_index),
compact: *compact,
items: vec![items[self.item_index].clone()],
layout: *layout,
source: *source,
},
Block::DefinitionList {
declaration_groups: _,
items,
compact,
layout,
source,
} => Block::DefinitionList {
declaration_groups: Vec::new(),
items: vec![items[self.item_index].clone()],
compact: *compact,
layout: *layout,
source: *source,
},
_ => unreachable!("entry containers are lists"),
}
}
}
impl serde::Serialize for ContentEntry<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeMap as _;
let mut output = serializer.serialize_map(None)?;
let (layout, source) = match self.container {
Block::List {
kind,
compact,
items,
layout,
source,
} => {
output.serialize_entry("type", "list")?;
output.serialize_entry("kind", &kind.for_excerpt(self.item_index))?;
if *compact {
output.serialize_entry("compact", compact)?;
}
output.serialize_entry("items", std::slice::from_ref(&items[self.item_index]))?;
(layout, source)
}
Block::DefinitionList {
declaration_groups: _,
items,
compact,
layout,
source,
} => {
output.serialize_entry("type", "definition-list")?;
output.serialize_entry("items", std::slice::from_ref(&items[self.item_index]))?;
if *compact {
output.serialize_entry("compact", compact)?;
}
(layout, source)
}
_ => unreachable!("entry containers are lists"),
};
if !layout.is_empty() {
output.serialize_entry("layout", layout)?;
}
if let Some(source) = source {
output.serialize_entry("source", source)?;
}
output.end()
}
}
#[must_use]
pub fn content_entry_locations(blocks: &[Block]) -> Vec<ContentEntry<'_>> {
collect::<false>(blocks)
}
#[must_use]
pub fn content_entries(blocks: &[Block]) -> Vec<ContentEntry<'_>> {
collect::<true>(blocks)
}
fn collect<const NAMES: bool>(blocks: &[Block]) -> Vec<ContentEntry<'_>> {
let mut output = Vec::new();
collect_scope::<NAMES>(blocks, &[], &mut Vec::new(), &[], &mut output);
output
}
fn collect_scope<'a, const NAMES: bool>(
blocks: &'a [Block],
parent_indices: &[usize],
ancestors: &mut Vec<EntryOwner<'a>>,
prefix: &[ContentBlockStep],
output: &mut Vec<ContentEntry<'a>>,
) {
let mut ordinal = 0;
visit_child_entry_locations(blocks, prefix, &mut |item, container, path, item_index| {
ordinal += 1;
let mut indices = parent_indices.to_vec();
indices.push(ordinal);
output.push(ContentEntry {
item,
names: if NAMES {
item.validated_names().unwrap_or_default()
} else {
&[]
},
source: item.source(),
indices: indices.clone(),
ancestors: ancestors.clone(),
container,
item_index: item_index as usize,
block_path: path.to_vec(),
});
ancestors.push(item);
let mut child_path = path.to_vec();
child_path.push(owner_child_step(item, item_index));
collect_scope::<NAMES>(item.blocks(), &indices, ancestors, &child_path, output);
ancestors.pop();
});
}
#[cfg(test)]
mod tests;