1use crate::error::HkError;
2use crate::value::{HkConfig, HkValue};
3use indexmap::IndexMap;
4use lazy_static::lazy_static;
5use regex::Regex;
6use std::collections::HashSet;
7use std::env;
8
9lazy_static! {
10 static ref INTERPOL_RE: Regex = Regex::new(r"\$\{([^}]+)\}").unwrap();
11}
12
13pub fn resolve_interpolations(config: &mut HkConfig) -> Result<(), HkError> {
15 let context = config.clone();
16 let mut resolved = HashSet::new();
17 let mut resolving = Vec::new();
18 for (section, value) in config.iter_mut() {
19 if let HkValue::Map(map) = value {
20 resolve_map(map, &context, &mut resolved, &mut resolving, &format!("{}", section))?;
21 }
22 }
23 Ok(())
24}
25
26fn resolve_map(
27 map: &mut IndexMap<String, HkValue>,
28 top: &HkConfig,
29 resolved: &mut HashSet<String>,
30 resolving: &mut Vec<String>,
31 path: &str,
32) -> Result<(), HkError> {
33 for (key, v) in map.iter_mut() {
34 let new_path = format!("{}.{}", path, key);
35 if resolved.contains(&new_path) {
36 continue;
37 }
38 resolving.push(new_path.clone());
39 resolve_value(v, top, resolved, resolving, &new_path)?;
40 resolving.pop();
41 resolved.insert(new_path);
42 }
43 Ok(())
44}
45
46fn resolve_value(
47 v: &mut HkValue,
48 top: &HkConfig,
49 resolved: &mut HashSet<String>,
50 resolving: &mut Vec<String>,
51 path: &str,
52) -> Result<(), HkError> {
53 match v {
54 HkValue::String(s) => {
55 let mut new_s = String::new();
56 let mut last = 0;
57 for cap in INTERPOL_RE.captures_iter(s) {
58 let m = cap.get(0).unwrap();
59 new_s.push_str(&s[last..m.start()]);
60 let var = &cap[1];
61 let repl = if var.starts_with("env:") {
62 env::var(&var[4..]).unwrap_or_default()
63 } else {
64 if resolving.contains(&var.to_string()) {
66 return Err(HkError::CyclicReference(var.to_string()));
67 }
68 resolve_reference(var, top, resolved, resolving)?
69 };
70 new_s.push_str(&repl);
71 last = m.end();
72 }
73 new_s.push_str(&s[last..]);
74 *s = new_s;
75 }
76 HkValue::Array(a) => {
77 for (i, item) in a.iter_mut().enumerate() {
78 resolve_value(item, top, resolved, resolving, &format!("{}[{}]", path, i))?;
79 }
80 }
81 HkValue::Map(m) => {
82 resolve_map(m, top, resolved, resolving, path)?;
83 }
84 _ => {}
85 }
86 Ok(())
87}
88
89fn resolve_reference(
90 path: &str,
91 top: &HkConfig,
92 resolved: &mut HashSet<String>,
93 resolving: &mut Vec<String>,
94) -> Result<String, HkError> {
95 if resolving.contains(&path.to_string()) {
97 return Err(HkError::CyclicReference(path.to_string()));
98 }
99
100 let raw_value = get_value_by_path(path, top).ok_or_else(|| HkError::InvalidReference(path.to_string()))?;
102 let mut cloned_value = raw_value.clone();
104
105 resolving.push(path.to_string());
107
108 resolve_value(&mut cloned_value, top, resolved, resolving, path)?;
110
111 resolving.pop();
113
114 cloned_value.as_string()
116}
117
118fn get_value_by_path<'a>(path: &str, config: &'a HkConfig) -> Option<&'a HkValue> {
119 let bracket_re = Regex::new(r"([^\[\].]+)(?:\[(\d+)\])?").unwrap();
120 let mut parts = Vec::new();
121 for cap in bracket_re.captures_iter(path) {
122 let key = cap.get(1).map(|m| m.as_str()).unwrap();
123 let idx = cap.get(2).map(|m| m.as_str().parse::<usize>().ok());
124 parts.push((key, idx.flatten()));
125 }
126
127 if parts.is_empty() {
128 return None;
129 }
130
131 let (first_key, _) = parts[0];
132 let mut current_value: Option<&'a HkValue> = config.get(first_key);
133 for (key, idx) in parts.iter().skip(1) {
134 match current_value {
135 Some(HkValue::Map(map)) => {
136 current_value = map.get(*key);
137 }
138 Some(HkValue::Array(arr)) if idx.is_some() => {
139 if let Some(i) = idx {
140 if *i < arr.len() {
141 current_value = Some(&arr[*i]);
142 continue;
143 } else {
144 return None;
145 }
146 } else {
147 return None;
148 }
149 }
150 _ => return None,
151 }
152 if let Some(idx) = idx {
153 if let Some(HkValue::Array(arr)) = current_value {
154 if *idx < arr.len() {
155 current_value = Some(&arr[*idx]);
156 } else {
157 return None;
158 }
159 } else {
160 return None;
161 }
162 }
163 }
164 current_value
165}