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