obsidian_parser/note/
parser.rs1use thiserror::Error;
4
5pub fn parse_links(text: &str) -> impl Iterator<Item = &str> {
22 text.match_indices("[[").filter_map(move |(start_pos, _)| {
23 let end_pos = text[start_pos + 2..].find("]]")?;
24 let inner = &text[start_pos + 2..start_pos + 2 + end_pos];
25
26 let note_name = inner
27 .split('#')
28 .next()?
29 .split('^')
30 .next()?
31 .split('|')
32 .next()?
33 .trim();
34
35 Some(note_name)
36 })
37}
38
39#[derive(Debug, PartialEq, Eq)]
40#[allow(missing_docs)]
41pub enum ResultParse<'a> {
42 WithProperties {
43 content: &'a str,
44 properties: &'a str,
45 },
46 WithoutProperties,
47}
48
49#[derive(Debug, Error)]
51pub enum Error {
52 #[error("Not found closer in yaml like `---`")]
54 NotFoundCloser,
55}
56
57pub fn parse_note(raw_text: &str) -> Result<ResultParse<'_>, Error> {
59 let have_start_properties = raw_text
60 .lines()
61 .next()
62 .is_some_and(|line| line.trim_end() == "---");
63
64 if have_start_properties {
65 let closed = raw_text["---".len()..]
66 .find("---")
67 .ok_or(Error::NotFoundCloser)?;
68
69 return Ok(ResultParse::WithProperties {
70 content: raw_text[(closed + 2 * "...".len())..].trim(),
71 properties: raw_text["...".len()..(closed + "...".len())].trim(),
72 });
73 }
74
75 Ok(ResultParse::WithoutProperties)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::{ResultParse, parse_note};
81
82 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
83 #[test]
84 fn parse_note_without_properties() {
85 let test_data = "test_data";
86 let result = parse_note(test_data).unwrap();
87
88 assert_eq!(result, ResultParse::WithoutProperties);
89 }
90
91 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
92 #[test]
93 fn parse_note_with_properties() {
94 let test_data = "---\nproperties data\n---\ntest data";
95 let result = parse_note(test_data).unwrap();
96
97 assert_eq!(
98 result,
99 ResultParse::WithProperties {
100 content: "test data",
101 properties: "properties data"
102 }
103 );
104 }
105
106 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
107 #[test]
108 fn parse_note_without_properties_but_with_closed() {
109 let test_data1 = "test_data---";
110 let test_data2 = "test_data\n---\n";
111
112 let result1 = parse_note(test_data1).unwrap();
113 let result2 = parse_note(test_data2).unwrap();
114
115 assert_eq!(result1, ResultParse::WithoutProperties);
116 assert_eq!(result2, ResultParse::WithoutProperties);
117 }
118
119 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
120 #[test]
121 #[should_panic]
122 fn parse_note_with_properties_but_without_closed() {
123 let test_data = "---\nproperties data\ntest data";
124 let _ = parse_note(test_data).unwrap();
125 }
126
127 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
128 #[test]
129 fn parse_note_with_() {
130 let test_data = "---properties data";
131
132 let result = parse_note(test_data).unwrap();
133 assert_eq!(result, ResultParse::WithoutProperties);
134 }
135
136 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
137 #[test]
138 fn parse_note_without_properties_but_with_spaces() {
139 let test_data = " ---\ndata";
140
141 let result = parse_note(test_data).unwrap();
142 assert_eq!(result, ResultParse::WithoutProperties);
143 }
144
145 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
146 #[test]
147 fn parse_note_with_properties_but_check_trim_end() {
148 let test_data = "---\r\nproperties data\r\n---\r \ntest data";
149 let result = parse_note(test_data).unwrap();
150
151 assert_eq!(
152 result,
153 ResultParse::WithProperties {
154 content: "test data",
155 properties: "properties data"
156 }
157 );
158 }
159
160 #[cfg_attr(feature = "tracing", tracing_test::traced_test)]
161 #[test]
162 fn test_parse_links() {
163 let test_data =
164 "[[Note]] [[Note|Alias]] [[Note^block]] [[Note#Heading|Alias]] [[Note^block|Alias]]";
165
166 let ds: Vec<_> = super::parse_links(test_data).collect();
167
168 assert!(ds.iter().all(|x| *x == "Note"))
169 }
170}