1pub(crate) mod package_wizard;
4pub(crate) mod prompts;
5pub(crate) mod render;
6pub(crate) mod timestamp;
7pub(crate) mod unpack;
8
9use std::{
10 path::{Path, PathBuf},
11 str::FromStr,
12};
13
14use anyhow::{Context as _, Result, bail};
15use once_cell::sync::Lazy;
16use regex::Regex;
17use wasmer_wasix::runners::MappedDirectory;
18
19fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<MappedDirectory> {
20 let pb = PathBuf::from(&real_dir).canonicalize()?;
21 if let Ok(pb_metadata) = pb.metadata() {
22 if !pb_metadata.is_dir() {
23 bail!("\"{}\" exists, but it is not a directory", &real_dir);
24 }
25 } else {
26 bail!("Directory \"{}\" does not exist", &real_dir);
27 }
28 Ok(MappedDirectory {
29 guest: alias.to_string(),
30 host: pb,
31 })
32}
33
34pub fn parse_mapdir(entry: &str) -> Result<MappedDirectory> {
36 if let [alias, real_dir] = entry.split("::").collect::<Vec<&str>>()[..] {
38 retrieve_alias_pathbuf(alias, real_dir)
39 }
40 else if let [alias, real_dir] = entry.splitn(2, ':').collect::<Vec<&str>>()[..] {
42 retrieve_alias_pathbuf(alias, real_dir)
43 } else {
44 bail!(
45 "Directory mappings must consist of two paths separate by a `::` or `:`. Found {}",
46 &entry
47 )
48 }
49}
50
51pub fn parse_envvar(entry: &str) -> Result<(String, String)> {
53 let entry = entry.trim();
54
55 match entry.find('=') {
56 None => bail!(
57 "Environment variable must be of the form `<name>=<value>`; found `{}`",
58 &entry
59 ),
60
61 Some(0) => bail!(
62 "Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `{}`",
63 &entry
64 ),
65
66 Some(position) if position == entry.len() - 1 => bail!(
67 "Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `{}`",
68 &entry
69 ),
70
71 Some(position) => Ok((entry[..position].into(), entry[position + 1..].into())),
72 }
73}
74
75pub(crate) const DEFAULT_PACKAGE_MANIFEST_FILE: &str = "wasmer.toml";
76
77pub fn load_package_manifest(
81 path: &Path,
82) -> Result<Option<(PathBuf, wasmer_config::package::Manifest)>, anyhow::Error> {
83 let file_path = if path.is_file() {
84 path.to_owned()
85 } else {
86 path.join(DEFAULT_PACKAGE_MANIFEST_FILE)
87 };
88
89 let contents = match std::fs::read_to_string(&file_path) {
90 Ok(c) => c,
91 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
92 Err(err) => {
93 return Err(err).with_context(|| {
94 format!(
95 "Could not read package manifest at '{}'",
96 file_path.display()
97 )
98 });
99 }
100 };
101
102 let manifest = wasmer_config::package::Manifest::parse(&contents).with_context(|| {
103 format!(
104 "Could not parse package config at: '{}' - full config: {}",
105 file_path.display(),
106 contents
107 )
108 })?;
109
110 Ok(Some((file_path, manifest)))
111}
112
113#[derive(Debug, Clone, PartialEq)]
116pub(crate) struct Identifier {
117 pub name: String,
119 pub owner: Option<String>,
121 pub version: Option<String>,
123}
124
125impl Identifier {
126 pub fn new(name: impl Into<String>) -> Self {
127 Identifier {
128 name: name.into(),
129 owner: None,
130 version: None,
131 }
132 }
133
134 pub fn with_owner(self, owner: impl Into<String>) -> Self {
135 Identifier {
136 owner: Some(owner.into()),
137 ..self
138 }
139 }
140
141 pub fn with_version(self, version: impl Into<String>) -> Self {
142 Identifier {
143 version: Some(version.into()),
144 ..self
145 }
146 }
147}
148
149impl FromStr for Identifier {
150 type Err = anyhow::Error;
151
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 const PATTERN: &str = r"^(?x)
154 (?:
155 (?P<owner>[a-zA-Z][\w\d_.-]*)
156 /
157 )?
158 (?P<name>[a-zA-Z][\w\d_.-]*)
159 (?:
160 @
161 (?P<version>[\w\d.]+)
162 )?
163 $
164 ";
165 static RE: Lazy<Regex> = Lazy::new(|| Regex::new(PATTERN).unwrap());
166
167 let caps = RE.captures(s).context(
168 "Invalid package identifier, expected something like namespace/package@version",
169 )?;
170
171 let mut identifier = Identifier::new(&caps["name"]);
172
173 if let Some(owner) = caps.name("owner") {
174 identifier = identifier.with_owner(owner.as_str());
175 }
176
177 if let Some(version) = caps.name("version") {
178 identifier = identifier.with_version(version.as_str());
179 }
180
181 Ok(identifier)
182 }
183}
184
185pub(crate) fn merge_yaml_values(a: &serde_yaml::Value, b: &serde_yaml::Value) -> serde_yaml::Value {
189 use serde_yaml::Value as V;
190 match (a, b) {
191 (V::Mapping(a), V::Mapping(b)) => {
192 let mut m = a.clone();
193 for (k, v) in b.iter() {
194 let newval = if let Some(old) = a.get(k) {
195 merge_yaml_values(old, v)
196 } else {
197 v.clone()
198 };
199 m.insert(k.clone(), newval);
200 }
201 V::Mapping(m)
202 }
203 _ => b.clone(),
204 }
205}
206
207#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_merge_yaml_values() {
225 use serde_yaml::Value;
226 let v1 = r#"
227a: a
228b:
229 b1: b1
230c: c
231 "#;
232 let v2 = r#"
233a: a1
234b:
235 b2: b2
236 "#;
237 let v3 = r#"
238a: a1
239b:
240 b1: b1
241 b2: b2
242c: c
243 "#;
244
245 let a: Value = serde_yaml::from_str(v1).unwrap();
246 let b: Value = serde_yaml::from_str(v2).unwrap();
247 let c: Value = serde_yaml::from_str(v3).unwrap();
248 let merged = merge_yaml_values(&a, &b);
249 assert_eq!(merged, c);
250 }
251
252 #[test]
253 fn parse_valid_identifiers() {
254 let inputs = [
255 ("python", Identifier::new("python")),
256 (
257 "syrusakbary/python",
258 Identifier::new("python").with_owner("syrusakbary"),
259 ),
260 (
261 "wasmer/wasmer.io",
262 Identifier::new("wasmer.io").with_owner("wasmer"),
263 ),
264 (
265 "syrusakbary/python@1.2.3",
266 Identifier::new("python")
267 .with_owner("syrusakbary")
268 .with_version("1.2.3"),
269 ),
270 (
271 "python@1.2.3",
272 Identifier::new("python").with_version("1.2.3"),
273 ),
274 ];
275
276 for (src, expected) in inputs {
277 let identifier = Identifier::from_str(src).expect(src);
278 assert_eq!(identifier, expected);
279 }
280 }
281
282 #[test]
283 fn invalid_package_identifiers() {
284 let inputs = ["", "$", "python/", "/python", "python@", "."];
285
286 for input in inputs {
287 let result = Identifier::from_str(input);
288 assert!(result.is_err(), "Got {result:?} from {input:?}");
289 }
290 }
291
292 #[test]
293 fn test_parse_envvar() {
294 assert_eq!(
295 parse_envvar("A").unwrap_err().to_string(),
296 "Environment variable must be of the form `<name>=<value>`; found `A`"
297 );
298 assert_eq!(
299 parse_envvar("=A").unwrap_err().to_string(),
300 "Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `=A`"
301 );
302 assert_eq!(
303 parse_envvar("A=").unwrap_err().to_string(),
304 "Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `A=`"
305 );
306 assert_eq!(parse_envvar("A=B").unwrap(), ("A".into(), "B".into()));
307 assert_eq!(parse_envvar(" A=B\t").unwrap(), ("A".into(), "B".into()));
308 assert_eq!(
309 parse_envvar("A=B=C=D").unwrap(),
310 ("A".into(), "B=C=D".into())
311 );
312 }
313}