1use anyhow::{Context, Result};
2use std::collections::HashSet;
3use std::fs::OpenOptions;
4use std::io::{BufWriter, Write};
5use std::path::Path;
6
7use crate::error::LinkError;
8use crate::link::Link;
9use crate::link_storage::LinkStorage;
10use crate::named_types::{NamedTypes, NamedTypesDecorator};
11
12pub trait NamedTypeLinks {
13 fn create(&mut self, source: u32, target: u32) -> u32;
14 fn ensure_created(&mut self, id: u32) -> u32;
15 fn try_ensure_created(&mut self, id: u32) -> Result<u32> {
16 if id == 0 || id == u32::MAX {
17 return Err(LinkError::InvalidFormat(format!(
18 "Cannot ensure unsupported link address {id}"
19 ))
20 .into());
21 }
22
23 Ok(self.ensure_created(id))
24 }
25 fn get_link(&mut self, id: u32) -> Option<Link>;
26 fn exists(&mut self, id: u32) -> bool;
27 fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link>;
28 fn delete(&mut self, id: u32) -> Result<Link>;
29 fn all_links(&mut self) -> Vec<Link>;
30 fn search(&mut self, source: u32, target: u32) -> Option<u32>;
31 fn get_or_create(&mut self, source: u32, target: u32) -> u32;
32 fn get_name(&mut self, id: u32) -> Result<Option<String>>;
33 fn set_name(&mut self, id: u32, name: &str) -> Result<u32>;
34 fn get_by_name(&mut self, name: &str) -> Result<Option<u32>>;
35 fn remove_name(&mut self, id: u32) -> Result<()>;
36 fn save(&mut self) -> Result<()>;
37
38 fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
39 if let Some(id) = self.get_by_name(name)? {
40 return Ok(id);
41 }
42
43 let id = self.create(0, 0);
44 self.set_name(id, name)?;
45 self.update(id, id, id)?;
46 Ok(id)
47 }
48
49 fn format_reference(&mut self, id: u32) -> Result<String> {
50 Ok(self
51 .get_name(id)?
52 .map(|name| escape_lino_reference(&name))
53 .unwrap_or_else(|| id.to_string()))
54 }
55
56 fn format_lino(&mut self, link: &Link) -> Result<String> {
57 Ok(format!(
58 "({}: {} {})",
59 self.format_reference(link.index)?,
60 self.format_reference(link.source)?,
61 self.format_reference(link.target)?
62 ))
63 }
64
65 fn lino_lines(&mut self) -> Result<Vec<String>> {
66 let mut links = self.all_links();
67 links.sort_by_key(|link| link.index);
68
69 links
70 .iter()
71 .map(|link| self.format_lino(link))
72 .collect::<Result<Vec<_>>>()
73 }
74
75 fn write_lino_output<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
76 let path = path.as_ref();
77 let file = OpenOptions::new()
78 .write(true)
79 .create(true)
80 .truncate(true)
81 .open(path)
82 .with_context(|| format!("Failed to create LiNo output: {}", path.display()))?;
83
84 let mut writer = BufWriter::new(file);
85 for line in self.lino_lines()? {
86 writeln!(writer, "{line}")?;
87 }
88 writer.flush()?;
89 Ok(())
90 }
91
92 fn print_all_lino(&mut self) -> Result<()> {
93 for line in self.lino_lines()? {
94 println!("{line}");
95 }
96 Ok(())
97 }
98
99 fn print_change_lino(&mut self, before: &Option<Link>, after: &Option<Link>) -> Result<()> {
100 let before_text = before
101 .map(|link| self.format_lino(&link))
102 .transpose()?
103 .unwrap_or_default();
104 let after_text = after
105 .map(|link| self.format_lino(&link))
106 .transpose()?
107 .unwrap_or_default();
108 println!("({before_text}) ({after_text})");
109 Ok(())
110 }
111
112 fn format_structure(&mut self, id: u32) -> Result<String> {
113 let mut visited = HashSet::new();
114 self.format_structure_recursive(id, &mut visited)
115 }
116
117 fn format_structure_recursive(
118 &mut self,
119 id: u32,
120 visited: &mut HashSet<u32>,
121 ) -> Result<String> {
122 let link = self.get_link(id).ok_or(LinkError::NotFound(id))?;
123 if !visited.insert(id) {
124 return self.format_reference(id);
125 }
126
127 let source = if self.exists(link.source) && !visited.contains(&link.source) {
128 self.format_structure_recursive(link.source, visited)?
129 } else {
130 self.format_reference(link.source)?
131 };
132 let target = self.format_reference(link.target)?;
133 let index = self.format_reference(link.index)?;
134 visited.remove(&id);
135
136 Ok(format!("({index}: {source} {target})"))
137 }
138}
139
140impl NamedTypeLinks for LinkStorage {
141 fn create(&mut self, source: u32, target: u32) -> u32 {
142 LinkStorage::create(self, source, target)
143 }
144
145 fn ensure_created(&mut self, id: u32) -> u32 {
146 LinkStorage::ensure_created(self, id)
147 }
148
149 fn get_link(&mut self, id: u32) -> Option<Link> {
150 self.get(id).copied()
151 }
152
153 fn exists(&mut self, id: u32) -> bool {
154 LinkStorage::exists(self, id)
155 }
156
157 fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
158 LinkStorage::update(self, id, source, target)
159 }
160
161 fn delete(&mut self, id: u32) -> Result<Link> {
162 LinkStorage::delete(self, id)
163 }
164
165 fn all_links(&mut self) -> Vec<Link> {
166 self.all().into_iter().copied().collect()
167 }
168
169 fn search(&mut self, source: u32, target: u32) -> Option<u32> {
170 LinkStorage::search(self, source, target)
171 }
172
173 fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
174 LinkStorage::get_or_create(self, source, target)
175 }
176
177 fn get_name(&mut self, id: u32) -> Result<Option<String>> {
178 Ok(LinkStorage::get_name(self, id).cloned())
179 }
180
181 fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
182 LinkStorage::set_name(self, id, name);
183 Ok(id)
184 }
185
186 fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
187 Ok(LinkStorage::get_by_name(self, name))
188 }
189
190 fn remove_name(&mut self, id: u32) -> Result<()> {
191 LinkStorage::remove_name(self, id);
192 Ok(())
193 }
194
195 fn save(&mut self) -> Result<()> {
196 LinkStorage::save(self)
197 }
198
199 fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
200 Ok(LinkStorage::get_or_create_named(self, name))
201 }
202}
203
204impl NamedTypeLinks for NamedTypesDecorator {
205 fn create(&mut self, source: u32, target: u32) -> u32 {
206 NamedTypesDecorator::create(self, source, target)
207 }
208
209 fn ensure_created(&mut self, id: u32) -> u32 {
210 NamedTypesDecorator::ensure_created(self, id)
211 }
212
213 fn get_link(&mut self, id: u32) -> Option<Link> {
214 self.get(id).copied()
215 }
216
217 fn exists(&mut self, id: u32) -> bool {
218 NamedTypesDecorator::exists(self, id)
219 }
220
221 fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
222 NamedTypesDecorator::update(self, id, source, target)
223 }
224
225 fn delete(&mut self, id: u32) -> Result<Link> {
226 NamedTypesDecorator::delete(self, id)
227 }
228
229 fn all_links(&mut self) -> Vec<Link> {
230 self.all().into_iter().copied().collect()
231 }
232
233 fn search(&mut self, source: u32, target: u32) -> Option<u32> {
234 NamedTypesDecorator::search(self, source, target)
235 }
236
237 fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
238 NamedTypesDecorator::get_or_create(self, source, target)
239 }
240
241 fn get_name(&mut self, id: u32) -> Result<Option<String>> {
242 NamedTypes::get_name(self, id)
243 }
244
245 fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
246 NamedTypes::set_name(self, id, name)
247 }
248
249 fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
250 NamedTypes::get_by_name(self, name)
251 }
252
253 fn remove_name(&mut self, id: u32) -> Result<()> {
254 NamedTypes::remove_name(self, id)
255 }
256
257 fn save(&mut self) -> Result<()> {
258 NamedTypesDecorator::save(self)
259 }
260}
261
262pub(crate) fn escape_lino_reference(reference: &str) -> String {
263 if reference.is_empty() || reference.trim().is_empty() {
264 return String::new();
265 }
266
267 let has_single_quote = reference.contains('\'');
268 let has_double_quote = reference.contains('"');
269 let needs_quoting = reference.contains(':')
270 || reference.contains('(')
271 || reference.contains(')')
272 || reference.contains(' ')
273 || reference.contains('\t')
274 || reference.contains('\n')
275 || reference.contains('\r')
276 || has_single_quote
277 || has_double_quote;
278
279 if has_single_quote && has_double_quote {
280 return format!("'{}'", reference.replace('\'', "\\'"));
281 }
282
283 if has_double_quote {
284 return format!("'{reference}'");
285 }
286
287 if has_single_quote {
288 return format!("\"{reference}\"");
289 }
290
291 if needs_quoting {
292 return format!("'{reference}'");
293 }
294
295 reference.to_string()
296}