Skip to content

Commit 4df6e6e

Browse files
authored
Merge pull request #54 from VolumeGraphics/file-exist-checker
File exist checker
2 parents 9fbcb77 + 446ff4c commit 4df6e6e

File tree

10 files changed

+499
-35
lines changed

10 files changed

+499
-35
lines changed

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description = "A flexible rule-based file and folder comparison tool and crate i
44
repository = "https://github.com/VolumeGraphics/havocompare"
55
homepage = "https://github.com/VolumeGraphics/havocompare"
66
documentation = "https://docs.rs/havocompare"
7-
version = "0.6.1"
7+
version = "0.7.0"
88
edition = "2021"
99
license = "MIT"
1010
authors = ["Volume Graphics GmbH"]
@@ -25,7 +25,7 @@ serde = "1.0"
2525
serde_yaml = "0.9"
2626
schemars = "0.8"
2727
schemars_derive = "0.8"
28-
thiserror = "1.0"
28+
thiserror = "2.0"
2929
regex = "1.10"
3030
image = "0.25"
3131
image-compare = "0.4"
@@ -35,16 +35,16 @@ serde_json = "1.0"
3535
glob = "0.3"
3636
test-log = { version = "0.2", features = ["trace"] }
3737
strsim = "0.11"
38-
itertools = "0.13"
38+
itertools = "0.14"
3939
tera = "1.19"
4040
sha2 = "0.10"
4141
data-encoding = "2.6"
4242
permutation = "0.4"
43-
pdf-extract = "0.7"
43+
pdf-extract = "0.9"
4444
vg_errortools = "0.1"
4545
rayon = "1.10.0"
4646
enable-ansi-support = "0.2"
47-
tempfile = "3.10"
47+
tempfile = "3.20"
4848
fs_extra = "1.3"
4949
opener = "0.7"
5050
anyhow = "1.0"

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,26 @@ rules:
276276
- "ignore_this_key(s?)"
277277
```
278278

279+
#### File Exists / Directory Comparison
280+
281+
Compares directory structure and file existence in both paths.
282+
283+
```yaml
284+
rules:
285+
- name: "Directory Checker"
286+
# to check directory structure only (ignoring the files) use "**/*/" and remove pattern_exclude
287+
# to check all files and directory use "**/*" and remove pattern_exclude
288+
# to check files only uses "**/*.*" but this works only in windows. Or use "**/*" and pattern_exclude "**/*/"
289+
pattern_include:
290+
- "**/*"
291+
pattern_exclude:
292+
- "**/*/"
293+
Directory:
294+
# Mode Identical to check whether both paths are really the same: whether entry is missing in actual, and/or if entry exists in actual but not in nominal
295+
# Mode MissingOnly to check only if entry is missing in actual, ignoring entries that exist in actual but not in nominal
296+
mode: Identical
297+
```
298+
279299
### Use HavoCompare in your unit-tests
280300

281301
1. Add havocompare to your dev-dependencies:
@@ -306,6 +326,12 @@ rules:
306326

307327
## Changelog
308328

329+
### 0.7.0
330+
331+
- add file exist checker
332+
- fix file property checker report that "Creation date" of "nominal" and "actual" columns are switched
333+
- update thiserror, itertools and pdf-extract crates
334+
309335
### 0.6.1
310336

311337
- Add new version of json-diff, featuring a better API and better filtering options

src/directory.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
use crate::report::{DiffDetail, Difference};
2+
use schemars_derive::JsonSchema;
3+
use serde::{Deserialize, Serialize};
4+
use std::path;
5+
use std::path::{Path, PathBuf};
6+
use thiserror::Error;
7+
use tracing::error;
8+
9+
#[derive(Debug, Error)]
10+
/// Errors during html / plain text checking
11+
pub enum Error {
12+
#[error("Failed to remove path's prefix")]
13+
StripPrefixError(#[from] path::StripPrefixError),
14+
}
15+
16+
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
17+
pub struct DirectoryConfig {
18+
pub mode: Mode,
19+
}
20+
21+
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
22+
pub enum Mode {
23+
/// check whether both paths are really the same: whether entry is missing in actual, and/or if entry exists in actual but not in nominal
24+
Identical,
25+
/// check only if entry is missing in actual, ignoring entries that exist in actual but not in nominal
26+
MissingOnly,
27+
}
28+
29+
pub(crate) fn compare_paths<P: AsRef<Path>>(
30+
nominal: P,
31+
actual: P,
32+
nominal_entries: &[PathBuf],
33+
actual_entries: &[PathBuf],
34+
config: &DirectoryConfig,
35+
) -> Result<Difference, Error> {
36+
let nominal_path = nominal.as_ref();
37+
let actual_path = actual.as_ref();
38+
39+
let mut difference = Difference::new_for_file(nominal_path, actual_path);
40+
41+
//remove root paths!
42+
let nominal_entries: Result<Vec<_>, path::StripPrefixError> = nominal_entries
43+
.iter()
44+
.map(|path| path.strip_prefix(nominal_path))
45+
.collect();
46+
let nominal_entries = nominal_entries?;
47+
48+
let actual_entries: Result<Vec<_>, path::StripPrefixError> = actual_entries
49+
.iter()
50+
.map(|path| path.strip_prefix(actual_path))
51+
.collect();
52+
let actual_entries = actual_entries?;
53+
54+
let mut is_the_same = true;
55+
if matches!(config.mode, Mode::Identical | Mode::MissingOnly) {
56+
nominal_entries.iter().for_each(|entry| {
57+
let detail = if let Some(f) = actual_entries.iter().find(|a| *a == entry) {
58+
(f.to_string_lossy().to_string(), false)
59+
} else {
60+
error!("{} doesn't exist in the actual folder", entry.display());
61+
is_the_same = false;
62+
("".to_owned(), true)
63+
};
64+
65+
difference.push_detail(DiffDetail::File {
66+
nominal: entry.to_string_lossy().to_string(),
67+
actual: detail.0,
68+
error: detail.1,
69+
});
70+
});
71+
}
72+
73+
if matches!(config.mode, Mode::Identical) {
74+
actual_entries.iter().for_each(|entry| {
75+
if !nominal_entries.iter().any(|n| n == entry) {
76+
difference.push_detail(DiffDetail::File {
77+
nominal: "".to_owned(),
78+
actual: entry.to_string_lossy().to_string(),
79+
error: true,
80+
});
81+
82+
error!(
83+
"Additional entry {} found in the actual folder",
84+
entry.display()
85+
);
86+
is_the_same = false;
87+
}
88+
});
89+
}
90+
91+
if !is_the_same {
92+
difference.error();
93+
}
94+
95+
Ok(difference)
96+
}
97+
98+
#[cfg(test)]
99+
100+
mod test {
101+
use super::*;
102+
103+
#[test]
104+
fn test_compare_directories() {
105+
let nominal_dir = tempfile::tempdir().expect("Could not create nominal temp dir");
106+
107+
std::fs::create_dir_all(nominal_dir.path().join("dir/a/aa"))
108+
.expect("Could not create directory");
109+
std::fs::create_dir_all(nominal_dir.path().join("dir/b"))
110+
.expect("Could not create directory");
111+
std::fs::create_dir_all(nominal_dir.path().join("dir/c"))
112+
.expect("Could not create directory");
113+
114+
let actual_dir = tempfile::tempdir().expect("Could not create actual temp dir");
115+
116+
std::fs::create_dir_all(actual_dir.path().join("dir/a/aa"))
117+
.expect("Could not create directory");
118+
std::fs::create_dir_all(actual_dir.path().join("dir/b"))
119+
.expect("Could not create directory");
120+
std::fs::create_dir_all(actual_dir.path().join("dir/c"))
121+
.expect("Could not create directory");
122+
123+
let pattern_include = ["**/*/"];
124+
let pattern_exclude: Vec<String> = Vec::new();
125+
126+
let nominal_entries = crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude)
127+
.expect("Could not get files");
128+
let actual_entries = crate::get_files(&actual_dir, &pattern_include, &pattern_exclude)
129+
.expect("Could not get files");
130+
131+
let result = compare_paths(
132+
nominal_dir.path(),
133+
actual_dir.path(),
134+
&nominal_entries,
135+
&actual_entries,
136+
&DirectoryConfig {
137+
mode: Mode::Identical,
138+
},
139+
)
140+
.expect("Could not compare paths");
141+
142+
assert!(!result.is_error);
143+
144+
std::fs::create_dir_all(actual_dir.path().join("dir/d"))
145+
.expect("Could not create directory");
146+
147+
let nominal_entries = crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude)
148+
.expect("Could not create directory");
149+
let actual_entries = crate::get_files(&actual_dir, &pattern_include, &pattern_exclude)
150+
.expect("Could not create directory");
151+
152+
let result = compare_paths(
153+
nominal_dir.path(),
154+
actual_dir.path(),
155+
&nominal_entries,
156+
&actual_entries,
157+
&DirectoryConfig {
158+
mode: Mode::Identical,
159+
},
160+
)
161+
.expect("Could not compare paths");
162+
163+
assert!(result.is_error);
164+
165+
let result = compare_paths(
166+
nominal_dir.path(),
167+
actual_dir.path(),
168+
&nominal_entries,
169+
&actual_entries,
170+
&DirectoryConfig {
171+
mode: Mode::MissingOnly,
172+
},
173+
)
174+
.expect("Could not compare paths");
175+
176+
assert!(!result.is_error);
177+
178+
std::fs::create_dir_all(nominal_dir.path().join("dir/d"))
179+
.expect("Could not create directory");
180+
std::fs::create_dir_all(nominal_dir.path().join("dir/e"))
181+
.expect("Could not create directory");
182+
183+
let nominal_entries = crate::get_files(&nominal_dir, &pattern_include, &pattern_exclude)
184+
.expect("Could not create directory");
185+
let actual_entries = crate::get_files(&actual_dir, &pattern_include, &pattern_exclude)
186+
.expect("Could not create directory");
187+
188+
let result = compare_paths(
189+
nominal_dir.path(),
190+
actual_dir.path(),
191+
&nominal_entries,
192+
&actual_entries,
193+
&DirectoryConfig {
194+
mode: Mode::Identical,
195+
},
196+
)
197+
.expect("Could not compare paths");
198+
199+
assert!(result.is_error);
200+
201+
let result = compare_paths(
202+
nominal_dir.path(),
203+
actual_dir.path(),
204+
&nominal_entries,
205+
&actual_entries,
206+
&DirectoryConfig {
207+
mode: Mode::MissingOnly,
208+
},
209+
)
210+
.expect("Could not compare paths");
211+
212+
assert!(result.is_error);
213+
}
214+
}

src/hash.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use thiserror::Error;
88
use vg_errortools::fat_io_wrap_std;
99
use vg_errortools::FatIOError;
1010

11-
use crate::{Deserialize, report, Serialize};
1211
use crate::report::{DiffDetail, Difference};
12+
use crate::{report, Deserialize, Serialize};
1313

1414
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy)]
1515
pub enum HashFunction {

src/image.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
77
use thiserror::Error;
88
use tracing::error;
99

10-
use crate::{get_file_name, report};
1110
use crate::report::DiffDetail;
11+
use crate::{get_file_name, report};
1212

1313
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
1414
pub enum RGBACompareMode {
@@ -27,6 +27,7 @@ impl Default for RGBACompareMode {
2727
}
2828
}
2929

30+
#[allow(clippy::upper_case_acronyms)]
3031
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone, Default)]
3132
pub enum RGBCompareMode {
3233
///Comparing rgb images using structure. RGB structure similarity is performed by doing a channel split and taking the maximum deviation (minimum similarity) for the result. The image contains the complete deviations. Algorithm: RMS
@@ -38,6 +39,7 @@ pub enum RGBCompareMode {
3839
Hybrid,
3940
}
4041

42+
#[allow(clippy::upper_case_acronyms)]
4143
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
4244
/// The distance algorithm to use for grayscale comparison, see
4345
/// https://github.com/ChrisRega/image-compare for equations
@@ -70,6 +72,7 @@ pub enum GrayCompareMode {
7072
Histogram(GrayHistogramCompareMetric),
7173
}
7274

75+
#[allow(clippy::upper_case_acronyms)]
7376
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone)]
7477
pub enum CompareMode {
7578
/// Compare images as RGB

src/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use schemars_derive::JsonSchema;
77
use serde::{Deserialize, Serialize};
88
use tracing::error;
99

10-
use crate::Error;
1110
use crate::report::{DiffDetail, Difference};
11+
use crate::Error;
1212

1313
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
1414
/// configuration for the json compare module

0 commit comments

Comments
 (0)