Refactor config handling

This commit is contained in:
Agatha Lovelace 2024-01-08 21:19:45 +01:00
parent bd4850969b
commit 66228bbefe
Signed by: sorceress
GPG Key ID: 01D0B3AB10CED4F8
2 changed files with 79 additions and 61 deletions

42
src/config.rs Normal file
View File

@ -0,0 +1,42 @@
use miette::{IntoDiagnostic, Result};
use std::{fs, ops::Deref};
#[derive(knuffel::Decode, Debug)]
pub(crate) struct Category {
#[knuffel(argument)]
pub name: String,
#[knuffel(property, default)]
disabled: bool,
#[knuffel(child, unwrap(arguments))]
pub params: Vec<String>,
}
pub(crate) struct Config(Vec<Category>);
impl Config {
/// Read filter file
pub fn from_file(path: &str) -> Result<Self> {
let filters = fs::read_to_string(path)
.into_diagnostic()
.map_err(|err| err.context(format!("Could not read file `{path}`")))?;
let filters = knuffel::parse::<Vec<Category>>("config.kdl", &filters)?
.into_iter()
.filter(|v| !v.disabled)
.collect::<Vec<Category>>();
Ok(Config(filters))
}
pub fn flat(&self) -> Vec<String> {
self.iter().flat_map(|v| v.params.clone()).collect()
}
}
impl Deref for Config {
type Target = Vec<Category>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@ -1,47 +1,29 @@
use arboard::Clipboard; use arboard::Clipboard;
use memoize::memoize; use memoize::memoize;
use miette::{miette, IntoDiagnostic, Result}; use miette::{miette, Result};
use std::{env, fs, time::Duration}; use std::{env, time::Duration};
use url::Url; use url::Url;
use wildmatch::WildMatch; use wildmatch::WildMatch;
#[derive(knuffel::Decode, Debug)] mod config;
struct Category { use config::Config;
#[knuffel(argument)]
name: String,
#[knuffel(property, default)]
disabled: bool,
#[knuffel(child, unwrap(arguments))]
params: Vec<String>,
}
/// How often should clipboard be checked for changes (0 will result in high CPU usage) /// How often should clipboard be checked for changes (0 will result in high CPU usage)
const ITERATION_DELAY: Duration = Duration::from_millis(250); const ITERATION_DELAY: Duration = Duration::from_millis(250);
fn main() -> Result<()> { fn main() -> Result<()> {
// Get filter file path // Get filter file path
let filters = env::args() let path = env::args()
.nth(1) .nth(1)
.ok_or_else(|| miette!("Please provide a path to a KDL file with parameter filters"))?; .ok_or_else(|| miette!("Please provide a path to a KDL file with parameter filters"))?;
// Read filter file let filters = Config::from_file(&path)?;
let filters = fs::read_to_string(&filters)
.into_diagnostic()
.map_err(|err| err.context(format!("Could not read file `{filters}`")))?;
let filters = knuffel::parse::<Vec<Category>>("config.kdl", &filters)?
.into_iter()
.filter(|v| !v.disabled)
.collect::<Vec<Category>>();
println!("Loaded with categories:"); println!("Loaded with categories:");
for filter in &filters { for filter in &*filters {
println!("\t{}", filter.name); println!("\t{}", filter.name);
} }
// Flatten filters into patterns
let patterns: Vec<String> = filters.iter().flat_map(|v| v.params.clone()).collect();
// Initialize clipboard context // Initialize clipboard context
let mut clipboard = Clipboard::new() let mut clipboard = Clipboard::new()
.map_err(|e| miette!(format!("Could not initialize clipboard context: {e}")))?; .map_err(|e| miette!(format!("Could not initialize clipboard context: {e}")))?;
@ -57,7 +39,7 @@ fn main() -> Result<()> {
// Clipboard changed // Clipboard changed
if contents != last_contents { if contents != last_contents {
last_contents = contents.clone(); last_contents = contents.clone();
if let Ok(url) = clean_url(contents, patterns.clone()) { if let Ok(url) = clean_url(contents, filters.flat()) {
// Update clipboard // Update clipboard
clipboard clipboard
.set_text(url.clone()) .set_text(url.clone())
@ -71,7 +53,7 @@ fn main() -> Result<()> {
#[memoize(Capacity: 1024)] #[memoize(Capacity: 1024)]
fn clean_url(text: String, patterns: Vec<String>) -> Result<String, String> { fn clean_url(text: String, patterns: Vec<String>) -> Result<String, String> {
if let Ok(mut url) = Url::parse(&text) { let mut url = Url::parse(&text).map_err(|e| format!("Contents are not a valid URL: {e}"))?;
let url_inner = url.clone(); let url_inner = url.clone();
// Skip URLs without a host // Skip URLs without a host
@ -81,8 +63,7 @@ fn clean_url(text: String, patterns: Vec<String>) -> Result<String, String> {
for pattern in &patterns { for pattern in &patterns {
let url_inner = url.clone(); let url_inner = url.clone();
match pattern.split_once('@') { if let Some((param, domain)) = pattern.split_once('@') {
Some((param, domain)) => {
if WildMatch::new(domain).matches(host) { if WildMatch::new(domain).matches(host) {
// Filter parameters to exclude blocked entries // Filter parameters to exclude blocked entries
let query = url_inner let query = url_inner
@ -91,8 +72,7 @@ fn clean_url(text: String, patterns: Vec<String>) -> Result<String, String> {
// Replace parameters in URL // Replace parameters in URL
url.query_pairs_mut().clear().extend_pairs(query); url.query_pairs_mut().clear().extend_pairs(query);
} }
} } else {
None => {
// Filter parameters to exclude blocked entries // Filter parameters to exclude blocked entries
let query = url_inner let query = url_inner
.query_pairs() .query_pairs()
@ -101,13 +81,9 @@ fn clean_url(text: String, patterns: Vec<String>) -> Result<String, String> {
url.query_pairs_mut().clear().extend_pairs(query); url.query_pairs_mut().clear().extend_pairs(query);
} }
} }
}
// Handle dangling ?s when no query pairs are appended // Handle dangling ?s when no query pairs are appended
let url = url.as_str().trim_end_matches('?').to_owned(); let url = url.as_str().trim_end_matches('?').to_owned();
Ok(url) Ok(url)
} else {
Err(String::from("Contents are not a valid URL"))
}
} }