wasmer_cli/utils/
mod.rs

1//! Utility functions for the WebAssembly module
2
3pub(crate) mod package_wizard;
4pub(crate) mod prompts;
5pub(crate) mod render;
6pub(crate) mod timestamp;
7pub(crate) mod unpack;
8
9use std::path::{Path, PathBuf};
10
11use anyhow::{Context as _, Result, bail};
12use itertools::Itertools;
13use wasmer_wasix::runners::MappedDirectory;
14
15fn retrieve_alias_pathbuf(host_dir: &str, guest_dir: &str) -> Result<MappedDirectory> {
16    let host_dir_path = PathBuf::from(&host_dir).canonicalize()?;
17    if let Ok(pb_metadata) = host_dir_path.metadata() {
18        if !pb_metadata.is_dir() {
19            bail!("\"{}\" exists, but it is not a directory", &host_dir);
20        }
21    } else {
22        bail!("Directory \"{}\" does not exist", &host_dir);
23    }
24    Ok(MappedDirectory {
25        host: host_dir_path,
26        guest: guest_dir.to_string(),
27    })
28}
29
30/// Parses a volume from a string
31pub fn parse_volume(entry: &str) -> Result<MappedDirectory> {
32    let components = entry.split(":").collect_vec();
33    match components.as_slice() {
34        [entry] => retrieve_alias_pathbuf(entry, entry),
35        [host_dir, guest_dir] => retrieve_alias_pathbuf(host_dir, guest_dir),
36        _ => bail!(
37            "Directory mappings must consist of a single path, or two paths separate by a `:`. Found {}",
38            &entry
39        ),
40    }
41}
42
43/// Parses a mapdir(legacy option) from a string.
44pub fn parse_mapdir(entry: &str) -> Result<MappedDirectory> {
45    let components = entry.split(":").collect_vec();
46    match components.as_slice() {
47        [entry] => retrieve_alias_pathbuf(entry, entry),
48        // swapper order compared to the --volume option
49        [guest_dir, host_dir] => retrieve_alias_pathbuf(host_dir, guest_dir),
50        _ => bail!(
51            "Directory mappings must consist of a single path, or two paths separate by a `:`. Found {}",
52            &entry
53        ),
54    }
55}
56
57/// Parses an environment variable.
58pub fn parse_envvar(entry: &str) -> Result<(String, String)> {
59    let entry = entry.trim();
60
61    match entry.find('=') {
62        None => bail!(
63            "Environment variable must be of the form `<name>=<value>`; found `{}`",
64            &entry
65        ),
66
67        Some(0) => bail!(
68            "Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `{}`",
69            &entry
70        ),
71
72        Some(position) if position == entry.len() - 1 => bail!(
73            "Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `{}`",
74            &entry
75        ),
76
77        Some(position) => Ok((entry[..position].into(), entry[position + 1..].into())),
78    }
79}
80
81pub(crate) const DEFAULT_PACKAGE_MANIFEST_FILE: &str = "wasmer.toml";
82
83/// Load a package manifest from the manifest file.
84///
85/// Path can either be a directory, or a concrete file path.
86pub fn load_package_manifest(
87    path: &Path,
88) -> Result<Option<(PathBuf, wasmer_config::package::Manifest)>, anyhow::Error> {
89    let file_path = if path.is_file() {
90        path.to_owned()
91    } else {
92        path.join(DEFAULT_PACKAGE_MANIFEST_FILE)
93    };
94
95    let contents = match std::fs::read_to_string(&file_path) {
96        Ok(c) => c,
97        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
98        Err(err) => {
99            return Err(err).with_context(|| {
100                format!(
101                    "Could not read package manifest at '{}'",
102                    file_path.display()
103                )
104            });
105        }
106    };
107
108    let manifest = wasmer_config::package::Manifest::parse(&contents).with_context(|| {
109        format!(
110            "Could not parse package config at: '{}' - full config: {}",
111            file_path.display(),
112            contents
113        )
114    })?;
115
116    Ok(Some((file_path, manifest)))
117}
118
119/// Merge two yaml values by recursively merging maps from b into a.
120///
121/// Preserves old values that were not in b.
122pub(crate) fn merge_yaml_values(a: &serde_yaml::Value, b: &serde_yaml::Value) -> serde_yaml::Value {
123    use serde_yaml::Value as V;
124    match (a, b) {
125        (V::Mapping(a), V::Mapping(b)) => {
126            let mut m = a.clone();
127            for (k, v) in b.iter() {
128                let newval = if let Some(old) = a.get(k) {
129                    merge_yaml_values(old, v)
130                } else {
131                    v.clone()
132                };
133                m.insert(k.clone(), newval);
134            }
135            V::Mapping(m)
136        }
137        _ => b.clone(),
138    }
139}
140
141// pub(crate) fn shell(cmd: &str, args: &[&str]) -> anyhow::Result<Option<i32>> {
142//     let ecode = Command::new(cmd).args(args).spawn()?.wait()?;
143//     if ecode.success() {
144//         Ok(ecode.code())
145//     } else {
146//         Err(anyhow::format_err!(
147//             "failed to execute linux command (code={:?})",
148//             ecode.code()
149//         ))
150//     }
151// }
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_merge_yaml_values() {
159        use serde_yaml::Value;
160        let v1 = r#"
161a: a
162b:
163  b1: b1
164c: c
165        "#;
166        let v2 = r#"
167a: a1
168b:
169  b2: b2
170        "#;
171        let v3 = r#"
172a: a1
173b:
174  b1: b1
175  b2: b2
176c: c
177        "#;
178
179        let a: Value = serde_yaml::from_str(v1).unwrap();
180        let b: Value = serde_yaml::from_str(v2).unwrap();
181        let c: Value = serde_yaml::from_str(v3).unwrap();
182        let merged = merge_yaml_values(&a, &b);
183        assert_eq!(merged, c);
184    }
185
186    #[test]
187    fn test_parse_envvar() {
188        assert_eq!(
189            parse_envvar("A").unwrap_err().to_string(),
190            "Environment variable must be of the form `<name>=<value>`; found `A`"
191        );
192        assert_eq!(
193            parse_envvar("=A").unwrap_err().to_string(),
194            "Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `=A`"
195        );
196        assert_eq!(
197            parse_envvar("A=").unwrap_err().to_string(),
198            "Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `A=`"
199        );
200        assert_eq!(parse_envvar("A=B").unwrap(), ("A".into(), "B".into()));
201        assert_eq!(parse_envvar("   A=B\t").unwrap(), ("A".into(), "B".into()));
202        assert_eq!(
203            parse_envvar("A=B=C=D").unwrap(),
204            ("A".into(), "B=C=D".into())
205        );
206    }
207}