Skip to content

Commit d1371f3

Browse files
committed
release: 0.10.0
2 parents 29054fb + 5700d02 commit d1371f3

File tree

7 files changed

+139
-20
lines changed

7 files changed

+139
-20
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33

44

5+
## [0.10.0](https://github.com/Blobfolio/dowser/releases/tag/v0.10.0) - 2024-10-22
6+
7+
### New
8+
9+
* `Dowser::read_paths_from_file`
10+
11+
### Changed
12+
13+
* Bump MSRV to `1.81`
14+
* Miscellaneous code cleanup and lints
15+
16+
17+
518
## [0.9.3](https://github.com/Blobfolio/dowser/releases/tag/v0.9.3) - 2024-09-05
619

720
### Changed

CREDITS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Project Dependencies
22
Package: dowser
3-
Version: 0.9.3
4-
Generated: 2024-09-05 19:25:18 UTC
3+
Version: 0.10.0
4+
Generated: 2024-10-23 04:07:24 UTC
55

66
| Package | Version | Author(s) | License |
77
| ---- | ---- | ---- | ---- |
88
| [ahash](https://github.com/tkaitchuck/ahash) | 0.8.11 | [Tom Kaitchuck](mailto:[email protected]) | Apache-2.0 or MIT |
99
| [cfg-if](https://github.com/alexcrichton/cfg-if) | 1.0.0 | [Alex Crichton](mailto:[email protected]) | Apache-2.0 or MIT |
10-
| [dactyl](https://github.com/Blobfolio/dactyl) | 0.7.3 | [Blobfolio, LLC.](mailto:[email protected]) | WTFPL |
10+
| [dactyl](https://github.com/Blobfolio/dactyl) | 0.7.4 | [Blobfolio, LLC.](mailto:[email protected]) | WTFPL |
1111
| [zerocopy](https://github.com/google/zerocopy) | 0.7.35 | [Joshua Liebow-Feeser](mailto:[email protected]) | Apache-2.0, BSD-2-Clause, or MIT |

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[package]
22
name = "dowser"
3-
version = "0.9.3"
3+
version = "0.10.0"
44
authors = ["Blobfolio, LLC. <[email protected]>"]
55
edition = "2021"
6-
rust-version = "1.72"
6+
rust-version = "1.81"
77
description = "A recursive, canonicalizing file finding library for Unix."
88
license = "WTFPL"
99
repository = "https://github.com/Blobfolio/dowser"

src/entry.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
# Dowser: Obligatory `DirEntry` Replacement.
33
*/
44

5-
use ahash::AHasher;
65
use std::{
76
fs::DirEntry,
8-
hash::Hasher,
97
io::Result,
108
path::{
119
Path,
@@ -15,6 +13,18 @@ use std::{
1513

1614

1715

16+
/// # Static Hasher.
17+
///
18+
/// This is used for cheap collision detection. No need to get fancy with it.
19+
const AHASHER: ahash::RandomState = ahash::RandomState::with_seeds(
20+
0x8596_cc44_bef0_1aa0,
21+
0x98d4_0948_da60_19ae,
22+
0x49f1_3013_c503_a6aa,
23+
0xc4d7_82ff_3c9f_7bef,
24+
);
25+
26+
27+
1828
/// # File Entry.
1929
///
2030
/// This holds a pre-computed hash, whether or not the path points to a
@@ -69,27 +79,22 @@ impl Entry {
6979

7080
#[cfg(unix)]
7181
#[must_use]
82+
#[inline]
7283
/// # Hash Path.
7384
///
7485
/// Since all paths are canonical, we can test for uniqueness by simply
7586
/// hashing them.
7687
pub(super) fn hash_path(path: &Path) -> u64 {
7788
use std::os::unix::ffi::OsStrExt;
78-
let mut hasher = AHasher::default();
79-
hasher.write(path.as_os_str().as_bytes());
80-
hasher.finish()
89+
AHASHER.hash_one(path.as_os_str().as_bytes())
8190
}
8291

8392
#[cfg(not(unix))]
8493
#[must_use]
94+
#[inline]
8595
/// # Hash Path.
8696
///
8797
/// Since all paths are canonical, we can test for uniqueness by simply
8898
/// hashing them.
89-
pub(super) fn hash_path(path: &Path) -> u64 {
90-
use std::hash::Hash;
91-
let mut hasher = AHasher::default();
92-
path.hash(&mut hasher);
93-
hasher.finish()
94-
}
99+
pub(super) fn hash_path(path: &Path) -> u64 { AHASHER.hash_one(path) }
95100
}

src/ext.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ impl Extension {
545545

546546
/// # Codegen Helpers.
547547
impl Extension {
548-
#[allow(clippy::needless_doctest_main)] // For demonstration.
548+
#[expect(clippy::needless_doctest_main, reason = "For demonstration.")]
549549
#[must_use]
550550
/// # Codegen Helper.
551551
///

src/iter.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,56 @@ impl Dowser {
228228
}
229229
}
230230

231+
impl Dowser {
232+
/// # Load Paths From File.
233+
///
234+
/// Queue up multiple file and/or directory paths from a text file, one
235+
/// entry per line.
236+
///
237+
/// Lines are trimmed and ignored if empty, but otherwise resolved the
238+
/// same as if passed directly to methods like [`Dowser::with_path`], i.e.
239+
/// relative to the current working directory (not the text file itself).
240+
///
241+
/// For that reason, it is recommended that all paths stored in text files
242+
/// be absolute to avoid any ambiguity.
243+
///
244+
/// ## Examples
245+
///
246+
/// ```no_run
247+
/// use dowser::Dowser;
248+
/// use std::path::PathBuf;
249+
///
250+
/// // Read the paths from list.txt.
251+
/// let mut crawler = Dowser::default();
252+
/// crawler.read_paths_from_file("list.txt").unwrap();
253+
///
254+
/// // Crunch into a vec.
255+
/// let files: Vec::<PathBuf> = crawler.collect();
256+
/// ```
257+
///
258+
/// ## Errors
259+
///
260+
/// This method will bubble up any errors encountered while trying to read
261+
/// the text file.
262+
pub fn read_paths_from_file<P: AsRef<Path>>(&mut self, src: P)
263+
-> Result<(), std::io::Error> {
264+
let raw = std::fs::read_to_string(src)?;
265+
for line in raw.lines() {
266+
let line = line.trim();
267+
if ! line.is_empty() {
268+
if let Some(e) = Entry::from_path(line) {
269+
if self.seen.insert(e.hash) {
270+
if e.is_dir { self.dirs.push(e.path); }
271+
else { self.files.push(e.path); }
272+
}
273+
}
274+
}
275+
}
276+
277+
Ok(())
278+
}
279+
}
280+
231281
impl Dowser {
232282
#[must_use]
233283
#[inline]
@@ -581,4 +631,55 @@ mod tests {
581631
let _res = Dowser::default().without_paths([path]);
582632
let _res = Dowser::default().without_paths(&[path.to_path_buf()]);
583633
}
634+
635+
#[test]
636+
fn t_read_paths_from_file() {
637+
use std::collections::BTreeSet;
638+
use std::fs::File;
639+
use std::io::Write;
640+
641+
// Find the temporary directory.
642+
let tmp = std::env::temp_dir();
643+
if ! tmp.is_dir() { return; }
644+
645+
// Declare a few paths to test crawl.
646+
let asset_dir = std::fs::canonicalize("tests/assets")
647+
.expect("Missing dowser assets dir");
648+
let link01 = std::fs::canonicalize("tests/links/01")
649+
.expect("Missing dowser links/01");
650+
651+
// Mock up a text file containing those entries.
652+
let text_file = tmp.join("dowser.test.txt");
653+
let text = format!(
654+
"{}\n{}\n",
655+
asset_dir.as_os_str().to_str().expect("Asset dir cannot be represented as a string."),
656+
link01.as_os_str().to_str().expect("Link01 cannot be represented as a string."),
657+
);
658+
659+
// Try to save the text file.
660+
let res = File::create(&text_file)
661+
.and_then(|mut file|
662+
file.write_all(text.as_bytes()).and_then(|()| file.flush())
663+
);
664+
665+
// Not all environments will allow that; only proceed with the testing
666+
// if it worked.
667+
if res.is_ok() && text_file.is_file() {
668+
// Feed the text file to dowser, collect the results.
669+
let mut crawl = Dowser::default();
670+
crawl.read_paths_from_file(&text_file)
671+
.expect("Loading text file failed.");
672+
let found: BTreeSet<PathBuf> = crawl.collect();
673+
674+
// We don't need the text file anymore.
675+
let _res = std::fs::remove_file(text_file);
676+
677+
// It should have found the following!
678+
assert!(found.len() == 4);
679+
assert!(found.contains(&link01));
680+
assert!(found.contains(&asset_dir.join("file.txt")));
681+
assert!(found.contains(&asset_dir.join("functioning.JPEG")));
682+
assert!(found.contains(&asset_dir.join("is-executable.sh")));
683+
}
684+
}
584685
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ assert_eq!(files1.len(), files2.len());
7474
#![forbid(unsafe_code)]
7575

7676
#![deny(
77-
// TODO: clippy::allow_attributes_without_reason,
77+
clippy::allow_attributes_without_reason,
7878
clippy::correctness,
7979
unreachable_pub,
8080
)]
@@ -86,7 +86,7 @@ assert_eq!(files1.len(), files2.len());
8686
clippy::perf,
8787
clippy::style,
8888
89-
// TODO: clippy::allow_attributes,
89+
clippy::allow_attributes,
9090
clippy::clone_on_ref_ptr,
9191
clippy::create_dir,
9292
clippy::filetype_is_file,
@@ -120,7 +120,7 @@ assert_eq!(files1.len(), files2.len());
120120
unused_import_braces,
121121
)]
122122

123-
#![allow(clippy::redundant_pub_crate)] // Unresolvable.
123+
#![expect(clippy::redundant_pub_crate, reason = "Unresolvable.")]
124124

125125
mod entry;
126126
mod ext;

0 commit comments

Comments
 (0)