link_cli/
lino_database_input.rs1use anyhow::{bail, Context, Result};
4use std::fs;
5use std::path::Path;
6
7use crate::lino_link::LinoLink;
8use crate::named_type_links::NamedTypeLinks;
9use crate::parser::Parser;
10
11pub fn import_lino_file<T, P>(storage: &mut T, path: P) -> Result<()>
12where
13 T: NamedTypeLinks,
14 P: AsRef<Path>,
15{
16 let path = path.as_ref();
17 let text = fs::read_to_string(path)
18 .with_context(|| format!("Failed to read LiNo input: {}", path.display()))?;
19 import_lino_text(storage, &text)?;
20 storage.save()?;
21 Ok(())
22}
23
24pub fn import_lino_text<T>(storage: &mut T, links_notation: &str) -> Result<()>
25where
26 T: NamedTypeLinks,
27{
28 let parser = Parser::new();
29 let normalized_links_notation = normalize_links_notation(links_notation);
30 let links = parser.parse(&normalized_links_notation)?;
31
32 for link in links.iter().filter(|link| !link.is_empty()) {
33 import_link(storage, link, false)?;
34 }
35
36 Ok(())
37}
38
39fn import_link<T>(storage: &mut T, link: &LinoLink, allow_anonymous_index: bool) -> Result<u32>
40where
41 T: NamedTypeLinks,
42{
43 let values = link
44 .values
45 .as_ref()
46 .filter(|values| values.len() == 2)
47 .ok_or_else(|| anyhow::anyhow!("LiNo import supports links with exactly two values"))?;
48
49 let source = resolve_reference(storage, &values[0])?;
50 let target = resolve_reference(storage, &values[1])?;
51 let identifier = normalize_identifier(link.id.as_deref());
52
53 if identifier.is_empty() {
54 if !allow_anonymous_index {
55 bail!("Top-level LiNo import links must have an index or name");
56 }
57
58 return Ok(storage.get_or_create(source, target));
59 }
60
61 let index = resolve_index(storage, &identifier)?;
62 update_link(storage, index, source, target)?;
63 Ok(index)
64}
65
66fn resolve_reference<T>(storage: &mut T, reference: &LinoLink) -> Result<u32>
67where
68 T: NamedTypeLinks,
69{
70 if reference.values_count() == 2 {
71 return import_link(storage, reference, true);
72 }
73
74 if reference.values_count() > 0 {
75 bail!(
76 "LiNo import references must be scalar values or nested links with exactly two values"
77 );
78 }
79
80 let identifier = normalize_identifier(reference.id.as_deref());
81 if identifier.is_empty() {
82 bail!("LiNo import references must have a value");
83 }
84
85 if let Some(id) = parse_reference_number(&identifier, true) {
86 return Ok(id);
87 }
88
89 storage.get_or_create_named(&identifier)
90}
91
92fn resolve_index<T>(storage: &mut T, identifier: &str) -> Result<u32>
93where
94 T: NamedTypeLinks,
95{
96 if let Some(id) = parse_reference_number(identifier, false) {
97 if !storage.exists(id) {
98 storage.ensure_created(id);
99 }
100
101 return Ok(id);
102 }
103
104 storage.get_or_create_named(identifier)
105}
106
107fn update_link<T>(storage: &mut T, index: u32, source: u32, target: u32) -> Result<()>
108where
109 T: NamedTypeLinks,
110{
111 if !storage.exists(index) {
112 storage.ensure_created(index);
113 }
114
115 if let Some(current) = storage.get_link(index) {
116 if current.source == source && current.target == target {
117 return Ok(());
118 }
119 }
120
121 storage.update(index, source, target)?;
122 Ok(())
123}
124
125fn parse_reference_number(identifier: &str, allow_zero: bool) -> Option<u32> {
126 let id = identifier.parse::<u32>().ok()?;
127 if id == 0 && !allow_zero {
128 return None;
129 }
130
131 Some(id)
132}
133
134fn normalize_identifier(identifier: Option<&str>) -> String {
135 let normalized = identifier
136 .unwrap_or_default()
137 .trim()
138 .trim_end_matches(':')
139 .to_string();
140
141 if normalized.len() >= 2 && normalized.starts_with('\'') && normalized.ends_with('\'') {
142 return normalized[1..normalized.len() - 1].replace("\\'", "'");
143 }
144
145 if normalized.len() >= 2 && normalized.starts_with('"') && normalized.ends_with('"') {
146 return normalized[1..normalized.len() - 1].replace("\\\"", "\"");
147 }
148
149 normalized
150}
151
152fn normalize_links_notation(links_notation: &str) -> String {
153 links_notation
154 .lines()
155 .map(str::trim)
156 .filter(|line| !line.is_empty())
157 .collect::<Vec<_>>()
158 .join("\n")
159}