1mod expander;
8mod resolver;
9
10pub mod presets;
11
12pub use expander::{ExpansionMode, ExpansionResult, InheritanceChain, VarExpander};
13pub use resolver::{VarReference, VarResolver};
14
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::fs::File;
18use std::io::{BufReader, BufWriter};
19use std::path::Path;
20
21use crate::error::Result;
22
23fn default_vars_schema() -> String {
24 "https://acp-protocol.dev/schemas/v1/vars.schema.json".to_string()
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct VarsFile {
30 #[serde(rename = "$schema", default = "default_vars_schema")]
32 pub schema: String,
33 pub version: String,
35 pub variables: HashMap<String, VarEntry>,
37}
38
39impl VarsFile {
40 pub fn new() -> Self {
42 Self {
43 schema: default_vars_schema(),
44 version: crate::VERSION.to_string(),
45 variables: HashMap::new(),
46 }
47 }
48
49 pub fn from_json<P: AsRef<Path>>(path: P) -> Result<Self> {
51 let file = File::open(path)?;
52 let reader = BufReader::new(file);
53 Ok(serde_json::from_reader(reader)?)
54 }
55
56 pub fn write_json<P: AsRef<Path>>(&self, path: P) -> Result<()> {
58 let file = File::create(path)?;
59 let writer = BufWriter::new(file);
60 serde_json::to_writer_pretty(writer, self)?;
61 Ok(())
62 }
63
64 pub fn add_variable(&mut self, name: String, entry: VarEntry) {
66 self.variables.insert(name, entry);
67 }
68}
69
70impl Default for VarsFile {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct VarEntry {
79 #[serde(rename = "type")]
81 pub var_type: VarType,
82 pub value: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub description: Option<String>,
87 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 pub refs: Vec<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub source: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub lines: Option<[usize; 2]>,
96}
97
98impl VarEntry {
99 pub fn symbol(value: impl Into<String>, description: Option<String>) -> Self {
101 Self {
102 var_type: VarType::Symbol,
103 value: value.into(),
104 description,
105 refs: vec![],
106 source: None,
107 lines: None,
108 }
109 }
110
111 pub fn symbol_with_source(
113 value: impl Into<String>,
114 description: Option<String>,
115 source: String,
116 lines: [usize; 2],
117 ) -> Self {
118 Self {
119 var_type: VarType::Symbol,
120 value: value.into(),
121 description,
122 refs: vec![],
123 source: Some(source),
124 lines: Some(lines),
125 }
126 }
127
128 pub fn symbol_with_refs(
130 value: impl Into<String>,
131 description: Option<String>,
132 refs: Vec<String>,
133 ) -> Self {
134 Self {
135 var_type: VarType::Symbol,
136 value: value.into(),
137 description,
138 refs,
139 source: None,
140 lines: None,
141 }
142 }
143
144 pub fn file(value: impl Into<String>, description: Option<String>) -> Self {
146 Self {
147 var_type: VarType::File,
148 value: value.into(),
149 description,
150 refs: vec![],
151 source: None,
152 lines: None,
153 }
154 }
155
156 pub fn domain(value: impl Into<String>, description: Option<String>) -> Self {
158 Self {
159 var_type: VarType::Domain,
160 value: value.into(),
161 description,
162 refs: vec![],
163 source: None,
164 lines: None,
165 }
166 }
167
168 pub fn layer(value: impl Into<String>, description: Option<String>) -> Self {
170 Self {
171 var_type: VarType::Layer,
172 value: value.into(),
173 description,
174 refs: vec![],
175 source: None,
176 lines: None,
177 }
178 }
179
180 pub fn pattern(value: impl Into<String>, description: Option<String>) -> Self {
182 Self {
183 var_type: VarType::Pattern,
184 value: value.into(),
185 description,
186 refs: vec![],
187 source: None,
188 lines: None,
189 }
190 }
191
192 pub fn context(value: impl Into<String>, description: Option<String>) -> Self {
194 Self {
195 var_type: VarType::Context,
196 value: value.into(),
197 description,
198 refs: vec![],
199 source: None,
200 lines: None,
201 }
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207#[serde(rename_all = "lowercase")]
208pub enum VarType {
209 Symbol,
210 File,
211 Domain,
212 Layer,
213 Pattern,
214 Context,
215}
216
217impl std::fmt::Display for VarType {
218 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219 let s = match self {
220 Self::Symbol => "symbol",
221 Self::File => "file",
222 Self::Domain => "domain",
223 Self::Layer => "layer",
224 Self::Pattern => "pattern",
225 Self::Context => "context",
226 };
227 write!(f, "{}", s)
228 }
229}
230
231pub fn estimate_tokens(text: &str) -> usize {
233 text.len().div_ceil(4)
234}
235
236pub fn capitalize(s: &str) -> String {
238 let mut c = s.chars();
239 match c.next() {
240 None => String::new(),
241 Some(f) => f.to_uppercase().chain(c).collect(),
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_find_references() {
251 let vars_file = VarsFile {
252 schema: default_vars_schema(),
253 version: "1.0.0".to_string(),
254 variables: HashMap::new(),
255 };
256 let resolver = VarResolver::new(vars_file);
257
258 let refs = resolver.find_references("Check $SYM_TEST and $ARCH_FLOW.value");
259 assert_eq!(refs.len(), 2);
260 assert_eq!(refs[0].name, "SYM_TEST");
261 assert_eq!(refs[1].name, "ARCH_FLOW");
262 assert_eq!(refs[1].modifier, Some("value".to_string()));
263 }
264
265 #[test]
266 fn test_vars_roundtrip() {
267 let mut vars_file = VarsFile::new();
268 vars_file.add_variable(
269 "SYM_TEST".to_string(),
270 VarEntry::symbol("test.rs:test_fn", Some("Test function".to_string())),
271 );
272
273 let json = serde_json::to_string_pretty(&vars_file).unwrap();
274 let parsed: VarsFile = serde_json::from_str(&json).unwrap();
275
276 assert!(parsed.variables.contains_key("SYM_TEST"));
277 assert_eq!(parsed.variables["SYM_TEST"].var_type, VarType::Symbol);
278 }
279}