1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use std::path::PathBuf;

use anyhow::Context;
use dialoguer::console::{style, Emoji};
use indicatif::ProgressBar;
use wasmer_package::utils::from_disk;

/// Extract contents of a webc image to a directory.
///
/// See --format flag for available output formats.
#[derive(clap::Parser, Debug)]
pub struct PackageUnpack {
    /// The output directory.
    #[clap(short = 'o', long)]
    pub out_dir: PathBuf,

    /// Overwrite existing directories/files.
    #[clap(long)]
    pub overwrite: bool,

    /// Run the unpack command without any output
    #[clap(long)]
    pub quiet: bool,

    /// Path to the package.
    pub package_path: PathBuf,

    /// Output format.
    ///
    /// * package
    ///   Restore a package directory with a wasmer.toml
    ///   NOTE: this conversion is lossy, because webcs don't store the original
    ///   wasmer.toml and the full contents can not be restored.
    ///
    /// * webc
    ///   Directly unpack the webc contents.
    ///   - Volumes will be placed in subdirectories.
    ///   - atoms will be placed in the root directory
    ///   - the full webc manifest will be placed in a manifest.json file
    #[clap(short, long, default_value = "package")]
    pub format: Format,
}

static PACKAGE_EMOJI: Emoji<'_, '_> = Emoji("📦 ", "");
static EXTRACTED_TO_EMOJI: Emoji<'_, '_> = Emoji("📂 ", "");

/// Webc unpack format.
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum Format {
    /// See [`PackageUnpack::format`] for details.
    Package,
    /// See [`PackageUnpack::format`] for details.
    Webc,
}

impl PackageUnpack {
    pub(crate) fn execute(&self) -> Result<(), anyhow::Error> {
        // Setup the progress bar
        let pb = if self.quiet {
            ProgressBar::hidden()
        } else {
            ProgressBar::new_spinner()
        };

        pb.println(format!(
            "{} {}Unpacking...",
            style("[1/2]").bold().dim(),
            PACKAGE_EMOJI
        ));

        let pkg = from_disk(&self.package_path).with_context(|| {
            format!(
                "could not open package at '{}'",
                self.package_path.display()
            )
        })?;

        let outdir = &self.out_dir;
        std::fs::create_dir_all(outdir)
            .with_context(|| format!("could not create output directory '{}'", outdir.display()))?;

        match self.format {
            Format::Package => {
                wasmer_package::convert::webc_to_package_dir(&pkg, outdir)
                    .with_context(|| "could not extract package")?;
            }
            Format::Webc => {
                pkg.unpack(outdir, self.overwrite)
                    .with_context(|| "could not extract package".to_string())?;
            }
        }

        pb.println(format!(
            "{} {}Extracted package contents to '{}'",
            style("[2/2]").bold().dim(),
            EXTRACTED_TO_EMOJI,
            self.out_dir.display()
        ));

        pb.finish();

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Download a package from the dev registry.
    #[test]
    fn test_cmd_package_extract() {
        let dir = tempfile::tempdir().unwrap();

        let package_path = std::env::var("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap()
            .parent().unwrap()
            .parent().unwrap()
            .join("tests/integration/cli/tests/webc/hello-0.1.0-665d2ddc-80e6-4845-85d3-4587b1693bb7.webc");

        assert!(package_path.is_file());

        let cmd = PackageUnpack {
            out_dir: dir.path().to_owned(),
            overwrite: false,
            package_path,
            quiet: true,
            format: Format::Webc,
        };

        cmd.execute().unwrap();

        let mut items = std::fs::read_dir(dir.path())
            .unwrap()
            .map(|x| {
                x.unwrap()
                    .path()
                    .file_name()
                    .unwrap()
                    .to_str()
                    .unwrap()
                    .to_string()
            })
            .collect::<Vec<_>>();
        items.sort();
        assert_eq!(
            items,
            vec![
                "atom".to_string(),
                "manifest.json".to_string(),
                "metadata".to_string(),
            ]
        );
    }
}