Skip to main content

link_cli/
named_type_links.rs

1//! [`NamedTypeLinks`], the storage interface every layer of the CLI is written
2//! against, and its implementations for the plain store and for each decorator.
3//!
4//! A store that implements this trait can be dropped anywhere in the stack, so
5//! an application can add its own layer -- a cache, a permission check, a
6//! remote store -- without the query processor knowing about it.
7
8use anyhow::{Context, Result};
9use std::collections::HashSet;
10use std::fs::OpenOptions;
11use std::io::{BufWriter, Write};
12use std::path::Path;
13
14use crate::error::LinkError;
15use crate::link::Link;
16use crate::link_storage::{ChangeObserver, LinkStorage};
17use crate::named_types::{NamedTypes, NamedTypesDecorator};
18
19pub trait NamedTypeLinks {
20    fn create(&mut self, source: u32, target: u32) -> u32;
21    fn ensure_created(&mut self, id: u32) -> u32;
22    fn try_ensure_created(&mut self, id: u32) -> Result<u32> {
23        if id == 0 || id == u32::MAX {
24            return Err(LinkError::InvalidFormat(format!(
25                "Cannot ensure unsupported link address {id}"
26            ))
27            .into());
28        }
29
30        Ok(self.ensure_created(id))
31    }
32    fn get_link(&mut self, id: u32) -> Option<Link>;
33    fn exists(&mut self, id: u32) -> bool;
34    fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
35        self.update_observed(id, source, target, &mut |_, _| {})
36    }
37    /// [`Self::update`], reporting every change the update caused.
38    ///
39    /// An update that would duplicate an existing doublet is resolved into a
40    /// merge: the updated link is deleted and its usages are moved to the
41    /// duplicate, so one call can make several changes. The C# CLI sees them
42    /// because `AdvancedMixedQueryProcessor` hands a handler to the store:
43    ///
44    /// ```csharp
45    /// links.Update(before, after, (beforeState, afterState) =>
46    ///     options.ChangesHandler?.Invoke(beforeState, afterState) ?? links.Constants.Continue);
47    /// ```
48    fn update_observed(
49        &mut self,
50        id: u32,
51        source: u32,
52        target: u32,
53        observer: ChangeObserver<'_>,
54    ) -> Result<Link>;
55    fn delete(&mut self, id: u32) -> Result<Link> {
56        self.delete_observed(id, &mut |_, _| {})
57    }
58    /// [`Self::delete`], reporting every change the deletion caused.
59    ///
60    /// Deleting a link cascades into every link that still used it, and each of
61    /// those removals is a change in its own right, reported the way
62    /// `AdvancedMixedQueryProcessor.RemoveLinks` reports them in C#:
63    ///
64    /// ```csharp
65    /// links.Delete(link, (before, after) =>
66    ///     options.ChangesHandler?.Invoke(before, after) ?? links.Constants.Continue);
67    /// ```
68    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link>;
69    fn all_links(&mut self) -> Vec<Link>;
70    fn search(&mut self, source: u32, target: u32) -> Option<u32>;
71    fn get_or_create(&mut self, source: u32, target: u32) -> u32;
72    fn get_name(&mut self, id: u32) -> Result<Option<String>>;
73    fn set_name(&mut self, id: u32, name: &str) -> Result<u32>;
74    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>>;
75    fn remove_name(&mut self, id: u32) -> Result<()>;
76    fn save(&mut self) -> Result<()>;
77
78    fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
79        if let Some(id) = self.get_by_name(name)? {
80            return Ok(id);
81        }
82
83        let id = self.create(0, 0);
84        self.set_name(id, name)?;
85        self.update(id, id, id)?;
86        Ok(id)
87    }
88
89    fn format_reference(&mut self, id: u32) -> Result<String> {
90        Ok(self
91            .get_name(id)?
92            .map(|name| escape_lino_reference(&name))
93            .unwrap_or_else(|| id.to_string()))
94    }
95
96    fn format_lino(&mut self, link: &Link) -> Result<String> {
97        Ok(format!(
98            "({}: {} {})",
99            self.format_reference(link.index)?,
100            self.format_reference(link.source)?,
101            self.format_reference(link.target)?
102        ))
103    }
104
105    fn lino_lines(&mut self) -> Result<Vec<String>> {
106        let mut links = self.all_links();
107        links.sort_by_key(|link| link.index);
108
109        links
110            .iter()
111            .map(|link| self.format_lino(link))
112            .collect::<Result<Vec<_>>>()
113    }
114
115    fn write_lino_output<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
116        let path = path.as_ref();
117        let file = OpenOptions::new()
118            .write(true)
119            .create(true)
120            .truncate(true)
121            .open(path)
122            .with_context(|| format!("Failed to create LiNo output: {}", path.display()))?;
123
124        let mut writer = BufWriter::new(file);
125        for line in self.lino_lines()? {
126            writeln!(writer, "{line}")?;
127        }
128        writer.flush()?;
129        Ok(())
130    }
131
132    fn print_all_lino(&mut self) -> Result<()> {
133        for line in self.lino_lines()? {
134            println!("{line}");
135        }
136        Ok(())
137    }
138
139    fn print_change_lino(&mut self, before: &Option<Link>, after: &Option<Link>) -> Result<()> {
140        let before_text = before
141            .map(|link| self.format_lino(&link))
142            .transpose()?
143            .unwrap_or_default();
144        let after_text = after
145            .map(|link| self.format_lino(&link))
146            .transpose()?
147            .unwrap_or_default();
148        println!("({before_text}) ({after_text})");
149        Ok(())
150    }
151
152    fn format_structure(&mut self, id: u32) -> Result<String> {
153        let mut visited = HashSet::new();
154        self.format_structure_recursive(id, &mut visited)
155    }
156
157    fn format_structure_recursive(
158        &mut self,
159        id: u32,
160        visited: &mut HashSet<u32>,
161    ) -> Result<String> {
162        let link = self.get_link(id).ok_or(LinkError::not_found(id))?;
163        if !visited.insert(id) {
164            return self.format_reference(id);
165        }
166
167        let source = if self.exists(link.source) && !visited.contains(&link.source) {
168            self.format_structure_recursive(link.source, visited)?
169        } else {
170            self.format_reference(link.source)?
171        };
172        let target = self.format_reference(link.target)?;
173        let index = self.format_reference(link.index)?;
174        visited.remove(&id);
175
176        Ok(format!("({index}: {source} {target})"))
177    }
178}
179
180impl NamedTypeLinks for LinkStorage {
181    fn create(&mut self, source: u32, target: u32) -> u32 {
182        LinkStorage::create(self, source, target)
183    }
184
185    fn ensure_created(&mut self, id: u32) -> u32 {
186        LinkStorage::ensure_created(self, id)
187    }
188
189    fn get_link(&mut self, id: u32) -> Option<Link> {
190        self.get(id).copied()
191    }
192
193    fn exists(&mut self, id: u32) -> bool {
194        LinkStorage::exists(self, id)
195    }
196
197    fn update_observed(
198        &mut self,
199        id: u32,
200        source: u32,
201        target: u32,
202        observer: ChangeObserver<'_>,
203    ) -> Result<Link> {
204        LinkStorage::update_observed(self, id, source, target, observer)
205    }
206
207    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
208        LinkStorage::delete_observed(self, id, observer)
209    }
210
211    fn all_links(&mut self) -> Vec<Link> {
212        self.all().into_iter().copied().collect()
213    }
214
215    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
216        LinkStorage::search(self, source, target)
217    }
218
219    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
220        LinkStorage::get_or_create(self, source, target)
221    }
222
223    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
224        Ok(LinkStorage::get_name(self, id).cloned())
225    }
226
227    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
228        LinkStorage::set_name(self, id, name);
229        Ok(id)
230    }
231
232    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
233        Ok(LinkStorage::get_by_name(self, name))
234    }
235
236    fn remove_name(&mut self, id: u32) -> Result<()> {
237        LinkStorage::remove_name(self, id);
238        Ok(())
239    }
240
241    fn save(&mut self) -> Result<()> {
242        LinkStorage::save(self)
243    }
244
245    fn get_or_create_named(&mut self, name: &str) -> Result<u32> {
246        Ok(LinkStorage::get_or_create_named(self, name))
247    }
248}
249
250impl NamedTypeLinks for NamedTypesDecorator {
251    fn create(&mut self, source: u32, target: u32) -> u32 {
252        NamedTypesDecorator::create(self, source, target)
253    }
254
255    fn ensure_created(&mut self, id: u32) -> u32 {
256        NamedTypesDecorator::ensure_created(self, id)
257    }
258
259    fn get_link(&mut self, id: u32) -> Option<Link> {
260        self.get(id).copied()
261    }
262
263    fn exists(&mut self, id: u32) -> bool {
264        NamedTypesDecorator::exists(self, id)
265    }
266
267    fn update_observed(
268        &mut self,
269        id: u32,
270        source: u32,
271        target: u32,
272        observer: ChangeObserver<'_>,
273    ) -> Result<Link> {
274        NamedTypesDecorator::update_observed(self, id, source, target, observer)
275    }
276
277    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
278        NamedTypesDecorator::delete_observed(self, id, observer)
279    }
280
281    fn all_links(&mut self) -> Vec<Link> {
282        self.all().into_iter().copied().collect()
283    }
284
285    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
286        NamedTypesDecorator::search(self, source, target)
287    }
288
289    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
290        NamedTypesDecorator::get_or_create(self, source, target)
291    }
292
293    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
294        NamedTypes::get_name(self, id)
295    }
296
297    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
298        NamedTypes::set_name(self, id, name)
299    }
300
301    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
302        NamedTypes::get_by_name(self, name)
303    }
304
305    fn remove_name(&mut self, id: u32) -> Result<()> {
306        NamedTypes::remove_name(self, id)
307    }
308
309    fn save(&mut self) -> Result<()> {
310        NamedTypesDecorator::save(self)
311    }
312}
313
314impl NamedTypeLinks for crate::transactions::TransactionsDecorator {
315    fn create(&mut self, source: u32, target: u32) -> u32 {
316        crate::transactions::TransactionsDecorator::create(self, source, target)
317            .expect("TransactionsDecorator::create failed in NamedTypeLinks bridge")
318    }
319
320    fn ensure_created(&mut self, id: u32) -> u32 {
321        crate::transactions::TransactionsDecorator::ensure_created(self, id)
322            .expect("TransactionsDecorator::ensure_created failed in NamedTypeLinks bridge")
323    }
324
325    fn get_link(&mut self, id: u32) -> Option<Link> {
326        self.get(id).copied()
327    }
328
329    fn exists(&mut self, id: u32) -> bool {
330        crate::transactions::TransactionsDecorator::exists(self, id)
331    }
332
333    fn update_observed(
334        &mut self,
335        id: u32,
336        source: u32,
337        target: u32,
338        observer: ChangeObserver<'_>,
339    ) -> Result<Link> {
340        Ok(crate::transactions::TransactionsDecorator::update_observed(
341            self, id, source, target, observer,
342        )?)
343    }
344
345    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
346        Ok(crate::transactions::TransactionsDecorator::delete_observed(
347            self, id, observer,
348        )?)
349    }
350
351    fn all_links(&mut self) -> Vec<Link> {
352        self.all().into_iter().copied().collect()
353    }
354
355    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
356        crate::transactions::TransactionsDecorator::search(self, source, target)
357    }
358
359    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
360        crate::transactions::TransactionsDecorator::get_or_create(self, source, target)
361            .expect("TransactionsDecorator::get_or_create failed in NamedTypeLinks bridge")
362    }
363
364    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
365        NamedTypes::get_name(self.inner_mut(), id)
366    }
367
368    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
369        NamedTypes::set_name(self.inner_mut(), id, name)
370    }
371
372    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
373        NamedTypes::get_by_name(self.inner_mut(), name)
374    }
375
376    fn remove_name(&mut self, id: u32) -> Result<()> {
377        NamedTypes::remove_name(self.inner_mut(), id)
378    }
379
380    fn save(&mut self) -> Result<()> {
381        Ok(crate::transactions::TransactionsDecorator::save(self)?)
382    }
383}
384
385impl NamedTypeLinks for crate::version_control::VersionControlDecorator {
386    fn create(&mut self, source: u32, target: u32) -> u32 {
387        crate::version_control::VersionControlDecorator::create(self, source, target)
388            .expect("VersionControlDecorator::create failed in NamedTypeLinks bridge")
389    }
390
391    fn ensure_created(&mut self, id: u32) -> u32 {
392        crate::version_control::VersionControlDecorator::ensure_created(self, id)
393    }
394
395    fn get_link(&mut self, id: u32) -> Option<Link> {
396        self.get(id).copied()
397    }
398
399    fn exists(&mut self, id: u32) -> bool {
400        crate::version_control::VersionControlDecorator::exists(self, id)
401    }
402
403    fn update_observed(
404        &mut self,
405        id: u32,
406        source: u32,
407        target: u32,
408        observer: ChangeObserver<'_>,
409    ) -> Result<Link> {
410        crate::version_control::VersionControlDecorator::update_observed(
411            self, id, source, target, observer,
412        )
413    }
414
415    fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
416        crate::version_control::VersionControlDecorator::delete_observed(self, id, observer)
417    }
418
419    fn all_links(&mut self) -> Vec<Link> {
420        self.all().into_iter().copied().collect()
421    }
422
423    fn search(&mut self, source: u32, target: u32) -> Option<u32> {
424        crate::version_control::VersionControlDecorator::search(self, source, target)
425    }
426
427    fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
428        crate::version_control::VersionControlDecorator::get_or_create(self, source, target)
429            .expect("VersionControlDecorator::get_or_create failed in NamedTypeLinks bridge")
430    }
431
432    fn get_name(&mut self, id: u32) -> Result<Option<String>> {
433        NamedTypes::get_name(self.transactions_mut().inner_mut(), id)
434    }
435
436    fn set_name(&mut self, id: u32, name: &str) -> Result<u32> {
437        NamedTypes::set_name(self.transactions_mut().inner_mut(), id, name)
438    }
439
440    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
441        NamedTypes::get_by_name(self.transactions_mut().inner_mut(), name)
442    }
443
444    fn remove_name(&mut self, id: u32) -> Result<()> {
445        NamedTypes::remove_name(self.transactions_mut().inner_mut(), id)
446    }
447
448    fn save(&mut self) -> Result<()> {
449        crate::version_control::VersionControlDecorator::save(self)
450    }
451}
452
453pub fn escape_lino_reference(reference: &str) -> String {
454    if reference.is_empty() || reference.trim().is_empty() {
455        return String::new();
456    }
457
458    let has_single_quote = reference.contains('\'');
459    let has_double_quote = reference.contains('"');
460    let needs_quoting = reference.contains(':')
461        || reference.contains('(')
462        || reference.contains(')')
463        || reference.contains(' ')
464        || reference.contains('\t')
465        || reference.contains('\n')
466        || reference.contains('\r')
467        || has_single_quote
468        || has_double_quote;
469
470    if has_single_quote && has_double_quote {
471        return format!("'{}'", reference.replace('\'', "\\'"));
472    }
473
474    if has_double_quote {
475        return format!("'{reference}'");
476    }
477
478    if has_single_quote {
479        return format!("\"{reference}\"");
480    }
481
482    if needs_quoting {
483        return format!("'{reference}'");
484    }
485
486    reference.to_string()
487}