1#![cfg(feature = "fs")]
4
5use std::path::{Path, PathBuf};
6
7use crate::pack::Pack;
8
9#[derive(Debug, Clone, Default)]
11pub struct ExtractOptions {
12 pub packages: bool,
14 pub fonts: bool,
16 pub force: bool,
18}
19
20#[derive(Debug, Clone, Default)]
22pub struct ExtractReport {
23 pub written: Vec<PathBuf>,
25}
26
27#[derive(Debug, thiserror::Error)]
29pub enum ExtractError {
30 #[error("`{0}` already exists (pass force to overwrite)")]
31 Exists(PathBuf),
32 #[error("failed to write `{path}`: {source}")]
33 Io {
34 path: PathBuf,
35 #[source]
36 source: std::io::Error,
37 },
38}
39
40pub fn extract(
48 pack: &Pack,
49 dir: &Path,
50 options: &ExtractOptions,
51) -> Result<ExtractReport, ExtractError> {
52 let mut report = ExtractReport::default();
53
54 for (path, data) in pack.files() {
55 write_file(dir, Path::new(path), data, options, &mut report)?;
56 }
57
58 if options.packages {
59 for (spec, files) in pack.packages() {
60 let base = PathBuf::from("packages")
61 .join(spec.namespace.as_str())
62 .join(spec.name.as_str())
63 .join(spec.version.to_string());
64 for (path, data) in files {
65 write_file(dir, &base.join(path), data, options, &mut report)?;
66 }
67 }
68 }
69
70 if options.fonts {
71 for font in pack.fonts() {
72 let path = Path::new(&font.entry.path);
73 if report.written.iter().any(|written| written == path) {
74 continue;
75 }
76 write_file(dir, path, &font.data, options, &mut report)?;
77 }
78 }
79
80 Ok(report)
81}
82
83fn write_file(
84 dir: &Path,
85 relative: &Path,
86 data: &[u8],
87 options: &ExtractOptions,
88 report: &mut ExtractReport,
89) -> Result<(), ExtractError> {
90 let target = dir.join(relative);
91 if !options.force && target.exists() {
92 return Err(ExtractError::Exists(target));
93 }
94 if let Some(parent) = target.parent() {
95 std::fs::create_dir_all(parent).map_err(|source| ExtractError::Io {
96 path: parent.to_owned(),
97 source,
98 })?;
99 }
100 std::fs::write(&target, data).map_err(|source| ExtractError::Io {
101 path: target.clone(),
102 source,
103 })?;
104 report.written.push(relative.to_owned());
105 Ok(())
106}