1use crate::parsing::ast::Span;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8pub struct Source {
9 pub attribute: String,
11
12 pub span: Span,
14
15 pub doc_name: String,
17}
18
19impl Source {
20 #[must_use]
22 pub fn new(attribute: impl Into<String>, span: Span, doc_name: impl Into<String>) -> Self {
23 Self {
24 attribute: attribute.into(),
25 span,
26 doc_name: doc_name.into(),
27 }
28 }
29
30 pub fn extract_text(&self, source: &str) -> Option<String> {
34 let bytes = source.as_bytes();
35 if self.span.start < bytes.len() && self.span.end <= bytes.len() {
36 Some(String::from_utf8_lossy(&bytes[self.span.start..self.span.end]).to_string())
37 } else {
38 None
39 }
40 }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn test_extract_text_valid() {
49 let source = "hello world";
50 let span = Span {
51 start: 0,
52 end: 5,
53 line: 1,
54 col: 0,
55 };
56 let loc = Source::new("test.lemma", span, "test");
57 assert_eq!(loc.extract_text(source), Some("hello".to_string()));
58 }
59
60 #[test]
61 fn test_extract_text_middle() {
62 let source = "hello world";
63 let span = Span {
64 start: 6,
65 end: 11,
66 line: 1,
67 col: 6,
68 };
69 let loc = Source::new("test.lemma", span, "test");
70 assert_eq!(loc.extract_text(source), Some("world".to_string()));
71 }
72
73 #[test]
74 fn test_extract_text_full_string() {
75 let source = "hello world";
76 let span = Span {
77 start: 0,
78 end: 11,
79 line: 1,
80 col: 0,
81 };
82 let loc = Source::new("test.lemma", span, "test");
83 assert_eq!(loc.extract_text(source), Some("hello world".to_string()));
84 }
85
86 #[test]
87 fn test_extract_text_empty() {
88 let source = "hello world";
89 let span = Span {
90 start: 5,
91 end: 5,
92 line: 1,
93 col: 5,
94 };
95 let loc = Source::new("test.lemma", span, "test");
96 assert_eq!(loc.extract_text(source), Some("".to_string()));
97 }
98
99 #[test]
100 fn test_extract_text_out_of_bounds_start() {
101 let source = "hello";
102 let span = Span {
103 start: 10,
104 end: 15,
105 line: 1,
106 col: 10,
107 };
108 let loc = Source::new("test.lemma", span, "test");
109 assert_eq!(loc.extract_text(source), None);
110 }
111
112 #[test]
113 fn test_extract_text_out_of_bounds_end() {
114 let source = "hello";
115 let span = Span {
116 start: 0,
117 end: 10,
118 line: 1,
119 col: 0,
120 };
121 let loc = Source::new("test.lemma", span, "test");
122 assert_eq!(loc.extract_text(source), None);
123 }
124
125 #[test]
126 fn test_extract_text_unicode() {
127 let source = "hello 世界";
128 let span = Span {
129 start: 6,
130 end: 12,
131 line: 1,
132 col: 6,
133 };
134 let loc = Source::new("test.lemma", span, "test");
135 assert_eq!(loc.extract_text(source), Some("世界".to_string()));
136 }
137
138 #[test]
139 fn test_new_with_string() {
140 let span = Span {
141 start: 0,
142 end: 5,
143 line: 1,
144 col: 0,
145 };
146 let loc = Source::new("test.lemma", span, "test");
147 assert_eq!(loc.attribute, "test.lemma");
148 assert_eq!(loc.doc_name, "test");
149 }
150
151 #[test]
152 fn test_new_with_str() {
153 let span = Span {
154 start: 0,
155 end: 5,
156 line: 1,
157 col: 0,
158 };
159 let loc = Source::new("test.lemma", span, "test");
160 assert_eq!(loc.attribute, "test.lemma");
161 assert_eq!(loc.doc_name, "test");
162 }
163}