Restructure crates and switch to Zed

This commit is contained in:
2026-05-31 16:54:30 -06:00
parent 778371b459
commit 4b2f134aaf
13 changed files with 160 additions and 224 deletions
+43
View File
@@ -0,0 +1,43 @@
[package]
name = "ipfilter-cli"
version = "0.0.2"
license-file.workspace = true
edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = [
"color",
"derive",
"help",
"std",
"suggestions",
"usage",
] }
csv = { workspace = true }
ipfilter = { workspace = true, features = ["std"] }
ipnet = { workspace = true }
itertools = { workspace = true }
[lints.clippy]
arithmetic_side_effects = "deny"
as_conversions = "deny"
checked_conversions = "deny"
default_union_representation = "deny"
expect_used = "deny"
indexing_slicing = "deny"
integer_division = "deny"
integer_division_remainder_used = "deny"
transmute_undefined_repr = "deny"
unchecked_time_subtraction = "deny"
unwrap_used = "deny"
[lints.rust]
arithmetic_overflow = "forbid"
unsafe_code = "forbid"
+38
View File
@@ -0,0 +1,38 @@
use std::path::PathBuf;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(short, long, value_name = "FILE")]
pub input: PathBuf,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Print a list of all countries and their codes
List,
/// Merge country IP blocks
Merge {
/// Comma separated list of country codes
#[arg(short, long)]
countries: String,
/// IPv6
#[arg(short, default_value = "false")]
_6: bool,
#[arg(short, long, value_name = "FILE")]
output: Option<PathBuf>,
},
/// Load a
Load {
/// IPv6
#[arg(short, default_value = "false")]
_6: bool,
},
}
+136
View File
@@ -0,0 +1,136 @@
use core::net::{Ipv4Addr, Ipv6Addr};
use std::collections::HashSet;
use std::fs;
use ipfilter::{v4, v6};
use anyhow::{Context, Result};
use clap::Parser;
use ipnet::{Ipv4Subnets, Ipv6Subnets};
mod cli;
use cli::{Cli, Commands};
use itertools::Itertools;
// RiRs
//
// APNIC - https://ftp.apnic.net/stats/apnic/delegated-apnic-extended-latest
// ARIN - https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
// RIPE - https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
// LACNIC - https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
// AFRINIC - https://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
fn main() -> Result<()> {
let cli = Cli::parse();
let input = fs::File::open(cli.input)?;
match cli.command {
Commands::List => {
let mut reader = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(input);
let mut countries = HashSet::<(String, String)>::new();
for result in reader.records() {
let record = result?;
countries.insert((
record
.get(2)
.context("record is missing index 2")?
.to_owned(),
record
.get(3)
.context("record is missing index 3")?
.to_owned(),
));
}
let mut countries: Vec<_> = countries.drain().collect();
countries.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0));
for (code, country) in countries {
println!("{code} - {country}");
}
}
Commands::Merge {
countries,
output,
_6,
} => {
let mut reader = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(input);
let countries: HashSet<String> = countries.split(',').map(ToOwned::to_owned).collect();
let records = reader
.records()
.filter_ok(|r| r.get(2).map(|r| countries.contains(r)).unwrap_or(false))
.map(|r| r.map_err(anyhow::Error::from));
macro_rules! merge_ip {
($v:ident, $net:ty, $addr:ty) => {
let r = records
.map(|r| {
r.and_then(|record| {
Ok(<$net>::new(
<$addr>::from_bits(
record
.get(0)
.context("record is missing index 0")?
.parse()?,
),
<$addr>::from_bits(
record
.get(1)
.context("record is missing index 1")?
.parse()?,
),
0,
))
})
})
.flatten_ok();
let range = $v::from_fallible_iter(r)?;
if let Some(o) = output {
let mut writer = fs::File::create(o)?;
$v::write_to(range, &mut writer)?;
} else {
for subnet in &range {
println!("{subnet}");
}
};
};
}
if _6 {
merge_ip!(v6, Ipv6Subnets, Ipv6Addr);
} else {
merge_ip!(v4, Ipv4Subnets, Ipv4Addr);
}
}
Commands::Load { _6 } => {
macro_rules! load_ip {
($v:ident, $net:ty, $addr:ty) => {
let range = $v::read_from(&mut &input)?;
for subnet in &range {
println!("{subnet}");
}
};
}
if _6 {
load_ip!(v6, Ipv6Subnets, Ipv6Addr);
} else {
load_ip!(v4, Ipv4Subnets, Ipv4Addr);
}
}
}
Ok(())
}