badness_parser/bib/
ast.rs1pub mod nodes;
8
9pub use nodes::{Entry, EntryType, Field, FieldName, Key, StringEntry, Value};
10
11use rowan::TextRange;
12
13use crate::bib::syntax::{SyntaxKind, SyntaxNode};
14
15pub trait AstNode {
17 fn can_cast(kind: SyntaxKind) -> bool
18 where
19 Self: Sized;
20 fn cast(syntax: SyntaxNode) -> Option<Self>
21 where
22 Self: Sized;
23 fn syntax(&self) -> &SyntaxNode;
24}
25
26pub fn child<N: AstNode>(parent: &SyntaxNode) -> Option<N> {
28 parent.children().find_map(N::cast)
29}
30
31pub fn children<N: AstNode>(parent: &SyntaxNode) -> impl Iterator<Item = N> {
33 parent.children().filter_map(N::cast)
34}
35
36pub fn entry_type(entry: &SyntaxNode) -> Option<String> {
42 child::<EntryType>(entry).and_then(|t| t.text())
43}
44
45pub fn cite_key(entry: &SyntaxNode) -> Option<(String, TextRange)> {
48 let key = child::<Key>(entry)?;
49 key.text().map(|text| (text, key.syntax().text_range()))
50}
51
52pub fn string_def_name(string_entry: &SyntaxNode) -> Option<(String, TextRange)> {
56 let name = child::<Field>(string_entry)?.name_node()?;
57 name.text().map(|text| (text, name.syntax().text_range()))
58}
59
60pub fn fields(entry: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> {
62 children::<Field>(entry).map(|f| f.syntax().clone())
63}
64
65pub fn field_name(field: &SyntaxNode) -> Option<String> {
67 child::<FieldName>(field).and_then(|n| n.text())
68}
69
70pub fn field_value(field: &SyntaxNode) -> Option<SyntaxNode> {
72 child::<Value>(field).map(|v| v.syntax().clone())
73}
74
75pub fn value_text_cleaned(value: &SyntaxNode) -> String {
79 let raw = value.text().to_string();
80 let trimmed = raw.trim();
81 let inner = trimmed
82 .strip_prefix('{')
83 .and_then(|s| s.strip_suffix('}'))
84 .or_else(|| trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')))
85 .unwrap_or(trimmed);
86 inner.split_whitespace().collect::<Vec<_>>().join(" ")
87}
88
89pub fn value_macro_uses(value: &SyntaxNode) -> impl Iterator<Item = (String, TextRange)> {
94 nodes::macro_uses_of(value)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::bib::parse;
101
102 fn node(src: &str, kind: SyntaxKind) -> SyntaxNode {
104 parse(src)
105 .syntax()
106 .descendants()
107 .find(|n| n.kind() == kind)
108 .unwrap_or_else(|| panic!("a {kind:?} node"))
109 }
110
111 #[test]
112 fn entry_type_reads_word() {
113 let entry = node("@article{k, title = {Hi}}\n", SyntaxKind::ENTRY);
114 assert_eq!(entry_type(&entry).as_deref(), Some("article"));
115 }
116
117 #[test]
118 fn cite_key_reassembles_colon_key() {
119 let entry = node("@book{westfahl:space, title = {X}}\n", SyntaxKind::ENTRY);
120 let (key, _range) = cite_key(&entry).expect("a key");
121 assert_eq!(key, "westfahl:space");
122 }
123
124 #[test]
125 fn cite_key_none_without_key() {
126 let entry = node("@misc{", SyntaxKind::ENTRY);
127 assert_eq!(cite_key(&entry), None);
128 }
129
130 #[test]
131 fn string_def_name_reads_field_name() {
132 let s = node("@string{jan = \"January\"}\n", SyntaxKind::STRING_ENTRY);
133 let (name, _range) = string_def_name(&s).expect("a name");
134 assert_eq!(name, "jan");
135 }
136
137 #[test]
138 fn fields_and_names() {
139 let entry = node("@misc{k, a = {x}, b = 3}\n", SyntaxKind::ENTRY);
140 let names: Vec<_> = fields(&entry).filter_map(|f| field_name(&f)).collect();
141 assert_eq!(names, vec!["a", "b"]);
142 }
143
144 #[test]
145 fn value_macro_uses_finds_word_not_number() {
146 let field = node("@misc{k, t = pub # {x} # 2020}\n", SyntaxKind::FIELD);
148 let value = field_value(&field).expect("a value");
149 let uses: Vec<_> = value_macro_uses(&value).map(|(n, _)| n).collect();
150 assert_eq!(uses, vec!["pub"]);
151 }
152
153 #[test]
156 fn cast_is_kind_exact() {
157 let entry = node("@article{k, title = {Hi}}\n", SyntaxKind::ENTRY);
158 assert!(Entry::cast(entry.clone()).is_some());
159 assert!(Field::cast(entry).is_none());
160 }
161
162 #[test]
163 fn entry_wrapper_reads_type_key_and_fields() {
164 let entry = Entry::cast(node("@article{k, a = {x}, b = 3}\n", SyntaxKind::ENTRY)).unwrap();
165 assert_eq!(entry.entry_type().as_deref(), Some("article"));
166 assert_eq!(entry.cite_key().map(|(k, _)| k).as_deref(), Some("k"));
167 let names: Vec<_> = entry.fields().filter_map(|f| f.name()).collect();
168 assert_eq!(names, vec!["a", "b"]);
169 }
170}