1#![forbid(unsafe_code)]
2
3use sha2::Digest;
4use std::borrow::Cow;
5use std::path::Path;
6use std::time::SystemTime;
7use std::{fs, io};
8
9#[doc(hidden)]
11#[cfg_attr(debug_assertions, allow(unused))]
12pub struct FileEntry {
13 pub rel_path: String,
14 pub full_canonical_path: String,
15}
16
17#[doc(hidden)]
18pub fn is_path_included(rel_path: &str, includes: &[&str], excludes: &[&str]) -> bool {
19 use globset::Glob;
20
21 for exclude in excludes {
23 let pattern = Glob::new(exclude)
24 .unwrap_or_else(|_| panic!("invalid exclude pattern '{}'", exclude))
25 .compile_matcher();
26
27 if pattern.is_match(rel_path) {
28 return false;
29 }
30 }
31
32 if includes.is_empty() {
34 return true;
35 }
36
37 for include in includes {
39 let pattern = Glob::new(include)
40 .unwrap_or_else(|_| panic!("invalid include pattern '{}'", include))
41 .compile_matcher();
42
43 if pattern.is_match(rel_path) {
44 return true;
45 }
46 }
47
48 false
49}
50
51#[doc(hidden)]
53#[cfg_attr(debug_assertions, allow(unused))]
54pub fn get_files<'patterns>(
55 folder_path: String,
56 includes: &'patterns [&str],
57 excludes: &'patterns [&str],
58) -> impl Iterator<Item = FileEntry> + 'patterns {
59 walkdir::WalkDir::new(&folder_path)
60 .follow_links(true)
61 .sort_by_file_name()
62 .into_iter()
63 .filter_map(|e| e.ok())
65 .filter(|e| e.file_type().is_file())
67 .filter_map(move |e| {
69 let rel_path = path_to_str(
70 e.path()
71 .strip_prefix(&folder_path)
72 .expect("Paths should be convertible to strings"),
73 );
74 let full_canonical_path =
75 path_to_str(std::fs::canonicalize(e.path()).expect("Could not get canonical path"));
76
77 let rel_path = if std::path::MAIN_SEPARATOR == '\\' {
78 rel_path.replace('\\', "/")
79 } else {
80 rel_path
81 };
82
83 if is_path_included(&rel_path, includes, excludes) {
84 Some(FileEntry {
85 rel_path,
86 full_canonical_path,
87 })
88 } else {
89 None
90 }
91 })
92}
93
94#[derive(Clone)]
96pub struct EmbeddedFile {
97 pub data: Cow<'static, [u8]>,
98 pub metadata: EmbeddedFileMetadata,
99}
100
101#[doc(hidden)]
103#[derive(Clone)]
104pub struct EmbeddedFileMetadata {
105 hash: [u8; 32],
106 last_modified: Option<u64>,
107 mimetype: Cow<'static, str>,
108}
109
110impl EmbeddedFileMetadata {
111 #[doc(hidden)]
112 pub const fn __rust_embed_new(
113 hash: [u8; 32],
114 last_modified: Option<u64>,
115 mimetype: &'static str,
116 ) -> Self {
117 Self {
118 hash,
119 last_modified,
120 mimetype: Cow::Borrowed(mimetype),
121 }
122 }
123
124 pub fn sha256_hash(&self) -> [u8; 32] {
126 self.hash
127 }
128
129 pub fn last_modified(&self) -> Option<u64> {
132 self.last_modified
133 }
134
135 pub fn mimetype(&self) -> &str {
137 &self.mimetype
138 }
139}
140
141#[doc(hidden)]
142pub fn read_file_from_fs(file_path: &Path) -> io::Result<EmbeddedFile> {
143 let data = fs::read(file_path)?;
144 let data = Cow::from(data);
145
146 let mut hasher = sha2::Sha256::new();
147 hasher.update(&data);
148 let hash: [u8; 32] = hasher.finalize().into();
149
150 let source_date_epoch = match std::env::var("SOURCE_DATE_EPOCH") {
151 Ok(value) => value.parse::<u64>().map_or(None, |v| Some(v)),
152 Err(_) => None,
153 };
154
155 let last_modified = fs::metadata(file_path)?
156 .modified()
157 .ok()
158 .map(|last_modified| {
159 last_modified
160 .duration_since(SystemTime::UNIX_EPOCH)
161 .expect("Time before the UNIX epoch is unsupported")
162 .as_secs()
163 });
164
165 let mimetype = mime_guess::from_path(file_path)
166 .first_or_octet_stream()
167 .to_string();
168
169 Ok(EmbeddedFile {
170 data,
171 metadata: EmbeddedFileMetadata {
172 hash,
173 last_modified: source_date_epoch.or(last_modified),
174 mimetype: mimetype.into(),
175 },
176 })
177}
178
179fn path_to_str<P: AsRef<std::path::Path>>(p: P) -> String {
180 p.as_ref()
181 .to_str()
182 .expect("Path does not have a string representation")
183 .to_owned()
184}