1extern crate glob;
2
3use self::glob::glob;
4use colored::*;
5use error::*;
6use regex::{Regex, RegexSet};
7use std::fs;
8use std::fs::Metadata;
9use std::path::PathBuf;
10use std::process::exit;
11use std::result::Result;
12use types::*;
13use utils::*;
14
15#[cfg(not(target_os = "windows"))]
16use std::os::unix::fs::PermissionsExt;
17
18pub fn glob_exists(s: &str) -> bool {
19 glob(s).unwrap().filter_map(Result::ok).count() != 0 }
21
22pub fn is_project_dir(p: &str, name: &str) -> bool {
36 lazy_static! {
38 static ref REGEX_PROJECT_DIR: Regex =
39 Regex::new(r"_minted|((\.stack-work|build|gen|cbits|ats-deps|\.atspkg|target|\.reco-work|\.cabal-sandbox|dist|\.criterion|dist-newstyle.*|target|\.egg-info|elm-stuff|\.pulp-cache|\.psc-package|output|bower_components|node_modules|__pycache__|lib|\.liquid)$)")
40 .unwrap();
41 }
42
43 if REGEX_PROJECT_DIR.is_match(name) {
44 let mut parent_path = PathBuf::from(p);
45 let mut parent_string = p.to_owned();
46 match name {
47 ".stack-work" => {
48 let mut hpack = parent_path.clone();
49 parent_path.push("../cabal.project");
50 hpack.push("package.yaml");
51 parent_string.push_str("/../*.cabal");
52 parent_path.exists() || hpack.exists() || glob_exists(&parent_string)
53 }
54 "nimcache" => {
55 parent_string.push_str("/../*.nim");
56 glob_exists(&parent_string)
57 }
58 "target" => {
59 let mut dhall = parent_path.clone();
60 dhall.push("../atspkg.dhall");
61 let mut shake = parent_path.clone();
62 shake.push("../shake.hs");
63 let mut elba = parent_path.clone();
64 elba.push("../elba.toml");
65 parent_path.push("../Cargo.toml");
66 parent_path.exists() || dhall.exists() || shake.exists() || elba.exists()
67 }
68 ".atspkg" | "ats-deps" | "cbits" | "gen" => {
69 parent_path.push("../atspkg.dhall");
70 parent_path.exists()
71 }
72 ".criterion" => {
73 parent_path.push("../Cargo.toml");
74 parent_path.exists()
75 }
76 ".liquid" => {
77 parent_string.push_str("/../*.hs");
78 glob_exists(&parent_string)
79 }
80 ".reco-work" => {
81 parent_path.push("../main.go");
82 parent_path.exists()
83 }
84 "elm-stuff" => {
89 let mut package_path = PathBuf::from(p);
90 package_path.push("../elm-package.json");
91 package_path.exists()
92 }
93 ".pulp-cache" | "output" | ".psc-package" => {
94 let mut package_path = PathBuf::from(p);
95 package_path.push("../psc-package.json");
96 package_path.exists()
97 }
98 "build" | "dist" | ".cabal-sandbox" | "dist-newstyle" | "dist-newstyle-meta" => {
99 let mut cabal_project = parent_path.clone();
100 let mut parent_string_blod = parent_string.clone();
101 let mut parent_string_idr2 = parent_string.clone();
102 parent_path.push("../setup.py");
103 parent_string.push_str("/../*.cabal");
104 cabal_project.push("../cabal.project");
105 parent_string_blod.push_str("/../*.blod");
106 parent_string_idr2.push_str("/../*.ipkg");
107 parent_path.exists()
108 || glob_exists(&parent_string)
109 || cabal_project.exists()
110 || glob_exists(&parent_string_blod)
111 || glob_exists(&parent_string_idr2)
112 }
113 "bower_components" => {
114 let mut package_path = PathBuf::from(p);
115 package_path.push("../bower.json");
116 package_path.exists()
117 }
118 "__pycache__" => true,
119 "node_modules" => true,
120 _ => {
121 let mut parent_path_latex = parent_path.clone();
122 parent_path.push("../setup.py");
123 parent_path_latex.push("../*.tex");
124 (parent_path.exists() && str::ends_with(name, ".egg-info"))
125 || (glob_exists(&parent_path_latex.to_string_lossy())
126 && str::starts_with(name, "_minted"))
127 }
128 }
129 } else {
130 false
131 }
132}
133
134pub fn is_artifact(
170 path_str: &str,
171 full_path: &str,
172 metadata: &Metadata,
173 vimtags: bool,
174 gitignore: &Option<RegexSet>,
175) -> bool {
176 lazy_static! {
177 static ref REGEX_GITIGNORE: Regex =
178 Regex::new(r"\.(stats|conf|h|c|out|cache.*|dat|pc|info|ll|js)$").unwrap();
179 }
180
181 {
183 lazy_static! {
184 static ref REGEX: Regex =
185 Regex::new(r"\.(a|i|ii|la|lo|o|keter|bc|dyn_o|d|rlib|crate|hi|hc|chi|dyn_hi|jsexe|webapp|js\.externs|ibc|toc|aux|fdb_latexmk|spl|bbl|blg|fls|egg-info|whl|js_a|js_hi|jld|ji|js_o|so.*|dump-.*|vmb|crx|orig|elmo|elmi|hspec-failures|pyc|vo|agdai|beam|mod|go\.(v|teak|xmldef|rewrittenast|rewrittengo|simplego|tree-(bind|eval|finish|parse))|p_hi|p_o|prof|hide-cache|ghc\.environment\..*\d.\d.\d|(t|p|m)ix|synctex\.gz|hl|hp|sandbox\.config|exe|eventlog|ipa|ttc|chs\.h|chi|\d+\.actual|\d+\.expected)$")
186 .unwrap();
187 }
188
189 if REGEX.is_match(path_str) || (path_str == "tags" && vimtags) {
190 true
191 } else if let Some(ref x) = *gitignore {
192 if metadata.permissions().mode() == 0o755 || REGEX_GITIGNORE.is_match(path_str) {
193 x.is_match(full_path)
194 } else {
195 false
196 }
197 } else {
198 path_str == "flxg_stats.txt"
199 }
200 }
201}
202
203pub fn read_size(
205 in_paths: &PathBuf,
206 excludes: Option<&Regex>,
207 maybe_gitignore: &Option<RegexSet>,
208 vimtags: bool,
209 artifacts_only: bool,
210) -> FileSize {
211 let mut size = FileSize::new(0);
213 let gitignore = if artifacts_only {
214 mk_ignores(in_paths, maybe_gitignore)
215 } else {
216 None
217 };
218
219 if let Ok(paths) = fs::read_dir(in_paths) {
221 for p in paths {
223 let val = match p {
224 Ok(x) => x,
225 _ => {
226 panic!("{}", Internal::IoError);
227 }
228 };
229 let path = val.path();
230 let (path_string, bool_loop): (&str, bool) = if let Some(x) = path.as_path().to_str() {
231 let bool_loop = match excludes {
232 Some(ex) => !ex.is_match(x),
233 _ => true,
234 };
235 (x, bool_loop)
236 } else {
237 eprintln!(
238 "{}: skipping invalid unicode filepath at {:?}",
239 "Warning".yellow(),
240 path
241 );
242 ("", false)
243 };
244
245 if bool_loop {
248 let path_type = val.file_type().unwrap(); if path_type.is_file() {
252 if let Ok(metadata) = val.metadata() {
254 if !artifacts_only || {
255 is_artifact(
256 val.file_name().to_str().unwrap(), path_string,
258 &metadata, vimtags,
260 &gitignore,
261 )
262 } {
263 let file_size = FileSize::new(metadata.len());
265 size.add(file_size);
266 }
267 }
268 }
269 else if path_type.is_dir() {
271 let dir_size = if artifacts_only
272 && is_project_dir(path_string, val.file_name().to_str().unwrap())
273 {
274 read_size(&path, excludes, &gitignore, vimtags, false)
275 } else {
276 read_size(&path, excludes, &gitignore, vimtags, artifacts_only)
277 };
278 size.add(dir_size);
279 }
280 }
281 }
289 }
290 else if !in_paths.exists() {
293 eprintln!(
294 "{}: path '{}' does not exist, or you do not have permission to enter.",
295 "Error".red(),
296 &in_paths.display()
297 );
298 }
299 else if !in_paths.is_dir() {
301 eprintln!(
302 "{}: {} is not a directory.",
303 "Error".red(),
304 &in_paths.display()
305 );
306 exit(0x0001);
307 }
308 else {
310 eprintln!(
311 "{}: permission denied for directory: {}",
312 "Warning".yellow(),
313 &in_paths.display()
314 );
315 }
316
317 size
318}
319
320pub fn read_all(
322 in_paths: &PathBuf,
323 depth: u8,
324 max_depth: Option<u8>,
325 excludes: Option<&Regex>,
326 maybe_gitignore: &Option<RegexSet>,
327 vimtags: bool,
328 artifacts_only: bool,
329) -> FileTree {
330 let mut tree = FileTree::new();
332 let gitignore = if artifacts_only {
333 mk_ignores(in_paths, maybe_gitignore)
334 } else {
335 None
336 };
337
338 if let Ok(paths) = fs::read_dir(in_paths) {
340 for p in paths {
342 let val = match p {
343 Ok(x) => x,
344 _ => {
345 eprintln!("{}: {:?}.", "Error".red(), p);
346 exit(0x0001)
347 }
348 };
349 let path = val.path();
350 let (path_string, bool_loop): (&str, bool) = if let Some(x) = path.as_path().to_str() {
351 let bool_loop = match excludes {
352 Some(ex) => !ex.is_match(x),
353 _ => true,
354 };
355
356 (x, bool_loop)
357 } else {
358 eprintln!(
359 "{}: skipping invalid unicode filepath at {:?}",
360 "Warning".yellow(),
361 path
362 );
363 ("", false)
364 };
365
366 if bool_loop {
369 let path_type = val.file_type().unwrap(); if path_type.is_file() {
373 if let Ok(metadata) = val.metadata() {
375 if !artifacts_only || {
377 is_artifact(
378 val.file_name().to_str().unwrap(), path_string,
380 &metadata,
381 vimtags,
382 &gitignore,
383 )
384 } {
385 let file_size = FileSize::new(metadata.len());
386 tree.push(path_string.to_string(), file_size, None, depth + 1, false);
387 }
388 }
389 }
390 else if path_type.is_dir() {
392 if let Some(d) = max_depth {
393 if depth + 1 >= d && !artifacts_only {
394 let dir_size =
395 { read_size(&path, excludes, &gitignore, vimtags, artifacts_only) };
396 tree.push(path_string.to_string(), dir_size, None, depth + 1, true);
397 } else if artifacts_only
398 && is_project_dir(path_string, val.file_name().to_str().unwrap())
399 {
400 let dir_size =
401 { read_size(&path, excludes, &gitignore, vimtags, false) };
402 tree.push(path_string.to_string(), dir_size, None, depth + 1, true);
403 } else {
404 let mut subtree = read_all(
405 &path,
406 depth + 1,
407 max_depth,
408 excludes,
409 &gitignore,
410 vimtags,
411 artifacts_only,
412 );
413 let dir_size = subtree.file_size;
414 tree.push(
415 path_string.to_string(),
416 dir_size,
417 Some(&mut subtree),
418 depth + 1,
419 true,
420 );
421 }
422 } else if artifacts_only
423 && is_project_dir(path_string, val.file_name().to_str().unwrap())
424 {
425 let dir_size = { read_size(&path, excludes, &gitignore, vimtags, false) };
426 tree.push(path_string.to_string(), dir_size, None, depth + 1, true);
427 } else {
428 let mut subtree = read_all(
429 &path,
430 depth + 1,
431 max_depth,
432 excludes,
433 &gitignore,
434 vimtags,
435 artifacts_only,
436 );
437 let dir_size = subtree.file_size;
438 tree.push(
439 path_string.to_string(),
440 dir_size,
441 Some(&mut subtree),
442 depth + 1,
443 true,
444 );
445 }
446 }
447 }
448 }
449 }
457 else if !in_paths.exists() {
460 eprintln!(
461 "{}: path '{}' does not exist, or you do not have permission to enter.",
462 "Error".red(),
463 &in_paths.display()
464 );
465 }
466 else if !in_paths.is_dir() {
468 if artifacts_only {
469 eprintln!(
470 "{}: {} is not a directory; not searching for artifacts",
471 "Warning".yellow(),
472 &in_paths.display()
473 );
474 }
475
476 if let Ok(l) = in_paths.metadata() {
477 let size = l.len();
478 let to_formatted = format!("{}", FileSize::new(size));
479 println!("{}\t {}", &to_formatted.green(), in_paths.display());
480 } else {
481 panic!("{}", Internal::IoError);
482 }
483 }
484 else {
486 eprintln!(
487 "{}: permission denied for directory: {}",
488 "Warning".yellow(),
489 &in_paths.display()
490 );
491 }
492
493 tree
494}
495
496pub fn read_no_excludes(
498 in_paths: &PathBuf,
499 _: Option<&Regex>,
500 _: &Option<RegexSet>,
501 _: bool,
502) -> FileSize {
503 let mut size = FileSize::new(0);
505
506 if let Ok(paths) = fs::read_dir(in_paths) {
508 for p in paths {
510 let val = match p {
511 Ok(x) => x,
512 _ => {
513 panic!("{}", Internal::IoError);
514 }
515 };
516 let path_type = val.file_type().unwrap(); if path_type.is_file() {
522 if let Ok(metadata) = val.metadata() {
524 let file_size = FileSize::new(metadata.len());
525 size.add(file_size);
526 }
527 }
528 else if path_type.is_dir() {
530 let dir_size = {
531 let path = val.path();
532 read_no_excludes(&path, None, &None, false)
533 };
534 size.add(dir_size);
535 }
536 }
537 }
538 else if !in_paths.exists() {
541 eprintln!(
542 "{}: path '{}' does not exist, or you do not have permission to enter.",
543 "Error".red(),
544 &in_paths.display()
545 );
546 }
547 else if !in_paths.is_dir() {
549 eprintln!(
550 "{}: {} is not a directory.",
551 "Error".red(),
552 &in_paths.display()
553 );
554 exit(0x0001);
555 }
556 else {
558 eprintln!(
559 "{}: permission denied for directory: {}",
560 "Warning".yellow(),
561 &in_paths.display()
562 );
563 }
564
565 size
566}
567
568pub fn read_all_fast(in_paths: &PathBuf, depth: u8, max_depth: Option<u8>) -> FileTree {
570 let mut tree = FileTree::new();
572
573 if let Ok(paths) = fs::read_dir(in_paths) {
575 for p in paths {
577 let val = match p {
578 Ok(x) => x,
579 _ => {
580 eprintln!("{}: unexpected failure on {:?} failed.", "Error".red(), p);
581 exit(0x0001)
582 }
583 };
584
585 let path_type = val.file_type().unwrap(); if path_type.is_file() {
591 if let Ok(metadata) = val.metadata() {
593 {
595 let path = val.path();
596 let path_string: &str = if let Some(x) = path.as_path().to_str() {
597 x
598 } else {
599 eprintln!(
600 "{}: skipping invalid unicode filepath at {:?}",
601 "Warning".yellow(),
602 path
603 );
604 ""
605 };
606 let file_size = FileSize::new(metadata.len());
607 tree.push(path_string.to_string(), file_size, None, depth + 1, false);
608 }
609 }
610 }
611 else if path_type.is_dir() {
613 if let Some(d) = max_depth {
614 if depth + 1 >= d {
615 let path = val.path();
616 let path_string: &str = if let Some(x) = path.as_path().to_str() {
617 x
618 } else {
619 eprintln!(
620 "{}: skipping invalid unicode filepath at {:?}",
621 "Warning".yellow(),
622 path
623 );
624 ""
625 };
626 let dir_size = { read_no_excludes(&path, None, &None, false) };
627 tree.push(path_string.to_string(), dir_size, None, depth + 1, true);
628 } else {
629 let path = val.path();
630 let path_string: &str = if let Some(x) = path.as_path().to_str() {
631 x
632 } else {
633 eprintln!(
634 "{}: skipping invalid unicode filepath at {:?}",
635 "Warning".yellow(),
636 path
637 );
638 ""
639 };
640 let mut subtree = read_all_fast(&path, depth + 1, max_depth);
641 let dir_size = subtree.file_size;
642 tree.push(
643 path_string.to_string(),
644 dir_size,
645 Some(&mut subtree),
646 depth + 1,
647 true,
648 );
649 }
650 } else {
651 let path = val.path();
652 let path_string: &str = if let Some(x) = path.as_path().to_str() {
653 x
654 } else {
655 eprintln!(
656 "{}: skipping invalid unicode filepath at {:?}",
657 "Warning".yellow(),
658 path
659 );
660 ""
661 };
662 let mut subtree = read_all_fast(&path, depth + 1, max_depth);
663 let dir_size = subtree.file_size;
664 tree.push(
665 path_string.to_string(),
666 dir_size,
667 Some(&mut subtree),
668 depth + 1,
669 true,
670 );
671 }
672 }
673 }
674 }
675 else if !in_paths.exists() {
678 eprintln!(
679 "{}: path '{}' does not exist, or you do not have permission to enter.",
680 "Error".red(),
681 &in_paths.display()
682 );
683 }
684 else if !in_paths.is_dir() {
686 if let Ok(l) = in_paths.metadata() {
687 let size = l.len();
688 let to_formatted = format!("{}", FileSize::new(size));
689 println!("{}\t {}", &to_formatted.green(), in_paths.display());
690 } else {
691 panic!("{}", Internal::IoError);
692 }
693 }
694 else {
696 eprintln!(
697 "{}: permission denied for directory: {}",
698 "Warning".yellow(),
699 &in_paths.display()
700 );
701 }
702
703 tree
704}