c6o_obsidian_export/
references.rs1use regex::Regex;
2use std::fmt;
3
4lazy_static! {
5 static ref OBSIDIAN_NOTE_LINK_RE: Regex =
6 Regex::new(r"^(?P<file>[^#|]+)??(#(?P<section>.+?))??(\|(?P<label>.+?))??$").unwrap();
7}
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct ObsidianNoteReference<'a> {
12 pub file: Option<&'a str>,
15 pub section: Option<&'a str>,
17 pub label: Option<&'a str>,
19}
20
21#[derive(PartialEq)]
22pub enum RefParserState {
24 NoState,
25 ExpectSecondOpenBracket,
26 ExpectRefText,
27 ExpectRefTextOrCloseBracket,
28 ExpectFinalCloseBracket,
29 Resetting,
30}
31
32pub enum RefType {
34 Link,
35 Embed,
36}
37
38pub struct RefParser {
40 pub state: RefParserState,
41 pub ref_type: Option<RefType>,
42 pub ref_text: String,
49}
50
51impl RefParser {
52 pub fn new() -> RefParser {
53 RefParser {
54 state: RefParserState::NoState,
55 ref_type: None,
56 ref_text: String::new(),
57 }
58 }
59
60 pub fn transition(&mut self, new_state: RefParserState) {
61 self.state = new_state;
62 }
63
64 pub fn reset(&mut self) {
65 self.state = RefParserState::NoState;
66 self.ref_type = None;
67 self.ref_text.clear();
68 }
69}
70
71impl<'a> ObsidianNoteReference<'a> {
72 pub fn from_str(text: &str) -> ObsidianNoteReference {
73 let captures = OBSIDIAN_NOTE_LINK_RE
74 .captures(text)
75 .expect("note link regex didn't match - bad input?");
76 let file = captures.name("file").map(|v| v.as_str());
77 let label = captures.name("label").map(|v| v.as_str());
78 let section = captures.name("section").map(|v| v.as_str());
79
80 ObsidianNoteReference {
81 file,
82 section,
83 label,
84 }
85 }
86
87 pub fn display(&self) -> String {
88 format!("{}", self)
89 }
90}
91
92impl<'a> fmt::Display for ObsidianNoteReference<'a> {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 let label =
95 self.label
96 .map(|v| v.to_string())
97 .unwrap_or_else(|| match (self.file, self.section) {
98 (Some(file), Some(section)) => format!("{} > {}", file, section),
99 (Some(file), None) => file.to_string(),
100 (None, Some(section)) => section.to_string(),
101
102 _ => panic!("Reference exists without file or section!"),
103 });
104 write!(f, "{}", label)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn parse_note_refs_from_strings() {
114 assert_eq!(
115 ObsidianNoteReference::from_str("Just a note"),
116 ObsidianNoteReference {
117 file: Some("Just a note"),
118 label: None,
119 section: None,
120 }
121 );
122 assert_eq!(
123 ObsidianNoteReference::from_str("A note?"),
124 ObsidianNoteReference {
125 file: Some("A note?"),
126 label: None,
127 section: None,
128 }
129 );
130 assert_eq!(
131 ObsidianNoteReference::from_str("Note#with heading"),
132 ObsidianNoteReference {
133 file: Some("Note"),
134 label: None,
135 section: Some("with heading"),
136 }
137 );
138 assert_eq!(
139 ObsidianNoteReference::from_str("Note#Heading|Label"),
140 ObsidianNoteReference {
141 file: Some("Note"),
142 label: Some("Label"),
143 section: Some("Heading"),
144 }
145 );
146 assert_eq!(
147 ObsidianNoteReference::from_str("#Heading|Label"),
148 ObsidianNoteReference {
149 file: None,
150 label: Some("Label"),
151 section: Some("Heading"),
152 }
153 );
154 }
155
156 #[test]
157 fn test_display_of_note_refs() {
158 assert_eq!(
159 "Note",
160 ObsidianNoteReference {
161 file: Some("Note"),
162 label: None,
163 section: None,
164 }
165 .display()
166 );
167 assert_eq!(
168 "Note > Heading",
169 ObsidianNoteReference {
170 file: Some("Note"),
171 label: None,
172 section: Some("Heading"),
173 }
174 .display()
175 );
176 assert_eq!(
177 "Heading",
178 ObsidianNoteReference {
179 file: None,
180 label: None,
181 section: Some("Heading"),
182 }
183 .display()
184 );
185 assert_eq!(
186 "Label",
187 ObsidianNoteReference {
188 file: Some("Note"),
189 label: Some("Label"),
190 section: Some("Heading"),
191 }
192 .display()
193 );
194 assert_eq!(
195 "Label",
196 ObsidianNoteReference {
197 file: None,
198 label: Some("Label"),
199 section: Some("Heading"),
200 }
201 .display()
202 );
203 }
204}