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