Skip to main content

rebecca_core/
path_template.rs

1use std::ffi::OsStr;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::environment::Environment;
7use crate::error::{RebeccaError, Result};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct PathTemplate(pub String);
11
12impl PathTemplate {
13    pub fn new(raw: impl Into<String>) -> Self {
14        Self(raw.into())
15    }
16
17    pub fn raw(&self) -> &str {
18        &self.0
19    }
20}
21
22impl From<&str> for PathTemplate {
23    fn from(value: &str) -> Self {
24        Self::new(value)
25    }
26}
27
28impl From<String> for PathTemplate {
29    fn from(value: String) -> Self {
30        Self::new(value)
31    }
32}
33
34pub fn expand_template(template: &PathTemplate, env: &impl Environment) -> Result<Option<PathBuf>> {
35    let mut expanded = String::new();
36    let mut chars = template.raw().chars().peekable();
37
38    while let Some(ch) = chars.next() {
39        if ch != '%' {
40            expanded.push(ch);
41            continue;
42        }
43
44        let mut key = String::new();
45        let mut closed = false;
46
47        for next in chars.by_ref() {
48            if next == '%' {
49                closed = true;
50                break;
51            }
52            key.push(next);
53        }
54
55        if !closed {
56            return Err(RebeccaError::PathExpansionFailed(format!(
57                "unterminated variable in template {template:?}"
58            )));
59        }
60
61        if key.is_empty() {
62            return Err(RebeccaError::PathExpansionFailed(format!(
63                "empty variable name in template {template:?}"
64            )));
65        }
66
67        let value = match env.get(&key) {
68            Some(value) => value,
69            None => return Ok(None),
70        };
71
72        expanded.push_str(&os_str_to_lossy(&value));
73    }
74
75    Ok(Some(PathBuf::from(normalize_template_separators(
76        &expanded,
77    ))))
78}
79
80fn os_str_to_lossy(value: &OsStr) -> String {
81    value.to_string_lossy().into_owned()
82}
83
84fn normalize_template_separators(value: &str) -> String {
85    if std::path::MAIN_SEPARATOR == '\\' {
86        value.replace('/', "\\")
87    } else {
88        value.replace('\\', "/")
89    }
90}