pub mod ids;
mod intern;
pub mod kinds;
pub mod messages;
pub mod pformat;
pub(crate) use intern::intern;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
pub source: u16,
pub line: u32,
pub start: u32,
pub end: u32,
}
impl Span {
pub const ZERO: Span = Span {
source: 0,
line: 0,
start: 0,
end: 0,
};
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttrValue {
Int(i64),
Str(String),
List(Vec<String>),
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Attrs {
pub ids: Vec<String>,
pub names: Vec<String>,
pub dupnames: Vec<String>,
pub classes: Vec<String>,
pub backrefs: Vec<String>,
#[serde(
serialize_with = "intern::serialize_extra",
deserialize_with = "intern::deserialize_extra"
)]
pub extra: Vec<(&'static str, AttrValue)>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Node {
#[serde(serialize_with = "intern::serialize_str")]
pub kind: &'static str,
pub span: Span,
pub text: Option<String>,
pub attrs: Attrs,
pub children: Vec<Node>,
}
#[derive(Deserialize)]
struct NodeShadow {
kind: String,
span: Span,
text: Option<String>,
attrs: Attrs,
children: Vec<Node>,
}
impl<'de> Deserialize<'de> for Node {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let shadow = NodeShadow::deserialize(deserializer)?;
let kind = intern(&shadow.kind).map_err(serde::de::Error::custom)?;
Ok(Node {
kind,
span: shadow.span,
text: shadow.text,
attrs: shadow.attrs,
children: shadow.children,
})
}
}
impl Node {
pub fn elem(kind: &'static str, span: Span) -> Node {
Node {
kind,
span,
text: None,
attrs: Attrs::default(),
children: Vec::new(),
}
}
pub fn text_node(s: impl Into<String>, span: Span) -> Node {
Node {
kind: kinds::TEXT,
span,
text: Some(s.into()),
attrs: Attrs::default(),
children: Vec::new(),
}
}
pub fn shallow_copy(&self) -> Node {
Node {
kind: self.kind,
span: self.span,
text: self.text.clone(),
attrs: self.attrs.clone(),
children: Vec::new(),
}
}
pub fn set(&mut self, key: &'static str, value: AttrValue) {
match self.attrs.extra.binary_search_by(|(k, _)| k.cmp(&key)) {
Ok(i) => self.attrs.extra[i].1 = value,
Err(i) => self.attrs.extra.insert(i, (key, value)),
}
}
pub fn get(&self, key: &'static str) -> Option<&AttrValue> {
self.attrs
.extra
.binary_search_by(|(k, _)| k.cmp(&key))
.ok()
.map(|i| &self.attrs.extra[i].1)
}
pub fn astext(&self) -> String {
match &self.text {
Some(t) => t.clone(),
None => self.children.iter().map(Node::astext).collect(),
}
}
pub fn pformat(&self) -> String {
pformat::pformat(self)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Doctree {
pub root: Node,
#[serde(default = "default_sources")]
pub sources: Vec<String>,
}
impl Doctree {
pub fn source_and_line(&self, span: Span) -> (&str, u32) {
let path = self
.sources
.get(span.source as usize)
.or_else(|| self.sources.first())
.map(String::as_str)
.unwrap_or("<document>");
(path, span.line)
}
}
fn default_sources() -> Vec<String> {
vec!["<document>".to_string()]
}
pub fn to_bincode(doctree: &Doctree) -> Vec<u8> {
bincode::serde::encode_to_vec(doctree, bincode::config::standard())
.expect("Doctree encoding is infallible")
}
pub fn from_bincode(bytes: &[u8]) -> anyhow::Result<Doctree> {
let (doctree, _consumed): (Doctree, usize) =
bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
Ok(doctree)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn doctree_deserialize_defaults_sources_when_field_absent_in_json() {
let json = r#"{"root":{"kind":"document","span":{"source":0,"line":0,"start":0,"end":0},"text":null,"attrs":{"ids":[],"names":[],"dupnames":[],"classes":[],"backrefs":[],"extra":{}},"children":[]}}"#;
let restored: Doctree = serde_json::from_str(json).expect("json without sources decodes");
assert_eq!(restored.sources, vec!["<document>".to_string()]);
assert_eq!(restored.root.kind, kinds::DOCUMENT);
}
#[test]
fn elem_constructs_with_kind_and_span() {
let n = Node::elem(
kinds::PARAGRAPH,
Span {
source: 0,
line: 1,
start: 0,
end: 10,
},
);
assert_eq!(n.kind, "paragraph");
assert!(n.text.is_none());
assert!(n.children.is_empty());
}
#[test]
fn text_node_holds_text() {
let t = Node::text_node(
"hello",
Span {
source: 0,
line: 1,
start: 0,
end: 5,
},
);
assert_eq!(t.kind, kinds::TEXT);
assert_eq!(t.text.as_deref(), Some("hello"));
}
#[test]
fn set_keeps_extra_sorted_and_get_finds() {
let mut n = Node::elem(kinds::TARGET, Span::ZERO);
n.set("refuri", AttrValue::Str("https://x/".into()));
n.set("anonymous", AttrValue::Int(1));
assert_eq!(n.attrs.extra[0].0, "anonymous");
assert_eq!(n.get("refuri"), Some(&AttrValue::Str("https://x/".into())));
n.set("refuri", AttrValue::Str("https://y/".into()));
assert_eq!(n.attrs.extra.len(), 2);
assert_eq!(n.get("refuri"), Some(&AttrValue::Str("https://y/".into())));
}
#[test]
fn source_and_line_reads_the_table_and_falls_back_to_entry_0() {
let tree = Doctree {
root: Node::elem(kinds::DOCUMENT, Span::ZERO),
sources: vec!["a.rst".to_string(), "b.rst".to_string()],
};
let span = |source, line| Span {
source,
line,
start: 0,
end: 0,
};
assert_eq!(tree.source_and_line(span(0, 3)), ("a.rst", 3));
assert_eq!(tree.source_and_line(span(1, 7)), ("b.rst", 7));
assert_eq!(
tree.source_and_line(span(9, 2)),
("a.rst", 2),
"an unknown source id falls back to the document's own path"
);
}
#[test]
fn astext_joins_text_descendants() {
let mut p = Node::elem(kinds::PARAGRAPH, Span::ZERO);
p.children
.push(Node::text_node("line one\nline two", Span::ZERO));
assert_eq!(p.astext(), "line one\nline two");
}
}