Skip to main content

cargo_zng/res/built_in/
glob.rs

1use super::*;
2
3const GLOB_HELP: &str = "
4Copy all matches in place
5
6The request file:
7  source/l10n/fluent-files.zr-glob
8   | # localization dir
9   | l10n
10   | # only Fluent files
11   | **/*.ftl
12   | # except test locales
13   | !:**/pseudo*
14
15Copies all '.ftl' not in a *pseudo* path to:
16  target/l10n/
17
18The first path pattern is required and defines the entries that
19will be copied, an initial pattern with '**' flattens the matches.
20The path is relative to the Cargo workspace root.
21
22The subsequent patterns are optional and filter each file or dir selected by
23the first pattern. The paths are relative to each match, if it is a file 
24the filters apply to the file name only, if it is a dir the filters apply to
25the dir and descendants.
26
27The glob pattern syntax is:
28
29    ? — matches any single character.
30    * — matches any (possibly empty) sequence of characters.
31   ** — matches the current directory and arbitrary subdirectories.
32  [c] — matches any character inside the brackets.
33[a-z] — matches any characters in the Unicode sequence.
34 [!b] — negates the brackets match.
35
36And in filter patterns only:
37
38!:pattern — negates the entire pattern.
39
40";
41pub(super) fn glob() {
42    help(GLOB_HELP);
43
44    // target derived from the request place
45    let target = path(ZR_TARGET);
46    let target = target.parent().unwrap();
47
48    let request_path = path(ZR_REQUEST);
49    let mut lines = read_lines(&request_path);
50    let (ln, selection) = lines
51        .next()
52        .unwrap_or_else(|| fatal!("expected at least one path pattern"))
53        .unwrap_or_else(|e| fatal!("{e}"));
54
55    // parse first pattern
56    let selection = ::glob::glob(&selection).unwrap_or_else(|e| fatal!("at line {ln}, {e}"));
57    // parse filter patterns
58    let mut filters = vec![];
59    for r in lines {
60        let (ln, filter) = r.unwrap_or_else(|e| fatal!("{e}"));
61        let (filter, matches_if) = if let Some(f) = filter.strip_prefix("!:") {
62            (f, false)
63        } else {
64            (filter.as_str(), true)
65        };
66        let pat = ::glob::Pattern::new(filter).unwrap_or_else(|e| fatal!("at line {ln}, {e}"));
67        filters.push((pat, matches_if));
68    }
69    // collect first matches
70    let selection = {
71        let mut s = vec![];
72        for entry in selection {
73            s.push(entry.unwrap_or_else(|e| fatal!("{e}")));
74        }
75        // sorted for deterministic results in case flattened files override previous
76        s.sort();
77        s
78    };
79
80    let mut any = false;
81
82    'apply: for source in selection {
83        if source.is_dir() {
84            let filters_root = source.parent().map(Path::to_owned).unwrap_or_default();
85            'copy_dir: for entry in walkdir::WalkDir::new(&source).sort_by_file_name() {
86                let source = entry.unwrap_or_else(|e| fatal!("cannot walkdir entry `{}`, {e}", source.display()));
87                let source = source.path();
88                // filters match 'entry/**'
89                let match_source = source.strip_prefix(&filters_root).unwrap();
90                for (filter, matches_if) in &filters {
91                    if filter.matches_path(match_source) != *matches_if {
92                        continue 'copy_dir;
93                    }
94                }
95                let target = target.join(match_source);
96
97                any = true;
98                if source.is_dir() {
99                    fs::create_dir_all(&target).unwrap_or_else(|e| fatal!("cannot create dir `{}`, {e}", source.display()));
100                } else {
101                    if let Some(p) = &target.parent() {
102                        fs::create_dir_all(p).unwrap_or_else(|e| fatal!("cannot create dir `{}`, {e}", p.display()));
103                    }
104                    fs::copy(source, &target)
105                        .unwrap_or_else(|e| fatal!("cannot copy `{}` to `{}`, {e}", source.display(), target.display()));
106                }
107                println!("{}", display_path(&target));
108            }
109        } else if source.is_file() {
110            // filters match 'entry'
111            let source_name = source.file_name().unwrap().to_string_lossy();
112            for (filter, matches_if) in &filters {
113                if filter.matches(&source_name) != *matches_if {
114                    continue 'apply;
115                }
116            }
117            let target = target.join(source_name.as_ref());
118
119            any = true;
120            fs::copy(&source, &target).unwrap_or_else(|e| fatal!("cannot copy `{}` to `{}`, {e}", source.display(), target.display()));
121            println!("{}", display_path(&target));
122        } else if source.is_symlink() {
123            symlink_warn(&source);
124        }
125    }
126
127    if !any {
128        warn!("no match")
129    }
130}