libroast/operations/roast/
mod.rs1pub mod helpers;
9use crate::{
10 compress,
11 operations::cli,
12 utils::{
13 process_globs,
14 start_tracing,
15 },
16};
17use helpers::{
18 filter_paths,
19 is_excluded,
20};
21use rayon::prelude::*;
22use std::{
23 fs::{
24 self,
25 },
26 io,
27 path::{
28 Path,
29 PathBuf,
30 },
31};
32#[allow(unused_imports)]
33use tracing::{
34 Level,
35 debug,
36 error,
37 info,
38 trace,
39 warn,
40};
41use walkdir::WalkDir;
42
43pub(crate) fn get_additional_paths(adtnl_path: &str, root: &Path) -> (PathBuf, PathBuf)
45{
46 if let Some((ar, tgt)) = adtnl_path.split_once(",")
47 {
48 debug!(?ar, ?tgt);
49 let tgt = if tgt.trim().is_empty() { root } else { &root.join(tgt) };
50 (PathBuf::from(&ar), tgt.to_path_buf())
51 }
52 else
53 {
54 (PathBuf::from(&adtnl_path), root.to_path_buf())
55 }
56}
57
58pub(crate) fn process_additional_paths(
61 additional_paths: &[String],
62 target_path: &Path,
63 exclude_canonicalized_paths: &[PathBuf],
64 setup_workdir: &Path,
65 roast_args: &cli::RoastArgs,
66) -> io::Result<()>
67{
68 additional_paths.par_iter().try_for_each(|adtnlp| {
69 debug!(?adtnlp);
70 let (additional_from_path, additional_to_path) =
71 get_additional_paths(adtnlp, setup_workdir);
72 debug!(?additional_from_path, ?additional_to_path);
73 let src_canonicalized =
74 additional_from_path.canonicalize().unwrap_or(additional_from_path.to_path_buf());
75 debug!(?src_canonicalized);
76
77 if src_canonicalized.is_file()
78 {
79 let tgt_stripped =
80 additional_to_path.strip_prefix(setup_workdir).unwrap_or(Path::new("/"));
81 let target_with_tgt = &target_path.join(tgt_stripped);
82 if is_excluded(target_with_tgt, exclude_canonicalized_paths)
83 {
84 warn!(
85 "⚠️ Directory `{}` is WITHIN an EXCLUDED path. Added a file OUTSIDE of target \
86 directory: {}",
87 &target_with_tgt.display(),
88 &src_canonicalized.display()
89 );
90 }
91 fs::create_dir_all(&additional_to_path)?;
93 fs::copy(
95 &src_canonicalized,
96 additional_to_path.join(additional_from_path.file_name().unwrap_or_default()),
97 )?;
98 Ok(())
99 }
100 else if src_canonicalized.is_dir()
101 {
102 let tgt_stripped =
103 additional_to_path.strip_prefix(setup_workdir).unwrap_or(Path::new("/"));
104 let target_with_tgt = &target_path.join(tgt_stripped);
105 if is_excluded(target_with_tgt, exclude_canonicalized_paths)
106 {
107 warn!(
108 "⚠️ ADDITIONAL directory that was WITHIN one of the EXCLUDED paths was added \
109 back from OUTSIDE target path: {}",
110 &target_with_tgt.display()
111 );
112 warn!("⚠️ This may not contain the same contents!");
113 }
114 let new_additional_to_path =
115 additional_to_path.join(src_canonicalized.file_name().unwrap_or_default());
116 fs::create_dir_all(&new_additional_to_path)?;
117 filter_paths(
118 &src_canonicalized,
119 &new_additional_to_path,
120 roast_args.ignore_hidden,
121 roast_args.ignore_git,
122 &[],
123 )
124 }
125 else
126 {
127 Ok(())
128 }
129 })?;
130 Ok(())
131}
132
133pub(crate) fn process_include_paths(
137 include_paths: &[PathBuf],
138 exclude_canonicalized_paths: &[PathBuf],
139 target_path: &Path,
140 setup_workdir: &Path,
141 roast_args: &cli::RoastArgs,
142) -> io::Result<()>
143{
144 include_paths.par_iter().try_for_each(|include_path| {
145 let include_from_path = &target_path.join(include_path);
146 let include_from_path =
147 include_from_path.canonicalize().unwrap_or(include_from_path.to_path_buf());
148 if !include_from_path.exists()
149 {
150 let err = io::Error::new(
151 io::ErrorKind::NotFound,
152 "Path does not exist. This means that this path is not WITHIN the target \
153 directory.",
154 );
155 error!(?err);
156 return Err(err);
157 }
158
159 let include_to_path = &setup_workdir.join(include_path);
160 debug!(?include_path, ?include_from_path, ?include_to_path);
161 if include_from_path.is_dir()
162 {
163 if is_excluded(&include_from_path, exclude_canonicalized_paths)
164 {
165 warn!(
166 "⚠️ INCLUDED directory that is EXCLUDED will be IGNORED: {}",
167 &include_from_path.display()
168 );
169 }
170 else
171 {
172 filter_paths(
173 &include_from_path,
174 include_to_path,
175 roast_args.ignore_hidden,
176 roast_args.ignore_git,
177 &[],
178 )?;
179 }
180 }
181 else if include_from_path.is_file()
182 {
183 let include_from_path_parent =
184 include_from_path.parent().unwrap_or(target_path).to_path_buf();
185 let include_to_path_parent =
186 include_to_path.parent().unwrap_or(setup_workdir).to_path_buf();
187 if is_excluded(&include_from_path_parent, exclude_canonicalized_paths)
188 {
189 warn!(
190 "⚠️ Path `{}` WITHIN an EXCLUDED path has added a file IN target directory. \
191 Added file: {}",
192 &include_from_path_parent.display(),
193 &include_from_path.display()
194 );
195 }
196 if is_excluded(&include_from_path, exclude_canonicalized_paths)
197 {
198 warn!(
199 "⚠️ EXCLUDED file `{}` has also been declared INCLUDED. Adding file takes \
200 precedence. Added file: {}",
201 &include_from_path.display(),
202 &include_from_path.display()
203 );
204 }
205 fs::create_dir_all(&include_to_path_parent)?;
207 fs::copy(include_from_path, include_to_path)?;
209 }
210 Ok(())
211 })?;
212 Ok(())
213}
214
215pub fn roast_opts(roast_args: &cli::RoastArgs, start_trace: bool) -> io::Result<()>
222{
223 if start_trace
224 {
225 start_tracing();
226 }
227
228 info!("❤️🔥 Starting Roast.");
229 debug!(?roast_args);
230 let target_path = process_globs(&roast_args.target)?;
231 let target_path = target_path.canonicalize().unwrap_or(target_path);
232 let tmp_binding = tempfile::Builder::new()
233 .prefix(".rooooooooooaaaaaaaasssst")
234 .rand_bytes(8)
235 .tempdir()
236 .inspect_err(|err| {
237 error!(?err, "Failed to create temporary directory");
238 })?;
239
240 let workdir = &tmp_binding.path();
241 let setup_workdir = if roast_args.preserve_root
242 {
243 workdir.join(target_path.file_name().unwrap_or_default())
244 }
245 else
246 {
247 workdir.to_path_buf()
248 };
249 fs::create_dir_all(&setup_workdir)?;
250
251 let outdir = match &roast_args.outdir
252 {
253 Some(v) => v,
254 None => &std::env::current_dir()?,
255 };
256
257 if !outdir.is_dir()
258 {
259 std::fs::create_dir_all(outdir)?;
260 }
261
262 let outpath = outdir.join(&roast_args.outfile);
263 let outpath = outpath.canonicalize().unwrap_or(outpath);
264
265 let mut exclude_canonicalized_paths: Vec<PathBuf> =
266 roast_args.exclude.clone().unwrap_or_default();
267
268 exclude_canonicalized_paths = exclude_canonicalized_paths
269 .iter()
270 .map(|p| target_path.join(p).canonicalize().unwrap_or_default())
271 .filter(|p| !p.to_string_lossy().trim().is_empty())
274 .collect();
275
276 debug!(?exclude_canonicalized_paths);
277
278 if let Some(additional_paths) = &roast_args.additional_paths
279 {
280 process_additional_paths(
281 additional_paths,
282 &target_path,
283 &exclude_canonicalized_paths,
284 &setup_workdir,
285 roast_args,
286 )?;
287 }
288
289 if let Some(include_paths) = &roast_args.include
290 {
291 process_include_paths(
292 include_paths,
293 &exclude_canonicalized_paths,
294 &target_path,
295 &setup_workdir,
296 roast_args,
297 )?;
298 }
299
300 filter_paths(
301 &target_path,
302 &setup_workdir,
303 roast_args.ignore_hidden,
304 roast_args.ignore_git,
305 &exclude_canonicalized_paths,
306 )?;
307
308 let archive_files: Vec<PathBuf> = WalkDir::new(workdir)
309 .into_iter()
310 .par_bridge()
311 .flatten()
312 .map(|f| {
313 debug!(?f);
314 f.into_path()
315 })
316 .filter(|p| p.is_file())
317 .collect();
318
319 debug!(?archive_files);
320
321 let reproducible = roast_args.reproducible;
322
323 let outpath_str = outpath.as_os_str().to_string_lossy();
324 let result = if outpath_str.ends_with("tar.gz")
325 {
326 compress::targz(&outpath, workdir, &archive_files, reproducible)
327 }
328 else if outpath_str.ends_with("tar.xz")
329 {
330 compress::tarxz(&outpath, workdir, &archive_files, reproducible)
331 }
332 else if outpath_str.ends_with("tar.zst") | outpath_str.ends_with("tar.zstd")
333 {
334 compress::tarzst(&outpath, workdir, &archive_files, reproducible)
335 }
336 else if outpath_str.ends_with("tar.bz")
337 {
338 compress::tarbz2(&outpath, workdir, &archive_files, reproducible)
339 }
340 else if outpath_str.ends_with("tar")
341 {
342 compress::vanilla(&outpath, workdir, &archive_files, reproducible)
343 }
344 else
345 {
346 let msg = format!("Unsupported file: {}", outpath_str);
347 Err(io::Error::new(io::ErrorKind::Unsupported, msg))
348 };
349
350 if let Err(err) = result
353 {
354 error!(?err);
355 }
356 else
357 {
358 info!("🧑🍳 Your new tarball is now in {}", &outpath.display());
359 }
360
361 tmp_binding.close().inspect_err(|e| {
362 error!(?e, "Failed to delete temporary directory!");
363 })?;
364
365 Ok(())
366}