commit 77e63a36d0be4305481adfb2f79af7df0e71fada
parent 16f45137001497d044b253edbf352b0fd1eb9c64
Author: Zack Newman <zack@philomathiclife.com>
Date: Tue, 25 Feb 2025 09:52:40 -0700
rust 2024
Diffstat:
M | Cargo.toml | | | 25 | +++++++++++-------------- |
M | README.md | | | 27 | ++++++++++++++++++++++++--- |
M | src/app.rs | | | 2 | +- |
M | src/args.rs | | | 475 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
M | src/config.rs | | | 132 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
M | src/dom.rs | | | 88 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
M | src/file.rs | | | 12 | ++++++------ |
M | src/lib.rs | | | 14 | +++++++++++++- |
M | src/main.rs | | | 55 | +++++++++++++++++++++++++++++++++---------------------- |
M | src/priv_sep.rs | | | 15 | +++++++++------ |
10 files changed, 482 insertions(+), 363 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -3,30 +3,27 @@ authors = ["Zack Newman <zack@philomathiclife.com>"]
categories = ["command-line-utilities"]
description = "RPZ file generator based on HTTP(S) URLs and local file paths entered into a config file."
documentation = "https://crates.io/crates/rpz"
-edition = "2021"
+edition = "2024"
keywords = ["adblock", "rpz", "unbound"]
license = "MIT OR Apache-2.0"
name = "rpz"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/rpz/"
-version = "1.0.1"
-
-[badges]
-maintenance = { status = "actively-developed" }
+version = "1.1.0"
[dependencies]
-ascii_domain = { version = "0.6.2", default-features = false }
+ascii_domain = { version = "0.6.3", default-features = false }
num-bigint = { version = "0.4.6", default-features = false }
-reqwest = { version = "0.12.7", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] }
-serde = { version = "1.0.210", default-features = false }
-superset_map = { version = "0.3.0", default-features = false }
-tokio = { version = "1.40.0", default-features = false, features = ["rt", "time"] }
-toml = { version = "0.8.19", default-features = false, features = ["parse"] }
-url = { version = "2.5.2", default-features = false, features = ["serde"] }
-zfc = { version = "0.4.0", default-features = false }
+reqwest = { version = "0.12.12", default-features = false, features = ["brotli", "deflate", "gzip", "rustls-tls-native-roots", "trust-dns"] }
+serde = { version = "1.0.218", default-features = false }
+superset_map = { version = "0.3.1", default-features = false }
+tokio = { version = "1.43.0", default-features = false, features = ["rt", "time"] }
+toml = { version = "0.8.20", default-features = false, features = ["parse"] }
+url = { version = "2.5.4", default-features = false, features = ["serde"] }
+zfc = { version = "0.4.1", default-features = false }
[target.'cfg(target_os = "openbsd")'.dependencies]
-priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true }
+priv_sep = { version = "2.2.0", default-features = false, features = ["openbsd"], optional = true }
### FEATURES #################################################################
diff --git a/README.md b/README.md
@@ -1,4 +1,8 @@
-# rpz
+# `rpz`
+
+[<img alt="git" src="https://git.philomathiclife.com/badges/rpz.svg" height="20">](https://git.philomathiclife.com/rpz/log.html)
+[<img alt="crates.io" src="https://img.shields.io/crates/v/rpz.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/rpz)
+[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-rpz-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/rpz/latest/rpz/)
`rpz` consists of a binary crate and [library crate](https://docs.rs/rpz/latest/rpz).
The binary crate, `rpz`, is an application that downloads, parses, and transforms ad-(un)block files from
@@ -179,12 +183,25 @@ to the total number of domains, comments, blanks, and parsing errors.
Parsing errors are ignored; all other errors are written to `stderr` before program abortion.
+## Minimum Supported Rust Version (MSRV)
+
+This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that
+update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV
+will be updated.
+
+MSRV changes will correspond to a SemVer minor version bump.
+
+## SemVer Policy
+
+* All on-by-default features of this library are covered by SemVer
+* MSRV is considered exempt from SemVer as noted above
+
## License
Licensed under either of
-* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0).
-* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT).
+* Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0))
+* MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT))
at your option.
@@ -193,6 +210,10 @@ at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you,
as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
+Before any PR is sent, `cargo clippy` and `cargo t` should be run for both `--no-default-features` and
+`--all-features`. Additionally `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to
+ensure documentation can be built.
+
### Status
This package is actively maintained.
diff --git a/src/app.rs b/src/app.rs
@@ -198,7 +198,7 @@ impl<'unblock, 'block> Domains<'unblock, 'block> {
) -> Result<usize, Error> {
let action = exclude.map_or(RpzAction::Nxdomain, |_| RpzAction::Passthru);
doms.into_iter().try_fold(0usize, |count, rpz| {
- if exclude.map_or(true, |other| other.contains_proper_superset(rpz)) {
+ if exclude.is_none_or(|other| other.contains_proper_superset(rpz)) {
rpz.write_to_rpz(action, &mut writer)
.map(|()| count.saturating_add(if rpz.is_subdomains() { 2 } else { 1 }))
} else {
diff --git a/src/args.rs b/src/args.rs
@@ -5,7 +5,6 @@ use core::{
use rpz::file::AbsFilePath;
use std::env::{self, Args};
/// Error returned when parsing arguments passed to the application.
-#[expect(clippy::module_name_repetitions, reason = "prefer the name")]
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum ArgsErr {
/// Error when no arguments were passed to the application.
@@ -144,7 +143,7 @@ impl Opts {
return Err(ArgsErr::DuplicateOption("-q/--quiet"));
}
Self::Verbose | Self::ConfigVerbose(_) => {
- return Err(ArgsErr::QuietAndVerbose)
+ return Err(ArgsErr::QuietAndVerbose);
}
Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
},
@@ -166,11 +165,11 @@ impl Opts {
opts = Self::ConfigQuiet(get_path(&mut args)?);
}
Self::Config(_) | Self::ConfigQuiet(_) => {
- return Err(ArgsErr::DuplicateOption("-f/--file"))
+ return Err(ArgsErr::DuplicateOption("-f/--file"));
}
Self::Quiet => return Err(ArgsErr::DuplicateOption("-q/--quiet")),
Self::Verbose | Self::ConfigVerbose(_) => {
- return Err(ArgsErr::QuietAndVerbose)
+ return Err(ArgsErr::QuietAndVerbose);
}
Self::Help | Self::Version => return Err(ArgsErr::MoreThanOneOption),
},
@@ -179,7 +178,7 @@ impl Opts {
opts = Self::ConfigVerbose(get_path(&mut args)?);
}
Self::Config(_) | Self::ConfigVerbose(_) => {
- return Err(ArgsErr::DuplicateOption("-f/--file"))
+ return Err(ArgsErr::DuplicateOption("-f/--file"));
}
Self::Verbose => return Err(ArgsErr::DuplicateOption("-v/--verbose")),
Self::Quiet | Self::ConfigQuiet(_) => return Err(ArgsErr::QuietAndVerbose),
@@ -196,7 +195,7 @@ impl Opts {
}
#[cfg(test)]
mod tests {
- use crate::{test_prog, ArgsErr, E};
+ use crate::{ArgsErr, E, test_prog};
use core::convert;
use std::io::Write;
use std::process::Stdio;
@@ -205,223 +204,259 @@ mod tests {
#[ignore]
fn test_args() {
test_prog::verify_files();
- assert!(test_prog::get_command()
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::NoArgs)).into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-f")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-q")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-v")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-fq")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-qf")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-fv")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-vf")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-h", "-V"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-h", "-h"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!(
- "Error: {:?}\n",
- E::Args(ArgsErr::DuplicateOption("-h/--help"))
- )
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-f", "/home/zack/foo", "-V"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-f", "home/zack/foo"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
+ assert!(
+ test_prog::get_command()
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::NoArgs)).into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-f")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-q")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-v")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-fq")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-qf")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-fv")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-vf")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::ConfigPathNotPassed))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-h", "-V"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-h", "-h"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!(
+ "Error: {:?}\n",
+ E::Args(ArgsErr::DuplicateOption("-h/--help"))
+ )
.into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-f", "/home/zack/foo/"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "/home/zack/foo", "-V"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::MoreThanOneOption))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "home/zack/foo"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "/home/zack/foo/"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::Args(ArgsErr::InvalidConfigPath))
+ .into_bytes()
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-foo")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!(
+ "Error: {:?}\n",
+ E::Args(ArgsErr::InvalidOption(String::from("-foo")))
+ )
.into_bytes()
- }));
- assert!(test_prog::get_command()
- .arg("-foo")
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| {
- !output.status.success()
- && output.stderr
- == format!(
- "Error: {:?}\n",
- E::Args(ArgsErr::InvalidOption(String::from("-foo")))
- )
- .into_bytes()
- }));
- assert!(test_prog::get_command()
- .args(["-f", "-"])
- .stderr(Stdio::piped())
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .output()
- .map_or(false, |output| !output.status.success()
- && output.stderr.get(..23) == Some(b"Error: TOML parse error")));
- assert!(test_prog::get_command()
- .args(["-f", "-"])
- .stderr(Stdio::piped())
- .stdin(Stdio::piped())
- .stdout(Stdio::null())
- .spawn()
- .map_or(false, |mut cmd| {
- cmd.stdin.take().map_or(false, |mut stdin| {
- thread::spawn(move || {
- stdin
- .write_all(b"junk")
- .map_or(false, |_| stdin.flush().map_or(false, |_| true))
+ })
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "-"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .output()
+ .map_or(false, |output| !output.status.success()
+ && output.stderr.get(..23) == Some(b"Error: TOML parse error"))
+ );
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "-"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::null())
+ .spawn()
+ .map_or(false, |mut cmd| {
+ cmd.stdin.take().map_or(false, |mut stdin| {
+ thread::spawn(move || {
+ stdin
+ .write_all(b"junk")
+ .map_or(false, |_| stdin.flush().map_or(false, |_| true))
+ })
+ .join()
+ .map_or(false, convert::identity)
+ }) && cmd.wait_with_output().map_or(false, |output| {
+ !output.status.success()
+ && output.stderr.get(..23) == Some(b"Error: TOML parse error")
})
- .join()
- .map_or(false, convert::identity)
- }) && cmd.wait_with_output().map_or(false, |output| {
- !output.status.success()
- && output.stderr.get(..23) == Some(b"Error: TOML parse error")
})
- }));
- assert!(test_prog::get_command()
- .arg("-h")
- .stderr(Stdio::null())
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .output()
- .map_or(false, |output| output.status.success()
- && output.stdout.get(..crate::HELP.len())
- == Some(crate::HELP.as_bytes())));
- assert!(test_prog::get_command()
- .arg("-V")
- .stderr(Stdio::null())
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .output()
- .map_or(false, |output| output.status.success()
- && output.stdout.get(..crate::VERSION.len())
- == Some(crate::VERSION.as_bytes())));
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-h")
+ .stderr(Stdio::null())
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .output()
+ .map_or(false, |output| output.status.success()
+ && output.stdout.get(..crate::HELP.len())
+ == Some(crate::HELP.as_bytes()))
+ );
+ assert!(
+ test_prog::get_command()
+ .arg("-V")
+ .stderr(Stdio::null())
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .output()
+ .map_or(false, |output| output.status.success()
+ && output.stdout.get(..crate::VERSION.len())
+ == Some(crate::VERSION.as_bytes()))
+ );
}
}
diff --git a/src/config.rs b/src/config.rs
@@ -92,7 +92,7 @@ impl<'de> Deserialize<'de> for Config {
{
/// `Visitor` for `Field`.
struct FieldVisitor;
- impl<'de> Visitor<'de> for FieldVisitor {
+ impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(
@@ -256,10 +256,10 @@ impl<'de> Deserialize<'de> for Config {
}
}
if local_dir.is_none()
- && ad.as_ref().map_or(true, |urls| urls.0.is_empty())
- && dom.as_ref().map_or(true, |urls| urls.0.is_empty())
- && hst.as_ref().map_or(true, |urls| urls.0.is_empty())
- && wc.as_ref().map_or(true, |urls| urls.0.is_empty())
+ && ad.as_ref().is_none_or(|urls| urls.0.is_empty())
+ && dom.as_ref().is_none_or(|urls| urls.0.is_empty())
+ && hst.as_ref().is_none_or(|urls| urls.0.is_empty())
+ && wc.as_ref().is_none_or(|urls| urls.0.is_empty())
{
Err(Error::invalid_type(
Unexpected::Other("no block list URLs or directory"),
@@ -316,86 +316,108 @@ mod tests {
#[test]
fn test_timeout() {
assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
- assert!(toml::from_str::<Config>(
- r#"timeout=15
+ assert!(
+ toml::from_str::<Config>(
+ r#"timeout=15
local_dir="/foo/""#
- )
- .is_ok());
- assert!(toml::from_str::<Config>(
- r#"timeout=4294967295
+ )
+ .is_ok()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"timeout=4294967295
local_dir="/foo/""#
- )
- .is_ok());
- assert!(toml::from_str::<Config>(
- r#"timeout=-1
+ )
+ .is_ok()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"timeout=-1
local_dir="/foo/""#
- )
- .is_err());
- assert!(toml::from_str::<Config>(
- r#"timeout=0
+ )
+ .is_err()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"timeout=0
local_dir="/foo/""#
- )
- .is_ok());
- assert!(toml::from_str::<Config>(
- r#"timeout=4294967296
+ )
+ .is_ok()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"timeout=4294967296
local_dir="/foo/""#
- )
- .is_err());
+ )
+ .is_err()
+ );
}
#[test]
fn test_arrays() {
- assert!(toml::from_str::<Config>(
- r#"adblock=["https://foo.com/foo","https://foo.com/foo"]"#
- )
- .is_err());
+ assert!(
+ toml::from_str::<Config>(r#"adblock=["https://foo.com/foo","https://foo.com/foo"]"#)
+ .is_err()
+ );
assert!(toml::from_str::<Config>(r#"adblock=["https://foo.com/foo"]"#).is_ok());
- assert!(toml::from_str::<Config>(
- r#"adblock=["https://foo.com/foo"]
+ assert!(
+ toml::from_str::<Config>(
+ r#"adblock=["https://foo.com/foo"]
domain=["https://foo.com/foo"]"#
- )
- .is_err());
+ )
+ .is_err()
+ );
}
#[test]
fn test_urls() {
assert!(toml::from_str::<Config>(r#"adblock=["file://foo.com/foo"]"#).is_err());
assert!(toml::from_str::<Config>(r#"adblock=["foo.com/foo"]"#).is_err());
assert!(toml::from_str::<Config>(r#"adblock=["http://foo.com/foo"]"#).is_ok());
- assert!(toml::from_str::<Config>(
- r#"adblock=[]
+ assert!(
+ toml::from_str::<Config>(
+ r#"adblock=[]
domain=["https:///foo"]"#
- )
- .is_ok());
+ )
+ .is_ok()
+ );
assert!(toml::from_str::<Config>(r#"adblock=[""]"#).is_err());
assert!(toml::from_str::<Config>(r#"adblock=["https://"]"#).is_err());
assert!(toml::from_str::<Config>(r#"adblock=["ftp://foo.com/foo"]"#).is_err());
}
#[test]
fn test_paths() {
- assert!(toml::from_str::<Config>(
- r#"rpz="/foo/"
+ assert!(
+ toml::from_str::<Config>(
+ r#"rpz="/foo/"
wildcard=["https://foo.com/foo"]"#
- )
- .is_err());
- assert!(toml::from_str::<Config>(
- r#"rpz="foo"
+ )
+ .is_err()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"rpz="foo"
wildcard=["https://foo.com/foo"]"#
- )
- .is_err());
- assert!(toml::from_str::<Config>(
- r#"rpz="/foo"
+ )
+ .is_err()
+ );
+ assert!(
+ toml::from_str::<Config>(
+ r#"rpz="/foo"
wildcard=["https://foo.com/foo"]"#
- )
- .is_ok());
+ )
+ .is_ok()
+ );
assert!(toml::from_str::<Config>(r#"local_dir="foo/""#).is_err());
assert!(toml::from_str::<Config>(r#"local_dir="/foo/""#).is_ok());
// Directories are allowed to not have a trailing `/`, but they will get it
// added.
- assert!(toml::from_str::<Config>(
- r#"local_dir="/foo"
+ assert!(
+ toml::from_str::<Config>(
+ r#"local_dir="/foo"
wildcard=["https://foo.com/foo"]"#
- )
- .map_or(false, |config| config.local_dir.map_or(false, |dir| dir
- .to_str()
- .map_or(false, |val| val == String::from("/foo/")))));
+ )
+ .map_or(false, |config| config.local_dir.map_or(false, |dir| dir
+ .to_str()
+ .map_or(false, |val| val == String::from("/foo/"))))
+ );
}
}
diff --git a/src/dom.rs b/src/dom.rs
@@ -1,6 +1,6 @@
use crate::dom_count_auto_gen::proper_subdomain_count;
use ascii_domain::{
- char_set::{AllowedAscii, ASCII_FIREFOX},
+ char_set::{ASCII_FIREFOX, AllowedAscii},
dom::{Domain, DomainErr, DomainOrdering},
};
use core::{
@@ -265,7 +265,7 @@ pub struct Adblock<'a> {
/// `true` iff `domain` represents all subdomains. Note that this includes `domain` itself.
subdomains: bool,
}
-impl<'a> Adblock<'a> {
+impl Adblock<'_> {
/// Returns `true` iff the contained [`Domain`] represents all subdomains. Note this includes the
/// `Domain` itself.
#[inline]
@@ -524,7 +524,6 @@ impl Ord for Adblock<'_> {
///
/// For example, `com` `<` `example.com` `<` `||example.com` `<` `||com` `<` `net` `<` `example.net` `<` `||example.net` `<` `||net`.
#[inline]
- #[must_use]
fn cmp(&self, other: &Self) -> Ordering {
match self.domain.cmp_by_domain_ordering(&other.domain) {
DomainOrdering::Less => Ordering::Less,
@@ -734,7 +733,7 @@ pub struct DomainOnly<'a> {
/// The `Domain`.
domain: Domain<&'a str>,
}
-impl<'a> DomainOnly<'a> {
+impl DomainOnly<'_> {
/// Read [`Adblock::cmp_domain_only`].
#[inline]
#[must_use]
@@ -989,7 +988,7 @@ pub struct Hosts<'a> {
/// The `Domain`.
domain: Domain<&'a str>,
}
-impl<'a> Hosts<'a> {
+impl Hosts<'_> {
/// Read [`Adblock::cmp_hosts`].
#[inline]
#[must_use]
@@ -1262,7 +1261,7 @@ pub struct Wildcard<'a> {
/// `true` iff `domain` represents all proper subdomains. Note that this does _not_ include `domain` itself.
proper_subdomains: bool,
}
-impl<'a> Wildcard<'a> {
+impl Wildcard<'_> {
/// Returns `true` iff the contained [`Domain`] represents all proper subdomains. Note this does _not_
/// include the `Domain` itself.
#[inline]
@@ -1392,7 +1391,6 @@ impl Ord for Wildcard<'_> {
///
/// For example, `com` `<` `example.com` `<` `*.example.com` `<` `*.com` `<` `net` `<` `example.net` `<` `*.example.net` `<` `*.net`.
#[inline]
- #[must_use]
fn cmp(&self, other: &Self) -> Ordering {
match self.domain.cmp_by_domain_ordering(&other.domain) {
DomainOrdering::Less => Ordering::Less,
@@ -1584,7 +1582,7 @@ pub enum RpzDomain<'a> {
/// A `Wildcard` domain.
Wildcard(Wildcard<'a>),
}
-impl<'a> RpzDomain<'a> {
+impl RpzDomain<'_> {
/// Returns `true` iff `self` represents a single [`Domain`].
#[inline]
#[must_use]
@@ -2026,25 +2024,31 @@ mod tests {
})
);
// Test blank.
- assert!(DomainOnly::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank)));
+ assert!(
+ DomainOnly::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank))
+ );
// Test blank.
- assert!(DomainOnly::parse_value("example.xn--abc")
- .map_or(false, |val| matches!(val, Value::Domain(_))));
+ assert!(
+ DomainOnly::parse_value("example.xn--abc")
+ .map_or(false, |val| matches!(val, Value::Domain(_)))
+ );
// Test invalid TLD.
- assert!(DomainOnly::parse_value("www.c1m")
- .map_or_else(|err| err == FirefoxDomainErr::InvalidTld, |_| false));
+ assert!(
+ DomainOnly::parse_value("www.c1m")
+ .map_or_else(|err| err == FirefoxDomainErr::InvalidTld, |_| false)
+ );
}
#[test]
fn test_hosts_parse_value() {
// Test whitespace and comment.
- assert!(Hosts::parse_value(
- " \t\t 127.0.0.1\t\t \twww.example.com#asdflkj asdf alskdfj "
- )
- .map_or(false, |val| match val {
- Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com",
- Value::Comment(_) | Value::Blank => false,
- }));
+ assert!(
+ Hosts::parse_value(" \t\t 127.0.0.1\t\t \twww.example.com#asdflkj asdf alskdfj ")
+ .map_or(false, |val| match val {
+ Value::Domain(ref dom) => dom.domain.as_bytes() == b"www.example.com",
+ Value::Comment(_) | Value::Blank => false,
+ })
+ );
assert!(
Hosts::parse_value(" \t\t 0.0.0.0\t\t \twww.example.com \t\t ^ \t\t ")
.map_or_else(
@@ -2057,14 +2061,22 @@ mod tests {
|_| false
));
// Test invalid IP
- assert!(Hosts::parse_value("::2 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false));
- assert!(Hosts::parse_value(":2 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false));
- assert!(Hosts::parse_value("www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false));
- assert!(Hosts::parse_value("10.4.2.256 www.example.com")
- .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false));
+ assert!(
+ Hosts::parse_value("::2 www.example.com")
+ .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ );
+ assert!(
+ Hosts::parse_value(":2 www.example.com")
+ .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ );
+ assert!(
+ Hosts::parse_value("www.example.com")
+ .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ );
+ assert!(
+ Hosts::parse_value("10.4.2.256 www.example.com")
+ .map_or_else(|e| e == FirefoxDomainErr::InvalidHostsIP, |_| false)
+ );
// Test case-insensitivity.
assert!(
Hosts::parse_value(":: www.ExAMPle.Com").map_or(false, |val| match val {
@@ -2085,8 +2097,10 @@ mod tests {
})
);
// Test blank.
- assert!(Hosts::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank)));
+ assert!(
+ Hosts::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank))
+ );
}
#[test]
fn test_wildcard_parse_value() {
@@ -2195,8 +2209,10 @@ mod tests {
})
);
// Test blank.
- assert!(Wildcard::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank)));
+ assert!(
+ Wildcard::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank))
+ );
}
#[test]
fn test_rpz_parse_value() {
@@ -2253,8 +2269,10 @@ mod tests {
})
);
// Test blank.
- assert!(RpzDomain::parse_value(" \t\t \t\t \t ")
- .map_or(false, |val| matches!(val, Value::Blank)));
+ assert!(
+ RpzDomain::parse_value(" \t\t \t\t \t ")
+ .map_or(false, |val| matches!(val, Value::Blank))
+ );
}
#[test]
fn test_rpz_ord_and_eq() -> Result<(), &'static str> {
diff --git a/src/file.rs b/src/file.rs
@@ -40,7 +40,7 @@ impl<const IS_DIR: bool> AbsFilePath<IS_DIR> {
let bytes = val.as_bytes();
if IS_DIR {
self.path.as_mut_os_string().push(val);
- bytes.last().map_or(true, |byt| {
+ bytes.last().is_none_or(|byt| {
if *byt != b'/' {
self.path.as_mut_os_string().push("/");
}
@@ -50,15 +50,15 @@ impl<const IS_DIR: bool> AbsFilePath<IS_DIR> {
// with `/` as well as verify the last component is not
// `..` which is true iff `val == ".."` or the last 3
// characters of `val` is `/..`.
- } else if bytes.last().map_or(false, |byt| {
+ } else if bytes.last().is_some_and(|byt| {
*byt == b'/'
|| bytes
.get(bytes.len().wrapping_sub(2)..)
- .map_or(false, |byts| {
+ .is_some_and(|byts| {
byts == b".."
&& bytes
.get(bytes.len().wrapping_sub(3))
- .map_or(true, |byt2| *byt2 == b'/')
+ .is_none_or(|byt2| *byt2 == b'/')
})
}) {
false
@@ -139,7 +139,7 @@ impl<'de, const IS_DIR: bool> Deserialize<'de> for AbsFilePath<IS_DIR> {
{
/// `Visitor` for `AbsFilePath`.
struct FilePathVisitor<const IS_DIR: bool>;
- impl<'de, const IS_DIR: bool> Visitor<'de> for FilePathVisitor<IS_DIR> {
+ impl<const IS_DIR: bool> Visitor<'_> for FilePathVisitor<IS_DIR> {
type Value = AbsFilePath<IS_DIR>;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("struct AbsFilePath")
@@ -321,7 +321,7 @@ impl<'de> Deserialize<'de> for HttpUrl {
{
/// `Visitor` for `HttpUrl`.
struct UrlVisitor;
- impl<'d> Visitor<'d> for UrlVisitor {
+ impl Visitor<'_> for UrlVisitor {
type Value = HttpUrl;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("struct HttpUrl")
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,4 +1,10 @@
-//! # `rpz`
+//! [![git]](https://git.philomathiclife.com/rpz/log.html) [![crates-io]](https://crates.io/crates/rpz) [![docs-rs]](crate)
+//!
+//! [git]: https://git.philomathiclife.com/git_badge.svg
+//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
+//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
+//!
+//! `rpz` is a library for the binary `rpz`.
//!
//! The primary module is [`mod@dom`] which contains types which can parse
//! [`str`]s into different kinds of domains.
@@ -34,7 +40,12 @@
clippy::style,
clippy::suspicious
)]
+#![allow(
+ unknown_lints,
+ reason = "OpenBSD does not recognize clippy::return_and_then"
+)]
#![expect(
+ clippy::arbitrary_source_item_ordering,
clippy::blanket_clippy_restriction_lints,
clippy::exhaustive_enums,
clippy::exhaustive_structs,
@@ -44,6 +55,7 @@
clippy::multiple_crate_versions,
clippy::question_mark_used,
clippy::ref_patterns,
+ clippy::return_and_then,
clippy::single_char_lifetime_names,
reason = "never want to use these lints"
)]
diff --git a/src/main.rs b/src/main.rs
@@ -26,7 +26,13 @@
clippy::style,
clippy::suspicious
)]
+#![allow(
+ unknown_lints,
+ reason = "OpenBSD does not recognize clippy::return_and_then"
+)]
+#![allow(clippy::pub_use, reason = "when priv_sep is used, we export UnveilErr")]
#![expect(
+ clippy::arbitrary_source_item_ordering,
clippy::blanket_clippy_restriction_lints,
clippy::implicit_return,
clippy::min_ident_chars,
@@ -34,6 +40,7 @@
clippy::multiple_crate_versions,
clippy::question_mark_used,
clippy::ref_patterns,
+ clippy::return_and_then,
clippy::single_call_fn,
clippy::single_char_lifetime_names,
clippy::unseparated_literal_suffix,
@@ -69,7 +76,7 @@ use rpz::{
use std::{
collections::HashSet,
fs,
- io::{self, Read, Write},
+ io::{self, Read as _, Write as _},
sync::OnceLock,
};
use tokio::runtime::Builder;
@@ -495,7 +502,7 @@ pub(crate) mod test_prog {
}
#[cfg(test)]
mod tests {
- use crate::{test_prog, E};
+ use crate::{E, test_prog};
use std::io::Write;
use std::process::Stdio;
use std::thread;
@@ -503,28 +510,32 @@ mod tests {
#[ignore]
fn test_app() {
test_prog::verify_files();
- assert!(test_prog::get_command()
- .args(["-f", "-"])
- .stderr(Stdio::piped())
- .stdin(Stdio::piped())
- .stdout(Stdio::null())
- .spawn()
- .map_or(false, |mut cmd| {
- cmd.stdin.take().map_or(false, |mut stdin| {
- thread::spawn(move || {
- stdin
- .write_all(
- format!("local_dir=\"{}\"", test_prog::PROG_DIR_NO_SUB).as_bytes(),
- )
- .map_or(false, |_| true)
+ assert!(
+ test_prog::get_command()
+ .args(["-f", "-"])
+ .stderr(Stdio::piped())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::null())
+ .spawn()
+ .map_or(false, |mut cmd| {
+ cmd.stdin.take().map_or(false, |mut stdin| {
+ thread::spawn(move || {
+ stdin
+ .write_all(
+ format!("local_dir=\"{}\"", test_prog::PROG_DIR_NO_SUB)
+ .as_bytes(),
+ )
+ .map_or(false, |_| true)
+ })
+ .join()
+ .map_or(false, |v| v)
+ }) && cmd.wait_with_output().map_or(false, |output| {
+ !output.status.success()
+ && output.stderr
+ == format!("Error: {:?}\n", E::NoBlockEntries).into_bytes()
})
- .join()
- .map_or(false, |v| v)
- }) && cmd.wait_with_output().map_or(false, |output| {
- !output.status.success()
- && output.stderr == format!("Error: {:?}\n", E::NoBlockEntries).into_bytes()
})
- }));
+ );
assert!(test_prog::get_command()
.args(["-vf", "-"])
.stderr(Stdio::null())
diff --git a/src/priv_sep.rs b/src/priv_sep.rs
@@ -52,7 +52,7 @@ pub fn pledge_away_create_write(promises: &mut Promises) -> Result<(), Error> {
/// No-op that always returns `Ok`.
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-pub fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> {
+pub const fn pledge_away_create_write(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes `Promise::Unveil`.
@@ -63,7 +63,7 @@ pub fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> {
/// No-op that always returns `Ok`.
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-pub fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> {
+pub const fn pledge_away_unveil(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes `Promise::Dns` and `Promise::Inet`.
@@ -74,7 +74,7 @@ pub fn pledge_away_net(promises: &mut Promises) -> Result<(), Error> {
/// No-op that always returns `Ok`.
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-pub fn pledge_away_net(_: &mut Zst) -> Result<(), !> {
+pub const fn pledge_away_net(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Removes all `Promise`s except `Stdio`.
@@ -85,7 +85,7 @@ pub fn pledge_away_all_but_stdio(promises: &mut Promises) -> Result<(), Error> {
/// No-op that always returns `Ok`.
#[expect(clippy::unnecessary_wraps, reason = "need to align with OpenBSD code")]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
-pub fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> {
+pub const fn pledge_away_all_but_stdio(_: &mut Zst) -> Result<(), !> {
Ok(())
}
/// Calls `unveil`_on `path` with no `Permissions`.
@@ -123,7 +123,10 @@ pub fn unveil_create<P: AsRef<Path>>(_: P) -> Result<(), !> {
}
/// Calls `unveil`_on `path` with `Permissions::READ` and returns
/// `true` iff `path` exists.
-#[expect(clippy::wildcard_enum_match_arm)]
+#[expect(
+ clippy::wildcard_enum_match_arm,
+ reason = "ErrorKind is non_exhaustive"
+)]
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
pub fn unveil_read_dir<P: AsRef<Path>>(path: P) -> Result<bool, UnveilErr> {
Permissions::READ.unveil(path).map_or_else(
@@ -167,7 +170,7 @@ pub fn unveil_read_file<P: AsRef<Path>>(_: P) -> Result<(), !> {
/// in addition to setting the `SSL_CERT_FILE` variable to `/etc/ssl/cert.pem`.
/// We rely on `SSL_CERT_FILE` as that allows one to `unveil(2)` only `/etc/ssl/cert.pem`
/// instead of `/etc/ssl/`.
-#[expect(unsafe_code)]
+#[expect(unsafe_code, reason = "safety comment justifies its correctness")]
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
pub fn unveil_https() -> Result<(), UnveilErr> {
/// The path to the root certificate store.