Skip to main content

systemprompt_models/profile/
paths.rs

1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
5#[serde(deny_unknown_fields)]
6pub struct PathsConfig {
7    pub system: String,
8    pub services: String,
9    pub bin: String,
10
11    #[serde(default)]
12    pub web_path: Option<String>,
13
14    #[serde(default)]
15    pub storage: Option<String>,
16
17    #[serde(default)]
18    pub geoip_database: Option<String>,
19}
20
21impl PathsConfig {
22    pub fn resolve_relative_to(&mut self, base: &Path) {
23        self.system = resolve_path(base, &self.system);
24        self.services = resolve_path(base, &self.services);
25        self.bin = resolve_path(base, &self.bin);
26        self.storage = self.storage.as_ref().map(|p| resolve_path(base, p));
27        self.geoip_database = self.geoip_database.as_ref().map(|p| resolve_path(base, p));
28        self.web_path = self.web_path.as_ref().map(|p| resolve_path(base, p));
29    }
30
31    pub fn skills(&self) -> String {
32        format!("{}/skills", self.services)
33    }
34
35    pub fn config(&self) -> String {
36        format!("{}/config/config.yaml", self.services)
37    }
38
39    pub fn ai_config(&self) -> String {
40        format!("{}/ai/config.yaml", self.services)
41    }
42
43    pub fn content_config(&self) -> String {
44        format!("{}/content/config.yaml", self.services)
45    }
46
47    pub fn web_config(&self) -> String {
48        format!("{}/web/config.yaml", self.services)
49    }
50
51    pub fn web_metadata(&self) -> String {
52        format!("{}/web/metadata.yaml", self.services)
53    }
54
55    pub fn plugins(&self) -> String {
56        format!("{}/plugins", self.services)
57    }
58
59    pub fn marketplaces(&self) -> String {
60        format!("{}/marketplaces", self.services)
61    }
62
63    pub fn hooks(&self) -> String {
64        format!("{}/hooks", self.services)
65    }
66
67    pub fn agents(&self) -> String {
68        format!("{}/agents", self.services)
69    }
70
71    pub fn logs(&self) -> String {
72        format!("{}/logs", self.system)
73    }
74
75    pub fn web_path_resolved(&self) -> String {
76        self.web_path
77            .clone()
78            .unwrap_or_else(|| format!("{}/web", self.system))
79    }
80
81    pub fn storage_resolved(&self) -> Option<&str> {
82        self.storage.as_deref()
83    }
84
85    pub fn geoip_database_resolved(&self) -> Option<&str> {
86        self.geoip_database.as_deref()
87    }
88}
89
90pub fn resolve_path(base: &Path, path: &str) -> String {
91    let p = Path::new(path);
92    if p.is_absolute() {
93        path.to_owned()
94    } else {
95        let resolved = base.join(p);
96        resolved.canonicalize().map_or_else(
97            |_| resolved.to_string_lossy().to_string(),
98            |canonical| canonical.to_string_lossy().to_string(),
99        )
100    }
101}
102
103pub fn expand_home(path_str: &str) -> PathBuf {
104    path_str.strip_prefix("~/").map_or_else(
105        || PathBuf::from(path_str),
106        |stripped| {
107            let home = std::env::var("HOME")
108                .or_else(|_| std::env::var("USERPROFILE"))
109                .unwrap_or_else(|_| {
110                    tracing::warn!(
111                        path = %path_str,
112                        "Cannot expand ~/ path: neither HOME nor USERPROFILE is set"
113                    );
114                    String::new()
115                });
116            PathBuf::from(home).join(stripped)
117        },
118    )
119}
120
121pub fn resolve_with_home(base: &Path, path_str: &str) -> PathBuf {
122    let path = expand_home(path_str);
123
124    if path.is_absolute() {
125        path
126    } else {
127        base.join(path)
128    }
129}