Skip to main content

hk_parser/
resolve.rs

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
13/// Resolves interpolations in the config, including env vars and references.
14pub 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                    // Resolve the reference recursively, detecting cycles
65                    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    // Check if the reference is already in the resolving stack (cycle)
96    if resolving.contains(&path.to_string()) {
97        return Err(HkError::CyclicReference(path.to_string()));
98    }
99
100    // Get the raw value from the config
101    let raw_value = get_value_by_path(path, top).ok_or_else(|| HkError::InvalidReference(path.to_string()))?;
102    // Clone the value so we can resolve it without affecting the original
103    let mut cloned_value = raw_value.clone();
104
105    // Push the path onto the resolving stack
106    resolving.push(path.to_string());
107
108    // Resolve the cloned value recursively
109    resolve_value(&mut cloned_value, top, resolved, resolving, path)?;
110
111    // Pop the path from the stack
112    resolving.pop();
113
114    // Convert the resolved value to a string
115    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}