trident/config/target/
os.rs1use super::*;
2
3#[derive(Clone, Debug)]
11pub struct UnionConfig {
12 pub name: String,
14 pub display_name: String,
16 pub vm: String,
18 pub binding_prefix: String,
20 pub account_model: String,
22 pub storage_model: String,
24 pub transaction_model: String,
26}
27
28impl UnionConfig {
29 pub fn resolve(name: &str) -> Result<Option<Self>, Diagnostic> {
36 if name.contains('/') || name.contains('\\') || name.contains("..") || name.starts_with('.')
38 {
39 return Ok(None);
40 }
41
42 let target_path = format!("os/{}/target.toml", name);
43
44 if let Ok(exe) = std::env::current_exe() {
46 if let Some(dir) = exe.parent() {
47 for ancestor in &[
48 Some(dir.to_path_buf()),
49 dir.parent().map(|p| p.to_path_buf()),
50 dir.parent()
51 .and_then(|p| p.parent())
52 .map(|p| p.to_path_buf()),
53 ] {
54 if let Some(base) = ancestor {
55 let path = base.join(&target_path);
56 if path.exists() {
57 return Self::load(&path).map(Some);
58 }
59 }
60 }
61 }
62 }
63
64 let cwd_path = std::path::PathBuf::from(&target_path);
66 if cwd_path.exists() {
67 return Self::load(&cwd_path).map(Some);
68 }
69
70 Ok(None)
71 }
72
73 pub fn load(path: &Path) -> Result<Self, Diagnostic> {
75 let content = std::fs::read_to_string(path).map_err(|e| {
76 Diagnostic::error(
77 format!("cannot read OS config '{}': {}", path.display(), e),
78 Span::dummy(),
79 )
80 })?;
81 Self::parse_toml(&content, path)
82 }
83
84 fn parse_toml(content: &str, path: &Path) -> Result<Self, Diagnostic> {
85 let err =
86 |msg: String| Diagnostic::error(format!("{}: {}", path.display(), msg), Span::dummy());
87
88 let mut name = String::new();
89 let mut display_name = String::new();
90 let mut vm = String::new();
91 let mut binding_prefix = String::new();
92 let mut account_model = String::new();
93 let mut storage_model = String::new();
94 let mut transaction_model = String::new();
95
96 let mut section = String::new();
97
98 for line in content.lines() {
99 let trimmed = line.trim();
100 if trimmed.is_empty() || trimmed.starts_with('#') {
101 continue;
102 }
103 if trimmed.starts_with('[') && trimmed.ends_with(']') {
104 section = trimmed[1..trimmed.len() - 1].trim().to_string();
105 continue;
106 }
107 if let Some((key, value)) = trimmed.split_once('=') {
108 let key = key.trim();
109 let unquoted = value.trim().trim_matches('"');
110
111 match (section.as_str(), key) {
112 ("os", "name") => name = unquoted.to_string(),
113 ("os", "display_name") => display_name = unquoted.to_string(),
114 ("os", "vm") => vm = unquoted.to_string(),
115 ("runtime", "binding_prefix") => binding_prefix = unquoted.to_string(),
116 ("runtime", "account_model") => account_model = unquoted.to_string(),
117 ("runtime", "storage_model") => storage_model = unquoted.to_string(),
118 ("runtime", "transaction_model") => transaction_model = unquoted.to_string(),
119 _ => {}
120 }
121 }
122 }
123
124 if name.is_empty() {
125 return Err(err("missing os.name".to_string()));
126 }
127 if vm.is_empty() {
128 return Err(err("missing os.vm".to_string()));
129 }
130
131 Ok(Self {
132 name,
133 display_name,
134 vm,
135 binding_prefix,
136 account_model,
137 storage_model,
138 transaction_model,
139 })
140 }
141}
142
143#[derive(Clone, Debug)]
151pub struct ResolvedTarget {
152 pub vm: TerrainConfig,
154 pub os: Option<UnionConfig>,
156 pub state: Option<StateConfig>,
158}
159
160impl ResolvedTarget {
161 pub fn resolve(name: &str) -> Result<Self, Diagnostic> {
168 if let Some(os_config) = UnionConfig::resolve(name)? {
170 let vm = TerrainConfig::resolve(&os_config.vm)?;
171 return Ok(ResolvedTarget {
172 vm,
173 os: Some(os_config),
174 state: None,
175 });
176 }
177
178 let vm = TerrainConfig::resolve(name)?;
180 Ok(ResolvedTarget {
181 vm,
182 os: None,
183 state: None,
184 })
185 }
186
187 pub fn resolve_with_state(target: &str, state_name: Option<&str>) -> Result<Self, Diagnostic> {
193 let mut resolved = Self::resolve(target)?;
194 if let Some(state) = state_name {
195 let union = resolved
196 .os
197 .as_ref()
198 .map(|os| os.name.as_str())
199 .ok_or_else(|| {
200 Diagnostic::error(
201 format!(
202 "--state requires a union target, not bare terrain '{}'",
203 target
204 ),
205 Span::dummy(),
206 )
207 })?;
208 resolved.state = StateConfig::resolve(union, state)?;
209 if resolved.state.is_none() {
210 return Err(Diagnostic::error(
211 format!("unknown state '{}' for union '{}'", state, union),
212 Span::dummy(),
213 )
214 .with_help(format!(
215 "available states: {}",
216 StateConfig::list_states(union).join(", ")
217 )));
218 }
219 }
220 Ok(resolved)
221 }
222}
223
224pub fn parse_string_array(s: &str) -> Vec<String> {
226 let s = s.trim();
227 if !s.starts_with('[') || !s.ends_with(']') {
228 return Vec::new();
229 }
230 let inner = &s[1..s.len() - 1];
231 inner
232 .split(',')
233 .map(|part| part.trim().trim_matches('"').to_string())
234 .filter(|s| !s.is_empty())
235 .collect()
236}