diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 6d36852..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,11 +0,0 @@ -comment_width = 80 -group_imports = "StdExternalCrate" -imports_granularity = "Module" -max_width = 80 -newline_style = "Unix" -normalize_comments = true -normalize_doc_attributes = true -reorder_impl_items = true -tab_spaces = 2 -version = "Two" -wrap_comments = true diff --git a/src/clipboard.rs b/src/clipboard.rs index c49649c..0a94a9c 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -5,24 +5,23 @@ use std::time::Duration; use tracing::{debug, error}; pub fn monitor(config: &Replacements) -> Result<()> { - loop { - let mut clipboard = - ClipboardContext::new().expect("Failed to get clipboard"); - if let Ok(contents) = clipboard.get_contents() { - if let Some(subst) = config - .substitutors - .iter() - .find(|subst| subst.matcher.check_match(&contents)) - { - if !subst.name.is_empty() { - debug!(?subst.name, ?contents); + loop { + let mut clipboard = ClipboardContext::new().expect("Failed to get clipboard"); + if let Ok(contents) = clipboard.get_contents() { + if let Some(subst) = config + .substitutors + .iter() + .find(|subst| subst.matcher.check_match(&contents)) + { + if !subst.name.is_empty() { + debug!(?subst.name, ?contents); + } + let result = subst.action.apply_action(&contents); + if let Err(e) = clipboard.set_contents(result) { + error!("{}", e); + } + }; } - let result = subst.action.apply_action(&contents); - if let Err(e) = clipboard.set_contents(result) { - error!("{}", e); - } - }; + std::thread::sleep(Duration::from_millis(1_000)); } - std::thread::sleep(Duration::from_millis(1_000)); - } } diff --git a/src/config.rs b/src/config.rs index eddc887..44d69a6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,123 +1,123 @@ use std::str::FromStr; -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; use regex::Regex; use serde_derive::Deserialize; use tracing::trace; #[derive(Debug, Deserialize)] pub struct Replacements { - #[serde(rename = "substitutor", default)] - pub substitutors: Vec, + #[serde(rename = "substitutor", default)] + pub substitutors: Vec, } #[derive(Debug, Deserialize)] pub struct Substitutor { - #[serde(default)] - pub name: String, - #[serde(alias = "matcher")] - pub matcher: MatcherType, - pub action: Action, + #[serde(default)] + pub name: String, + #[serde(alias = "matcher")] + pub matcher: MatcherType, + pub action: Action, } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum MatcherType { - Single(Matcher), - Multiple(Vec), + Single(Matcher), + Multiple(Vec), } #[derive(Debug, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Matcher { - StartsWith { prefix: String }, - EndsWith { suffix: String }, - Contains { substring: String }, - Regex { pattern: String }, - Exactly { content: String }, + StartsWith { prefix: String }, + EndsWith { suffix: String }, + Contains { substring: String }, + Regex { pattern: String }, + Exactly { content: String }, } #[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Action { - Set { content: String }, - Replace { from: String, to: String }, - Prefix { prefix: String }, - Suffix { suffix: String }, + Set { content: String }, + Replace { from: String, to: String }, + Prefix { prefix: String }, + Suffix { suffix: String }, } pub trait Match { - fn check_match(&self, string: &str) -> bool; + fn check_match(&self, string: &str) -> bool; } pub trait Act { - fn apply_action(&self, input: &str) -> String; + fn apply_action(&self, input: &str) -> String; } impl Replacements { - pub fn validate(&self) -> Result<()> { - for subst in &self.substitutors { - match &subst.matcher { - MatcherType::Single(matcher) => { - if let Matcher::Regex { pattern } = matcher { - if let Err(e) = Regex::from_str(pattern) { - bail!(e); + pub fn validate(&self) -> Result<()> { + for subst in &self.substitutors { + match &subst.matcher { + MatcherType::Single(matcher) => { + if let Matcher::Regex { pattern } = matcher { + if let Err(e) = Regex::from_str(pattern) { + bail!(e); + } + } + } + MatcherType::Multiple(matchers) => { + for matcher in matchers { + if let Matcher::Regex { pattern } = matcher { + if let Err(e) = Regex::from_str(pattern) { + bail!(e); + } + } + } + } } - } } - MatcherType::Multiple(matchers) => { - for matcher in matchers { - if let Matcher::Regex { pattern } = matcher { - if let Err(e) = Regex::from_str(pattern) { - bail!(e); - } - } - } - } - } + Ok(()) } - Ok(()) - } } impl Match for Matcher { - fn check_match(&self, string: &str) -> bool { - trace!(?self, ?string, "Checking for match"); - match self { - Matcher::StartsWith { prefix } => string.starts_with(prefix), - Matcher::EndsWith { suffix } => string.ends_with(suffix), - Matcher::Contains { substring } => string.contains(substring), - Matcher::Regex { pattern } => { - if let Ok(regex) = Regex::from_str(pattern) { - regex.is_match(string) - } else { - false + fn check_match(&self, string: &str) -> bool { + trace!(?self, ?string, "Checking for match"); + match self { + Matcher::StartsWith { prefix } => string.starts_with(prefix), + Matcher::EndsWith { suffix } => string.ends_with(suffix), + Matcher::Contains { substring } => string.contains(substring), + Matcher::Regex { pattern } => { + if let Ok(regex) = Regex::from_str(pattern) { + regex.is_match(string) + } else { + false + } + } + Matcher::Exactly { content } => string == content, } - } - Matcher::Exactly { content } => string == content, } - } } impl Match for MatcherType { - fn check_match(&self, string: &str) -> bool { - match self { - MatcherType::Single(matcher) => matcher.check_match(string), - MatcherType::Multiple(matchers) => { - matchers.iter().all(|matcher| matcher.check_match(string)) - } + fn check_match(&self, string: &str) -> bool { + match self { + MatcherType::Single(matcher) => matcher.check_match(string), + MatcherType::Multiple(matchers) => { + matchers.iter().all(|matcher| matcher.check_match(string)) + } + } } - } } impl Act for Action { - fn apply_action(&self, input: &str) -> String { - trace!(?self, ?input, "Applying action"); - match self { - Action::Replace { from, to } => input.replace(from, to), - Action::Prefix { prefix } => format!("{prefix}{input}"), - Action::Suffix { suffix } => format!("{input}{suffix}"), - Action::Set { content } => content.clone(), + fn apply_action(&self, input: &str) -> String { + trace!(?self, ?input, "Applying action"); + match self { + Action::Replace { from, to } => input.replace(from, to), + Action::Prefix { prefix } => format!("{prefix}{input}"), + Action::Suffix { suffix } => format!("{input}{suffix}"), + Action::Set { content } => content.clone(), + } } - } } diff --git a/src/logging.rs b/src/logging.rs index c9c01d9..b5ecb36 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,30 +1,28 @@ -use tracing::Level; use tracing::dispatcher::SetGlobalDefaultError; use tracing::subscriber::set_global_default; +use tracing::Level; use tracing_subscriber::filter::Targets; #[cfg(not(feature = "journald"))] fn configure_tracing(filter: Targets) -> Result<(), SetGlobalDefaultError> { - use tracing_subscriber::layer::SubscriberExt; - use tracing_subscriber::{Layer, fmt}; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::{fmt, Layer}; - let stdout_log = fmt::layer().pretty(); - let subscriber = - tracing_subscriber::registry().with(stdout_log.with_filter(filter)); - set_global_default(subscriber) + let stdout_log = fmt::layer().pretty(); + let subscriber = tracing_subscriber::registry().with(stdout_log.with_filter(filter)); + set_global_default(subscriber) } #[cfg(feature = "journald")] fn configure_tracing(filter: Targets) -> Result<(), SetGlobalDefaultError> { - use tracing_journald::layer; - use tracing_subscriber::{layer::SubscriberExt, registry}; + use tracing_journald::layer; + use tracing_subscriber::{layer::SubscriberExt, registry}; - let subscriber = registry().with(filter).with(layer().unwrap()); - set_global_default(subscriber) + let subscriber = registry().with(filter).with(layer().unwrap()); + set_global_default(subscriber) } pub fn init() -> Result<(), SetGlobalDefaultError> { - let tracing_filter = - Targets::new().with_target("clipboard_subsitutor", Level::DEBUG); - configure_tracing(tracing_filter) + let tracing_filter = Targets::new().with_target("clipboard_subsitutor", Level::DEBUG); + configure_tracing(tracing_filter) } diff --git a/src/main.rs b/src/main.rs index 113c493..cbda583 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod test; use std::path::PathBuf; -use anyhow::{Result, anyhow, bail}; +use anyhow::{anyhow, bail, Result}; use dirs::config_dir; use tracing::debug; @@ -14,43 +14,41 @@ use crate::clipboard::monitor; use crate::config::Replacements; fn main() -> Result<()> { - if check_for_version_arg() { - return Ok(()); - } - if let Err(e) = logging::init() { - bail!(e) - }; - let config_path = get_config_path()?; - let config_str = - std::fs::read_to_string(config_path.as_path()).unwrap_or_default(); - let config: Replacements = toml::from_str(&config_str)?; - config.validate()?; - monitor(&config) + if check_for_version_arg() { + return Ok(()); + } + if let Err(e) = logging::init() { + bail!(e) + }; + let config_path = get_config_path()?; + let config_str = std::fs::read_to_string(config_path.as_path()).unwrap_or_default(); + let config: Replacements = toml::from_str(&config_str)?; + config.validate()?; + monitor(&config) } fn check_for_version_arg() -> bool { - for arg in argv::iter() { - if arg == "-v" || arg == "version" || arg == "--version" { - print_version(); - return true; + for arg in argv::iter() { + if arg == "-v" || arg == "version" || arg == "--version" { + print_version(); + return true; + } } - } - false + false } fn print_version() { - println!( - "{}", - concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION")) - ); + println!( + "{}", + concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION")) + ); } fn get_config_path() -> Result { - let mut config_path = - config_dir().ok_or_else(|| anyhow!("Failed to get config dir"))?; - config_path.push("substitutor"); - config_path.push("config"); - config_path.set_extension("toml"); - debug!("Config file: {}", config_path.to_string_lossy()); - Ok(config_path) + let mut config_path = config_dir().ok_or_else(|| anyhow!("Failed to get config dir"))?; + config_path.push("substitutor"); + config_path.push("config"); + config_path.set_extension("toml"); + debug!("Config file: {}", config_path.to_string_lossy()); + Ok(config_path) } diff --git a/src/test.rs b/src/test.rs index 2df5dbe..d06135d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,49 +4,49 @@ use crate::config::{Act, Action, Match, Matcher, MatcherType, Replacements}; #[assay] fn regex_matcher() { - let matcher = Matcher::Regex { - pattern: "^https.*".to_string(), - }; - assert!(matcher.check_match("https://example.com")); - assert!(!matcher.check_match("example.com")); + let matcher = Matcher::Regex { + pattern: "^https.*".to_string(), + }; + assert!(matcher.check_match("https://example.com")); + assert!(!matcher.check_match("example.com")); } #[assay] fn set_action() { - let action = Action::Set { - content: "doe".to_string(), - }; - assert_eq!("doe", &action.apply_action("john")); + let action = Action::Set { + content: "doe".to_string(), + }; + assert_eq!("doe", &action.apply_action("john")); } #[assay] fn replace_action() { - let action = Action::Replace { - from: "doe".to_string(), - to: "bow".to_string(), - }; - assert_eq!("john bow", &action.apply_action("john doe")); + let action = Action::Replace { + from: "doe".to_string(), + to: "bow".to_string(), + }; + assert_eq!("john bow", &action.apply_action("john doe")); } #[assay] fn prefix_action() { - let action = Action::Prefix { - prefix: "hello ".to_string(), - }; - assert_eq!("hello john", &action.apply_action("john")); + let action = Action::Prefix { + prefix: "hello ".to_string(), + }; + assert_eq!("hello john", &action.apply_action("john")); } #[assay] fn suffix_action() { - let action = Action::Suffix { - suffix: " doe".to_string(), - }; - assert_eq!("john doe", &action.apply_action("john")); + let action = Action::Suffix { + suffix: " doe".to_string(), + }; + assert_eq!("john doe", &action.apply_action("john")); } #[assay] fn parse_with_multiple_matchers() { - let config = r#" + let config = r#" [[substitutor]] name = "Example" matcher = [ @@ -55,50 +55,50 @@ fn parse_with_multiple_matchers() { ] action = { prefix = { prefix = "/mirror" } } "#; - let config: Replacements = toml::from_str(config)?; - assert_eq!(1, config.substitutors.len()); - let subst = &config.substitutors[0]; - assert_eq!("Example", &subst.name); - assert!(matches!(subst.matcher, MatcherType::Multiple(_))); - assert!(matches!(subst.action, Action::Prefix { .. })); + let config: Replacements = toml::from_str(config)?; + assert_eq!(1, config.substitutors.len()); + let subst = &config.substitutors[0]; + assert_eq!("Example", &subst.name); + assert!(matches!(subst.matcher, MatcherType::Multiple(_))); + assert!(matches!(subst.action, Action::Prefix { .. })); } #[assay] fn parse_with_single_matcher() { - let config = r#" + let config = r#" [[substitutor]] name = "Example" matcher = { starts_with = { prefix = "https://example.com" } } action = { prefix = { prefix = "/mirror" } } "#; - let config: Replacements = toml::from_str(config)?; - assert_eq!(1, config.substitutors.len()); - let subst = &config.substitutors[0]; - assert_eq!("Example", &subst.name); - assert!(matches!(subst.matcher, MatcherType::Single(_))); - assert!(matches!(subst.action, Action::Prefix { .. })); + let config: Replacements = toml::from_str(config)?; + assert_eq!(1, config.substitutors.len()); + let subst = &config.substitutors[0]; + assert_eq!("Example", &subst.name); + assert!(matches!(subst.matcher, MatcherType::Single(_))); + assert!(matches!(subst.action, Action::Prefix { .. })); } #[assay] fn config_validation_success() { - let config = r#" + let config = r#" [[substitutor]] name = "vxTwitter" matcher = { regex = { pattern = "^https://(?P(?:mobile.)?twitter.com)/.*/status/[0-9]+.*" } } action = { replace = { from = "twitter.com", to = "vxtwitter.com" } } "#; - let config: Replacements = toml::from_str(config)?; - assert!(config.validate().is_ok()); + let config: Replacements = toml::from_str(config)?; + assert!(config.validate().is_ok()); } #[assay] fn config_validation_failure() { - let config = r#" + let config = r#" [[substitutor]] name = "vxTwitter" matcher = { regex = { pattern = "^https://(?P<>(?:mobile.)?twitter.com)/.*/status/[0-9]+.*" } } action = { replace = { from = "twitter.com", to = "vxtwitter.com" } } "#; - let config: Replacements = toml::from_str(config)?; - assert!(config.validate().is_err()); + let config: Replacements = toml::from_str(config)?; + assert!(config.validate().is_err()); }