rebecca_core/
path_template.rs1use 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}