1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
//! ref: composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php
use crate::package::archiver::ComposerExcludeFilter;
use crate::package::archiver::GitExcludeFilter;
use crate::util::Filesystem;
use shirabe_external_packages::composer::pcre::Preg;
use shirabe_external_packages::symfony::component::finder::Finder;
use shirabe_external_packages::symfony::component::finder::SplFileInfo;
use shirabe_php_shim::{RuntimeException, preg_quote, realpath};
pub struct ArchivableFilesFinder {
pub(crate) finder: Finder,
inner_iter: Box<dyn Iterator<Item = SplFileInfo>>,
}
impl std::fmt::Debug for ArchivableFilesFinder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArchivableFilesFinder")
.field("finder", &self.finder)
.finish()
}
}
impl ArchivableFilesFinder {
pub fn new(sources: &str, excludes: Vec<String>, ignore_filters: bool) -> anyhow::Result<Self> {
let fs = Filesystem::new(None);
let sources_real_path = realpath(sources);
if sources_real_path.is_none() {
return Err(RuntimeException {
message: format!("Could not realpath() the source directory \"{}\"", sources),
code: 0,
}
.into());
}
let sources = fs.normalize_path(&sources_real_path.unwrap());
let filters: Vec<Box<dyn ArchivableFilesFilter>> = if ignore_filters {
vec![]
} else {
vec![
Box::new(GitExcludeFilter::new(sources.clone())),
Box::new(ComposerExcludeFilter::new(sources.clone(), excludes)),
]
};
let mut finder = Finder::new();
let sources_clone = sources.clone();
let filter = move |file: &SplFileInfo| -> bool {
let realpath = file.get_real_path();
if realpath.is_none() {
return false;
}
let realpath = realpath.unwrap();
if file.is_link() && !realpath.starts_with(sources_clone.as_str()) {
return false;
}
let relative_path = Preg::replace(
&format!("^{}", preg_quote(&sources_clone, Some('#'))),
"",
&fs.normalize_path(&realpath),
)
.unwrap_or_default();
let mut exclude = false;
for f in &filters {
exclude = f.filter(&relative_path, exclude);
}
!exclude
};
finder
.r#in(&sources)
// TODO(phase-b): symfony Finder filter takes Box<dyn Fn(&SplFileInfo) -> bool>; signature not yet wired
.ignore_vcs(true)
.ignore_dot_files(false)
.sort_by_name();
let _ = filter;
let inner_iter: Box<dyn Iterator<Item = SplFileInfo>> = Box::new(finder.get_iterator());
Ok(Self { finder, inner_iter })
}
pub fn accept(&self, current: &SplFileInfo) -> bool {
if !current.is_dir() {
return true;
}
let path = current.get_pathname();
match std::fs::read_dir(&path) {
Ok(mut iter) => iter.next().is_none(),
Err(_) => false,
}
}
}
trait ArchivableFilesFilter {
fn filter(&self, relative_path: &str, exclude: bool) -> bool;
}
impl ArchivableFilesFilter for GitExcludeFilter {
fn filter(&self, relative_path: &str, exclude: bool) -> bool {
self.filter(relative_path, exclude)
}
}
impl ArchivableFilesFilter for ComposerExcludeFilter {
fn filter(&self, relative_path: &str, exclude: bool) -> bool {
self.filter(relative_path, exclude)
}
}
impl Iterator for ArchivableFilesFinder {
type Item = SplFileInfo;
fn next(&mut self) -> Option<Self::Item> {
loop {
let item = self.inner_iter.next()?;
if self.accept(&item) {
return Some(item);
}
}
}
}
|