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