copy_glob/
lib.rs

1//! # About it
2//! 
3//! **copy-glob** is a small utility to copy files to an output directory using glob syntax. Inspired by [copy_to_output](https://crates.io/crates/copy_to_output).
4//! 
5//! # How to use
6//! 
7//! Add a crate to the dev dependencies in `Cargo.toml`.
8//! 
9//! ```toml
10//! [dev-dependencies]
11//! copy-glob = "0.1"
12//! ```
13//! 
14//! Create `build.rs` and add the following to it:
15//! 
16//! ```rust
17//! use copy_glob::{get_target_folder, copy_glob};
18//! 
19//! fn main() {
20//!     let output = get_target_folder().join("copies"); // target/{debug,release}/copies
21//!     copy_glob("**/for_copy/*", output);
22//! }
23//! ```
24//! 
25//! This will copy all the files in the directory, preserving the structure, since the copying starts from the root of the project (where the `Cargo.toml` file is located).
26//! 
27//! To localize the file search somehow, you can use the following code:
28//! 
29//! ```rust,no_run
30//! use copy_glob::{get_target_folder, get_root_path, CopyGlobOptionsBuilder, copy_glob_with};
31//! 
32//! fn main() {
33//!     let output = get_target_folder().join("copies");
34//!     let root = get_root_path().join("for_copy"); // same level as Cargo.toml
35//!     let options = CopyGlobOptionsBuilder::new().set_root_path(root).build();
36//!     copy_glob_with("*.toml", output, &options);
37//! }
38//! ```
39//! 
40//! This will copy all files with the `.toml` extension in the `for_copy` folder without preserving the structure.
41//! 
42//! In addition, you can add exceptions from where files will be copied. By default, the `target` directory hangs in the excludes.
43//! 
44//! ```rust
45//! # use copy_glob::CopyGlobOptionsBuilder;
46//! let options = CopyGlobOptionsBuilder::new().add_exclude("**/some_pattern/*").build();
47//! ```
48
49use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
50use std::fs::create_dir_all;
51use std::path::{Path, PathBuf};
52use walkdir::WalkDir;
53
54pub fn copy_glob(glob: impl AsRef<str>, output_dir: impl AsRef<Path>) {
55    let options = CopyGlobOptionsBuilder::new().build();
56    copy_glob_with(glob, output_dir, &options);
57}
58
59pub fn copy_glob_with(
60    glob: impl AsRef<str>,
61    output_dir: impl AsRef<Path>,
62    options: &CopyGlobOptions,
63) {
64    let glob = glob.as_ref();
65    let matcher = new_glob(glob);
66    let current_dir = options.root.as_ref();
67
68    create_dir_all(&output_dir).unwrap();
69
70    for entry in WalkDir::new(&current_dir) {
71        let entry = entry.expect("Unable to read dir");
72        if options.exclude.is_match(entry.path()) || !matcher.is_match(entry.path()) {
73            continue;
74        }
75
76        let path = entry.path().display();
77        println!("cargo:rerun-if-changed={path}");
78
79        let metadata = entry.metadata().expect("Unable to get metadata");
80        let dirname = get_str(entry.path())
81            .strip_prefix(get_str(current_dir))
82            .expect("Unable to strip a string")
83            .trim_start_matches("\\")
84            .trim_start_matches("/");
85        let output_path = output_dir.as_ref().join(dirname);
86
87        if metadata.is_dir() {
88            create_dir_all(output_path).expect("Unable to create output dir");
89        } else if metadata.is_file() {
90            let parent = output_path.parent().unwrap();
91
92            if parent.exists() {
93                std::fs::copy(entry.path(), output_path).expect("Unable to copy file");
94            } else {
95                std::fs::copy(entry.path(), output_dir.as_ref().join(entry.file_name()))
96                    .expect("Unable to copy file");
97            }
98        }
99    }
100}
101
102pub struct CopyGlobOptions {
103    root: PathBuf,
104    exclude: GlobSet,
105}
106
107pub struct CopyGlobOptionsBuilder {
108    root: PathBuf,
109    exclude: GlobSetBuilder,
110}
111
112impl CopyGlobOptionsBuilder {
113    pub fn new() -> Self {
114        let root = get_root_path();
115        let mut exclude = GlobSet::builder();
116
117        exclude.add(Glob::new("**/target/*").unwrap());
118
119        Self { root, exclude }
120    }
121}
122
123impl CopyGlobOptionsBuilder {
124    pub fn set_root_path(mut self, root: impl AsRef<Path>) -> Self {
125        self.root = root.as_ref().to_path_buf();
126        self
127    }
128
129    pub fn add_exclude(mut self, glob: impl AsRef<str>) -> Self {
130        self.exclude.add(Glob::new(glob.as_ref()).unwrap());
131        self
132    }
133
134    pub fn build(self) -> CopyGlobOptions {
135        CopyGlobOptions {
136            root: self.root,
137            exclude: self.exclude.build().unwrap(),
138        }
139    }
140}
141
142pub fn new_glob(glob: impl AsRef<str>) -> GlobMatcher {
143    Glob::new(glob.as_ref())
144        .expect("Wrong pattern")
145        .compile_matcher()
146}
147
148pub fn get_target_folder() -> PathBuf {
149    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target");
150
151    #[cfg(debug_assertions)]
152    return path.join("debug");
153
154    #[cfg(not(debug_assertions))]
155    return path.join("release");
156}
157
158pub fn get_root_path() -> PathBuf {
159    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
160}
161
162fn get_str(path: &Path) -> &str {
163    path.to_str().expect("Unable to convert path to str")
164}