From ecdcca93a43d020e1d8954bfdc878152895f3de6 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Sat, 28 Mar 2026 12:27:56 +0100 Subject: [PATCH] chore: remove calculator, converter, system plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These providers are now built into owlry-core >= 1.2.0. The plugins are retired — transitional AUR packages redirect to owlry-core. --- Cargo.lock | 45 - Cargo.toml | 3 - .../owlry-plugin-converter-1.0.0.tar.gz | Bin 0 -> 94655 bytes crates/owlry-plugin-calculator/Cargo.toml | 23 - crates/owlry-plugin-calculator/src/lib.rs | 231 ----- crates/owlry-plugin-converter/Cargo.toml | 21 - crates/owlry-plugin-converter/src/currency.rs | 313 ------ crates/owlry-plugin-converter/src/lib.rs | 237 ----- crates/owlry-plugin-converter/src/parser.rs | 235 ----- crates/owlry-plugin-converter/src/units.rs | 944 ------------------ crates/owlry-plugin-system/Cargo.toml | 20 - crates/owlry-plugin-system/src/lib.rs | 257 ----- 12 files changed, 2329 deletions(-) create mode 100644 aur/owlry-plugin-converter/owlry-plugin-converter-1.0.0.tar.gz delete mode 100644 crates/owlry-plugin-calculator/Cargo.toml delete mode 100644 crates/owlry-plugin-calculator/src/lib.rs delete mode 100644 crates/owlry-plugin-converter/Cargo.toml delete mode 100644 crates/owlry-plugin-converter/src/currency.rs delete mode 100644 crates/owlry-plugin-converter/src/lib.rs delete mode 100644 crates/owlry-plugin-converter/src/parser.rs delete mode 100644 crates/owlry-plugin-converter/src/units.rs delete mode 100644 crates/owlry-plugin-system/Cargo.toml delete mode 100644 crates/owlry-plugin-system/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ed3b5d1..9ef152d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,16 +817,6 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "meval" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" -dependencies = [ - "fnv", - "nom", -] - [[package]] name = "mime" version = "0.3.17" @@ -844,12 +834,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "nom" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" - [[package]] name = "once_cell" version = "1.21.4" @@ -889,15 +873,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "owlry-plugin-calculator" -version = "1.0.1" -dependencies = [ - "abi_stable", - "meval", - "owlry-plugin-api", -] - [[package]] name = "owlry-plugin-clipboard" version = "1.0.0" @@ -906,18 +881,6 @@ dependencies = [ "owlry-plugin-api", ] -[[package]] -name = "owlry-plugin-converter" -version = "1.0.2" -dependencies = [ - "abi_stable", - "dirs", - "owlry-plugin-api", - "reqwest", - "serde", - "serde_json", -] - [[package]] name = "owlry-plugin-emoji" version = "1.0.1" @@ -974,14 +937,6 @@ dependencies = [ "toml", ] -[[package]] -name = "owlry-plugin-system" -version = "1.0.0" -dependencies = [ - "abi_stable", - "owlry-plugin-api", -] - [[package]] name = "owlry-plugin-systemd" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index fe8ef21..72d2d54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ "crates/owlry-plugin-bookmarks", - "crates/owlry-plugin-calculator", - "crates/owlry-plugin-converter", "crates/owlry-plugin-clipboard", "crates/owlry-plugin-emoji", "crates/owlry-plugin-filesearch", @@ -10,7 +8,6 @@ members = [ "crates/owlry-plugin-pomodoro", "crates/owlry-plugin-scripts", "crates/owlry-plugin-ssh", - "crates/owlry-plugin-system", "crates/owlry-plugin-systemd", "crates/owlry-plugin-weather", "crates/owlry-plugin-websearch", diff --git a/aur/owlry-plugin-converter/owlry-plugin-converter-1.0.0.tar.gz b/aur/owlry-plugin-converter/owlry-plugin-converter-1.0.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e9d4e63ffae6d021403a5ba2400f9baa3bc21ccc GIT binary patch literal 94655 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 literal 0 HcmV?d00001 diff --git a/crates/owlry-plugin-calculator/Cargo.toml b/crates/owlry-plugin-calculator/Cargo.toml deleted file mode 100644 index f4ace61..0000000 --- a/crates/owlry-plugin-calculator/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "owlry-plugin-calculator" -version = "1.0.1" -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "Calculator plugin for owlry - evaluates mathematical expressions" -keywords = ["owlry", "plugin", "calculator"] -categories = ["mathematics"] - -[lib] -crate-type = ["cdylib"] # Compile as dynamic library (.so) - -[dependencies] -# Plugin API for owlry -owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" } - -# Math expression evaluation -meval = "0.2" - -# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc) -abi_stable = "0.11" diff --git a/crates/owlry-plugin-calculator/src/lib.rs b/crates/owlry-plugin-calculator/src/lib.rs deleted file mode 100644 index 7288fe7..0000000 --- a/crates/owlry-plugin-calculator/src/lib.rs +++ /dev/null @@ -1,231 +0,0 @@ -//! Calculator Plugin for Owlry -//! -//! A dynamic provider that evaluates mathematical expressions. -//! Supports queries prefixed with `=` or `calc `. -//! -//! Examples: -//! - `= 5 + 3` → 8 -//! - `calc sqrt(16)` → 4 -//! - `= pi * 2` → 6.283185... - -use abi_stable::std_types::{ROption, RStr, RString, RVec}; -use owlry_plugin_api::{ - API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, - ProviderPosition, owlry_plugin, -}; - -// Plugin metadata -const PLUGIN_ID: &str = "calculator"; -const PLUGIN_NAME: &str = "Calculator"; -const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION"); -const PLUGIN_DESCRIPTION: &str = "Evaluate mathematical expressions"; - -// Provider metadata -const PROVIDER_ID: &str = "calculator"; -const PROVIDER_NAME: &str = "Calculator"; -const PROVIDER_PREFIX: &str = "="; -const PROVIDER_ICON: &str = "accessories-calculator"; -const PROVIDER_TYPE_ID: &str = "calc"; - -/// Calculator provider state (empty for now, but could cache results) -struct CalculatorState; - -// ============================================================================ -// Plugin Interface Implementation -// ============================================================================ - -extern "C" fn plugin_info() -> PluginInfo { - PluginInfo { - id: RString::from(PLUGIN_ID), - name: RString::from(PLUGIN_NAME), - version: RString::from(PLUGIN_VERSION), - description: RString::from(PLUGIN_DESCRIPTION), - api_version: API_VERSION, - } -} - -extern "C" fn plugin_providers() -> RVec { - vec![ProviderInfo { - id: RString::from(PROVIDER_ID), - name: RString::from(PROVIDER_NAME), - prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)), - icon: RString::from(PROVIDER_ICON), - provider_type: ProviderKind::Dynamic, - type_id: RString::from(PROVIDER_TYPE_ID), - position: ProviderPosition::Normal, - priority: 10000, // Dynamic: calculator results first - }] - .into() -} - -extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle { - // Create state and return handle - let state = Box::new(CalculatorState); - ProviderHandle::from_box(state) -} - -extern "C" fn provider_refresh(_handle: ProviderHandle) -> RVec { - // Dynamic provider - refresh does nothing - RVec::new() -} - -extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec { - let query_str = query.as_str(); - - // Extract expression from query - let expr = match extract_expression(query_str) { - Some(e) if !e.is_empty() => e, - _ => return RVec::new(), - }; - - // Evaluate the expression - match evaluate_expression(expr) { - Some(item) => vec![item].into(), - None => RVec::new(), - } -} - -extern "C" fn provider_drop(handle: ProviderHandle) { - if !handle.ptr.is_null() { - // SAFETY: We created this handle from Box - unsafe { - handle.drop_as::(); - } - } -} - -// Register the plugin vtable -owlry_plugin! { - info: plugin_info, - providers: plugin_providers, - init: provider_init, - refresh: provider_refresh, - query: provider_query, - drop: provider_drop, -} - -// ============================================================================ -// Calculator Logic -// ============================================================================ - -/// Extract expression from query (handles `= expr` and `calc expr` formats) -fn extract_expression(query: &str) -> Option<&str> { - let trimmed = query.trim(); - - // Support both "= expr" and "=expr" (with or without space) - if let Some(expr) = trimmed.strip_prefix("= ") { - Some(expr.trim()) - } else if let Some(expr) = trimmed.strip_prefix('=') { - Some(expr.trim()) - } else if let Some(expr) = trimmed.strip_prefix("calc ") { - Some(expr.trim()) - } else { - // For filter mode - accept raw expressions - Some(trimmed) - } -} - -/// Evaluate a mathematical expression and return a PluginItem -fn evaluate_expression(expr: &str) -> Option { - match meval::eval_str(expr) { - Ok(result) => { - // Format result nicely - let result_str = format_result(result); - - Some( - PluginItem::new( - format!("calc:{}", expr), - result_str.clone(), - format!("printf '%s' '{}' | wl-copy", result_str.replace('\'', "'\\''")), - ) - .with_description(format!("= {}", expr)) - .with_icon(PROVIDER_ICON) - .with_keywords(vec!["math".to_string(), "calculator".to_string()]), - ) - } - Err(_) => None, - } -} - -/// Format a numeric result nicely -fn format_result(result: f64) -> String { - if result.fract() == 0.0 && result.abs() < 1e15 { - // Integer result - format!("{}", result as i64) - } else { - // Float result with reasonable precision, trimming trailing zeros - let formatted = format!("{:.10}", result); - formatted - .trim_end_matches('0') - .trim_end_matches('.') - .to_string() - } -} - -// ============================================================================ -// Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_expression() { - assert_eq!(extract_expression("= 5+3"), Some("5+3")); - assert_eq!(extract_expression("=5+3"), Some("5+3")); - assert_eq!(extract_expression("calc 5+3"), Some("5+3")); - assert_eq!(extract_expression(" = 5 + 3 "), Some("5 + 3")); - assert_eq!(extract_expression("5+3"), Some("5+3")); // Raw expression - } - - #[test] - fn test_format_result() { - assert_eq!(format_result(8.0), "8"); - assert_eq!(format_result(2.5), "2.5"); - assert_eq!(format_result(3.14159265358979), "3.1415926536"); - } - - #[test] - fn test_evaluate_basic() { - let item = evaluate_expression("5+3").unwrap(); - assert_eq!(item.name.as_str(), "8"); - - let item = evaluate_expression("10 * 2").unwrap(); - assert_eq!(item.name.as_str(), "20"); - - let item = evaluate_expression("15 / 3").unwrap(); - assert_eq!(item.name.as_str(), "5"); - } - - #[test] - fn test_evaluate_float() { - let item = evaluate_expression("5/2").unwrap(); - assert_eq!(item.name.as_str(), "2.5"); - } - - #[test] - fn test_evaluate_functions() { - let item = evaluate_expression("sqrt(16)").unwrap(); - assert_eq!(item.name.as_str(), "4"); - - let item = evaluate_expression("abs(-5)").unwrap(); - assert_eq!(item.name.as_str(), "5"); - } - - #[test] - fn test_evaluate_constants() { - let item = evaluate_expression("pi").unwrap(); - assert!(item.name.as_str().starts_with("3.14159")); - - let item = evaluate_expression("e").unwrap(); - assert!(item.name.as_str().starts_with("2.718")); - } - - #[test] - fn test_evaluate_invalid() { - assert!(evaluate_expression("").is_none()); - assert!(evaluate_expression("invalid").is_none()); - assert!(evaluate_expression("5 +").is_none()); - } -} diff --git a/crates/owlry-plugin-converter/Cargo.toml b/crates/owlry-plugin-converter/Cargo.toml deleted file mode 100644 index 1fdadbe..0000000 --- a/crates/owlry-plugin-converter/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "owlry-plugin-converter" -version = "1.0.2" -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "Unit and currency conversion plugin for owlry" -keywords = ["owlry", "plugin", "converter", "units", "currency"] -categories = ["science"] - -[lib] -crate-type = ["cdylib"] - -[dependencies] -owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" } -abi_stable = "0.11" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -reqwest = { version = "0.13", features = ["blocking"] } -dirs = "5" diff --git a/crates/owlry-plugin-converter/src/currency.rs b/crates/owlry-plugin-converter/src/currency.rs deleted file mode 100644 index ea35c44..0000000 --- a/crates/owlry-plugin-converter/src/currency.rs +++ /dev/null @@ -1,313 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; -use std::sync::Mutex; -use std::time::SystemTime; - -use serde::{Deserialize, Serialize}; - -const ECB_URL: &str = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"; -const CACHE_MAX_AGE_SECS: u64 = 86400; // 24 hours - -static CACHED_RATES: Mutex> = Mutex::new(None); - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CurrencyRates { - pub date: String, - pub rates: HashMap, -} - -struct CurrencyAlias { - code: &'static str, - aliases: &'static [&'static str], -} - -static CURRENCY_ALIASES: &[CurrencyAlias] = &[ - CurrencyAlias { - code: "EUR", - aliases: &["eur", "euro", "euros", "€"], - }, - CurrencyAlias { - code: "USD", - aliases: &["usd", "dollar", "dollars", "$", "us_dollar"], - }, - CurrencyAlias { - code: "GBP", - aliases: &["gbp", "pound_sterling", "£", "british_pound", "pounds"], - }, - CurrencyAlias { - code: "JPY", - aliases: &["jpy", "yen", "¥", "japanese_yen"], - }, - CurrencyAlias { - code: "CHF", - aliases: &["chf", "swiss_franc", "francs"], - }, - CurrencyAlias { - code: "CAD", - aliases: &["cad", "canadian_dollar", "c$"], - }, - CurrencyAlias { - code: "AUD", - aliases: &["aud", "australian_dollar", "a$"], - }, - CurrencyAlias { - code: "CNY", - aliases: &["cny", "yuan", "renminbi", "rmb"], - }, - CurrencyAlias { - code: "SEK", - aliases: &["sek", "swedish_krona", "kronor"], - }, - CurrencyAlias { - code: "NOK", - aliases: &["nok", "norwegian_krone"], - }, - CurrencyAlias { - code: "DKK", - aliases: &["dkk", "danish_krone"], - }, - CurrencyAlias { - code: "PLN", - aliases: &["pln", "zloty", "złoty"], - }, - CurrencyAlias { - code: "CZK", - aliases: &["czk", "czech_koruna"], - }, - CurrencyAlias { - code: "HUF", - aliases: &["huf", "forint"], - }, - CurrencyAlias { - code: "TRY", - aliases: &["try", "turkish_lira", "lira"], - }, -]; - -pub fn resolve_currency_code(alias: &str) -> Option<&'static str> { - let lower = alias.to_lowercase(); - - // Check aliases - for ca in CURRENCY_ALIASES { - if ca.aliases.contains(&lower.as_str()) { - return Some(ca.code); // ca.code is already &'static str - } - } - - // Check if it's a raw 3-letter ISO code we know about - let upper = alias.to_uppercase(); - if upper.len() == 3 { - if upper == "EUR" { - return Some("EUR"); - } - if let Some(rates) = get_rates() - && rates.rates.contains_key(&upper) - { - for ca in CURRENCY_ALIASES { - if ca.code == upper { - return Some(ca.code); - } - } - } - } - - None -} - -#[allow(dead_code)] -pub fn is_currency_alias(alias: &str) -> bool { - resolve_currency_code(alias).is_some() -} - -pub fn get_rates() -> Option { - // Check memory cache first - { - let cache = CACHED_RATES.lock().ok()?; - if let Some(ref rates) = *cache { - return Some(rates.clone()); - } - } - - // Try disk cache - if let Some(rates) = load_cache() - && !is_stale(&rates) - { - let mut cache = CACHED_RATES.lock().ok()?; - *cache = Some(rates.clone()); - return Some(rates); - } - - // Fetch fresh rates - if let Some(rates) = fetch_rates() { - save_cache(&rates); - let mut cache = CACHED_RATES.lock().ok()?; - *cache = Some(rates.clone()); - return Some(rates); - } - - // Fall back to stale cache - load_cache() -} - -fn cache_path() -> Option { - let cache_dir = dirs::cache_dir()?.join("owlry"); - Some(cache_dir.join("ecb_rates.json")) -} - -fn load_cache() -> Option { - let path = cache_path()?; - let content = fs::read_to_string(path).ok()?; - serde_json::from_str(&content).ok() -} - -fn save_cache(rates: &CurrencyRates) { - if let Some(path) = cache_path() { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).ok(); - } - if let Ok(json) = serde_json::to_string_pretty(rates) { - fs::write(path, json).ok(); - } - } -} - -fn is_stale(_rates: &CurrencyRates) -> bool { - let path = match cache_path() { - Some(p) => p, - None => return true, - }; - let metadata = match fs::metadata(path) { - Ok(m) => m, - Err(_) => return true, - }; - let modified = match metadata.modified() { - Ok(t) => t, - Err(_) => return true, - }; - match SystemTime::now().duration_since(modified) { - Ok(age) => age.as_secs() > CACHE_MAX_AGE_SECS, - Err(_) => true, - } -} - -fn fetch_rates() -> Option { - let response = reqwest::blocking::get(ECB_URL).ok()?; - let body = response.text().ok()?; - parse_ecb_xml(&body) -} - -fn parse_ecb_xml(xml: &str) -> Option { - let mut rates = HashMap::new(); - let mut date = String::new(); - - for line in xml.lines() { - let trimmed = line.trim(); - - // Extract date: - if trimmed.contains("time=") - && let Some(start) = trimmed.find("time='") - { - let rest = &trimmed[start + 6..]; - if let Some(end) = rest.find('\'') { - date = rest[..end].to_string(); - } - } - - // Extract rate: - if trimmed.contains("currency=") && trimmed.contains("rate=") { - let currency = extract_attr(trimmed, "currency")?; - let rate_str = extract_attr(trimmed, "rate")?; - let rate: f64 = rate_str.parse().ok()?; - rates.insert(currency, rate); - } - } - - if rates.is_empty() { - return None; - } - - Some(CurrencyRates { date, rates }) -} - -fn extract_attr(line: &str, attr: &str) -> Option { - let needle = format!("{}='", attr); - let start = line.find(&needle)? + needle.len(); - let rest = &line[start..]; - let end = rest.find('\'')?; - Some(rest[..end].to_string()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_resolve_currency_code_iso() { - assert_eq!(resolve_currency_code("usd"), Some("USD")); - assert_eq!(resolve_currency_code("EUR"), Some("EUR")); - } - - #[test] - fn test_resolve_currency_code_name() { - assert_eq!(resolve_currency_code("dollar"), Some("USD")); - assert_eq!(resolve_currency_code("euro"), Some("EUR")); - assert_eq!(resolve_currency_code("pounds"), Some("GBP")); - } - - #[test] - fn test_resolve_currency_code_symbol() { - assert_eq!(resolve_currency_code("$"), Some("USD")); - assert_eq!(resolve_currency_code("€"), Some("EUR")); - assert_eq!(resolve_currency_code("£"), Some("GBP")); - } - - #[test] - fn test_resolve_currency_unknown() { - assert_eq!(resolve_currency_code("xyz"), None); - } - - #[test] - fn test_is_currency_alias() { - assert!(is_currency_alias("usd")); - assert!(is_currency_alias("euro")); - assert!(is_currency_alias("$")); - assert!(!is_currency_alias("km")); - } - - #[test] - fn test_parse_ecb_xml() { - let xml = r#" - - Reference rates - - - - - - - -"#; - - let rates = parse_ecb_xml(xml).unwrap(); - assert!((rates.rates["USD"] - 1.0832).abs() < 0.001); - assert!((rates.rates["GBP"] - 0.8345).abs() < 0.001); - assert!((rates.rates["JPY"] - 161.94).abs() < 0.01); - } - - #[test] - fn test_cache_roundtrip() { - let rates = CurrencyRates { - date: "2026-03-26".to_string(), - rates: { - let mut m = HashMap::new(); - m.insert("USD".to_string(), 1.0832); - m.insert("GBP".to_string(), 0.8345); - m - }, - }; - let json = serde_json::to_string(&rates).unwrap(); - let parsed: CurrencyRates = serde_json::from_str(&json).unwrap(); - assert_eq!(parsed.rates["USD"], 1.0832); - } -} diff --git a/crates/owlry-plugin-converter/src/lib.rs b/crates/owlry-plugin-converter/src/lib.rs deleted file mode 100644 index 554f896..0000000 --- a/crates/owlry-plugin-converter/src/lib.rs +++ /dev/null @@ -1,237 +0,0 @@ -//! Converter Plugin for Owlry -//! -//! A dynamic provider that converts between units and currencies. -//! Supports queries prefixed with `>` or auto-detected. -//! -//! Examples: -//! - `> 100 F to C` → 37.78 °C -//! - `50 kg in lb` → 110.23 lb -//! - `100 eur to usd` → 108.32 USD - -mod currency; -mod parser; -mod units; - -use abi_stable::std_types::{ROption, RStr, RString, RVec}; -use owlry_plugin_api::{ - API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, - ProviderPosition, owlry_plugin, -}; - -const PLUGIN_ID: &str = "converter"; -const PLUGIN_NAME: &str = "Converter"; -const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION"); -const PLUGIN_DESCRIPTION: &str = "Convert between units and currencies"; - -const PROVIDER_ID: &str = "converter"; -const PROVIDER_NAME: &str = "Converter"; -const PROVIDER_PREFIX: &str = ">"; -const PROVIDER_ICON: &str = "edit-find-replace-symbolic"; -const PROVIDER_TYPE_ID: &str = "conv"; - -struct ConverterState; - -extern "C" fn plugin_info() -> PluginInfo { - PluginInfo { - id: RString::from(PLUGIN_ID), - name: RString::from(PLUGIN_NAME), - version: RString::from(PLUGIN_VERSION), - description: RString::from(PLUGIN_DESCRIPTION), - api_version: API_VERSION, - } -} - -extern "C" fn plugin_providers() -> RVec { - vec![ProviderInfo { - id: RString::from(PROVIDER_ID), - name: RString::from(PROVIDER_NAME), - prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)), - icon: RString::from(PROVIDER_ICON), - provider_type: ProviderKind::Dynamic, - type_id: RString::from(PROVIDER_TYPE_ID), - position: ProviderPosition::Normal, - priority: 9000, - }] - .into() -} - -extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle { - let state = Box::new(ConverterState); - ProviderHandle::from_box(state) -} - -extern "C" fn provider_refresh(_handle: ProviderHandle) -> RVec { - RVec::new() -} - -extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec { - let query_str = query.as_str().trim(); - // Strip prefix - let input = if let Some(rest) = query_str.strip_prefix('>') { - rest.trim() - } else { - query_str - }; - - let parsed = match parser::parse_conversion(input) { - Some(p) => p, - None => return RVec::new(), - }; - - let results = if let Some(ref target) = parsed.target_unit { - units::convert_to(&parsed.value, &parsed.from_unit, target) - .into_iter() - .collect() - } else { - units::convert_common(&parsed.value, &parsed.from_unit) - }; - - results - .into_iter() - .map(|r| { - PluginItem::new( - format!("conv:{}:{}:{}", parsed.from_unit, r.target_symbol, r.value), - r.display_value.clone(), - format!("printf '%s' '{}' | wl-copy", r.raw_value.replace('\'', "'\\''")), - ) - .with_description(format!( - "{} {} = {}", - format_number(parsed.value), - parsed.from_symbol, - r.display_value, - )) - .with_icon(PROVIDER_ICON) - }) - .collect::>() - .into() -} - -extern "C" fn provider_drop(handle: ProviderHandle) { - if !handle.ptr.is_null() { - unsafe { - handle.drop_as::(); - } - } -} - -owlry_plugin! { - info: plugin_info, - providers: plugin_providers, - init: provider_init, - refresh: provider_refresh, - query: provider_query, - drop: provider_drop, -} - -fn format_number(n: f64) -> String { - if n.fract() == 0.0 && n.abs() < 1e15 { - let i = n as i64; - if i.abs() >= 1000 { - format_with_separators(i) - } else { - format!("{}", i) - } - } else { - format!("{:.4}", n) - .trim_end_matches('0') - .trim_end_matches('.') - .to_string() - } -} - -fn format_with_separators(n: i64) -> String { - let s = n.abs().to_string(); - let mut result = String::new(); - for (i, c) in s.chars().rev().enumerate() { - if i > 0 && i % 3 == 0 { - result.push(','); - } - result.push(c); - } - if n < 0 { - result.push('-'); - } - result.chars().rev().collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_number_integer() { - assert_eq!(format_number(42.0), "42"); - } - - #[test] - fn test_format_number_large_integer() { - assert_eq!(format_number(1000000.0), "1,000,000"); - } - - #[test] - fn test_format_number_decimal() { - assert_eq!(format_number(3.14), "3.14"); - } - - #[test] - fn test_format_with_separators() { - assert_eq!(format_with_separators(1234567), "1,234,567"); - assert_eq!(format_with_separators(999), "999"); - assert_eq!(format_with_separators(-1234), "-1,234"); - } - - #[test] - fn test_provider_query_with_prefix() { - let result = provider_query( - ProviderHandle { - ptr: std::ptr::null_mut(), - }, - RStr::from("> 100 km to mi"), - ); - assert!(!result.is_empty()); - } - - #[test] - fn test_provider_query_auto_detect() { - let result = provider_query( - ProviderHandle { - ptr: std::ptr::null_mut(), - }, - RStr::from("100 km to mi"), - ); - assert!(!result.is_empty()); - } - - #[test] - fn test_provider_query_no_target() { - let result = provider_query( - ProviderHandle { - ptr: std::ptr::null_mut(), - }, - RStr::from("> 100 km"), - ); - assert!(result.len() > 1); - } - - #[test] - fn test_provider_query_nonsense() { - let result = provider_query( - ProviderHandle { - ptr: std::ptr::null_mut(), - }, - RStr::from("hello world"), - ); - assert!(result.is_empty()); - } - - #[test] - fn test_provider_query_temperature() { - let result = provider_query( - ProviderHandle { - ptr: std::ptr::null_mut(), - }, - RStr::from("102F to C"), - ); - assert!(!result.is_empty()); - } -} diff --git a/crates/owlry-plugin-converter/src/parser.rs b/crates/owlry-plugin-converter/src/parser.rs deleted file mode 100644 index 952d797..0000000 --- a/crates/owlry-plugin-converter/src/parser.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::units; - -pub struct ParsedQuery { - pub value: f64, - pub from_unit: String, - pub from_symbol: String, - pub target_unit: Option, -} - -pub fn parse_conversion(input: &str) -> Option { - let input = input.trim(); - if input.is_empty() { - return None; - } - - // Extract leading number - let (value, rest) = extract_number(input)?; - let rest = rest.trim(); - - if rest.is_empty() { - return None; - } - - // Split on " to " or " in " (case-insensitive) - let (from_str, target_str) = split_on_connector(rest); - - // Resolve from unit - let from_lower = from_str.trim().to_lowercase(); - let from_symbol = units::find_unit(&from_lower)?; - - let from_symbol_str = from_symbol.to_string(); - - // Resolve target unit if present - let target_unit = target_str.and_then(|t| { - let t_lower = t.trim().to_lowercase(); - if t_lower.is_empty() { - None - } else { - units::find_unit(&t_lower).map(|_| t_lower) - } - }); - - Some(ParsedQuery { - value, - from_unit: from_lower, - from_symbol: from_symbol_str, - target_unit, - }) -} - -fn extract_number(input: &str) -> Option<(f64, &str)> { - let bytes = input.as_bytes(); - let mut i = 0; - - // Optional negative sign - if i < bytes.len() && bytes[i] == b'-' { - i += 1; - } - - // Must have at least one digit or start with . - if i >= bytes.len() { - return None; - } - - let start_digits = i; - - // Integer part - while i < bytes.len() && bytes[i].is_ascii_digit() { - i += 1; - } - - // Decimal part - if i < bytes.len() && bytes[i] == b'.' { - i += 1; - while i < bytes.len() && bytes[i].is_ascii_digit() { - i += 1; - } - } - - if i == start_digits && !(i > 0 && bytes[0] == b'-') { - // No digits found (and not just a negative sign before a dot) - // Handle ".5" case - if bytes[start_digits] == b'.' { - // already advanced past dot above - } else { - return None; - } - } - - if i == 0 || (i == 1 && bytes[0] == b'-') { - return None; - } - - let num_str = &input[..i]; - let value: f64 = num_str.parse().ok()?; - let rest = &input[i..]; - - Some((value, rest)) -} - -fn split_on_connector(input: &str) -> (&str, Option<&str>) { - let lower = input.to_lowercase(); - - // Try " to " first - if let Some(pos) = lower.find(" to ") { - let from = &input[..pos]; - let target = &input[pos + 4..]; - return (from, Some(target)); - } - - // Try " in " - if let Some(pos) = lower.find(" in ") { - let from = &input[..pos]; - let target = &input[pos + 4..]; - return (from, Some(target)); - } - - (input, None) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_number_and_unit_with_space() { - let p = parse_conversion("100 km").unwrap(); - assert!((p.value - 100.0).abs() < 0.001); - assert_eq!(p.from_unit, "km"); - assert!(p.target_unit.is_none()); - } - - #[test] - fn test_number_and_unit_no_space() { - let p = parse_conversion("100km").unwrap(); - assert!((p.value - 100.0).abs() < 0.001); - assert_eq!(p.from_unit, "km"); - } - - #[test] - fn test_with_target_to() { - let p = parse_conversion("100 km to mi").unwrap(); - assert!((p.value - 100.0).abs() < 0.001); - assert_eq!(p.from_unit, "km"); - assert_eq!(p.target_unit.as_deref(), Some("mi")); - } - - #[test] - fn test_with_target_in() { - let p = parse_conversion("100 km in mi").unwrap(); - assert_eq!(p.target_unit.as_deref(), Some("mi")); - } - - #[test] - fn test_temperature_no_space() { - let p = parse_conversion("102F to C").unwrap(); - assert!((p.value - 102.0).abs() < 0.001); - assert_eq!(p.from_unit, "f"); - assert_eq!(p.target_unit.as_deref(), Some("c")); - } - - #[test] - fn test_temperature_with_space() { - let p = parse_conversion("102 F in K").unwrap(); - assert!((p.value - 102.0).abs() < 0.001); - assert_eq!(p.from_unit, "f"); - assert_eq!(p.target_unit.as_deref(), Some("k")); - } - - #[test] - fn test_decimal_number() { - let p = parse_conversion("3.5 kg to lb").unwrap(); - assert!((p.value - 3.5).abs() < 0.001); - } - - #[test] - fn test_decimal_starting_with_dot() { - let p = parse_conversion(".5 kg").unwrap(); - assert!((p.value - 0.5).abs() < 0.001); - } - - #[test] - fn test_full_unit_names() { - let p = parse_conversion("100 kilometers to miles").unwrap(); - assert_eq!(p.from_unit, "kilometers"); - assert_eq!(p.target_unit.as_deref(), Some("miles")); - } - - #[test] - fn test_case_insensitive() { - let p = parse_conversion("100 KM TO MI").unwrap(); - assert_eq!(p.from_unit, "km"); - assert_eq!(p.target_unit.as_deref(), Some("mi")); - } - - #[test] - fn test_currency() { - let p = parse_conversion("100 eur to usd").unwrap(); - assert_eq!(p.from_unit, "eur"); - assert_eq!(p.target_unit.as_deref(), Some("usd")); - } - - #[test] - fn test_no_number_returns_none() { - assert!(parse_conversion("km to mi").is_none()); - } - - #[test] - fn test_unknown_unit_returns_none() { - assert!(parse_conversion("100 xyz to abc").is_none()); - } - - #[test] - fn test_empty_returns_none() { - assert!(parse_conversion("").is_none()); - } - - #[test] - fn test_number_only_returns_none() { - assert!(parse_conversion("100").is_none()); - } - - #[test] - fn test_compound_unit_alias() { - let p = parse_conversion("100 km/h to mph").unwrap(); - assert_eq!(p.from_unit, "km/h"); - assert_eq!(p.target_unit.as_deref(), Some("mph")); - } - - #[test] - fn test_multi_word_unit() { - let p = parse_conversion("100 fl_oz to ml").unwrap(); - assert_eq!(p.from_unit, "fl_oz"); - } -} diff --git a/crates/owlry-plugin-converter/src/units.rs b/crates/owlry-plugin-converter/src/units.rs deleted file mode 100644 index f43a085..0000000 --- a/crates/owlry-plugin-converter/src/units.rs +++ /dev/null @@ -1,944 +0,0 @@ -use std::collections::HashMap; -use std::sync::LazyLock; - -use crate::currency; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Category { - Temperature, - Length, - Weight, - Volume, - Speed, - Area, - Data, - Time, - Pressure, - Energy, - Currency, -} - -#[derive(Clone)] -enum Conversion { - Factor(f64), - Custom { - to_base: fn(f64) -> f64, - from_base: fn(f64) -> f64, - }, -} - -#[derive(Clone)] -pub(crate) struct UnitDef { - _id: &'static str, - symbol: &'static str, - aliases: &'static [&'static str], - category: Category, - conversion: Conversion, -} - -impl UnitDef { - fn to_base(&self, value: f64) -> f64 { - match &self.conversion { - Conversion::Factor(f) => value * f, - Conversion::Custom { to_base, .. } => to_base(value), - } - } - - fn convert_from_base(&self, value: f64) -> f64 { - match &self.conversion { - Conversion::Factor(f) => value / f, - Conversion::Custom { from_base, .. } => from_base(value), - } - } -} - -pub struct ConversionResult { - pub value: f64, - pub raw_value: String, - pub display_value: String, - pub target_symbol: String, -} - -static UNITS: LazyLock> = LazyLock::new(build_unit_table); -static ALIAS_MAP: LazyLock> = LazyLock::new(|| { - let mut map = HashMap::new(); - for (i, unit) in UNITS.iter().enumerate() { - for alias in unit.aliases { - map.insert(alias.to_lowercase(), i); - } - } - map -}); - -// Common conversions per category (symbols to show when no target specified) -static COMMON_TARGETS: LazyLock>> = LazyLock::new(|| { - let mut m = HashMap::new(); - m.insert(Category::Temperature, vec!["°C", "°F", "K"]); - m.insert(Category::Length, vec!["m", "km", "ft", "mi", "in"]); - m.insert(Category::Weight, vec!["kg", "lb", "oz", "g", "st"]); - m.insert(Category::Volume, vec!["l", "gal", "ml", "cup", "fl oz"]); - m.insert(Category::Speed, vec!["km/h", "mph", "m/s", "kn"]); - m.insert(Category::Area, vec!["m²", "ft²", "ac", "ha", "km²"]); - m.insert(Category::Data, vec!["MB", "GB", "MiB", "GiB", "TB"]); - m.insert(Category::Time, vec!["s", "min", "h", "d", "wk"]); - m.insert(Category::Pressure, vec!["bar", "psi", "atm", "hPa", "mmHg"]); - m.insert(Category::Energy, vec!["kJ", "kcal", "kWh", "BTU", "Wh"]); - m.insert(Category::Currency, vec!["USD", "EUR", "GBP", "JPY", "CNY"]); - m -}); - -pub fn find_unit(alias: &str) -> Option<&'static str> { - let lower = alias.to_lowercase(); - if let Some(&i) = ALIAS_MAP.get(&lower) { - return Some(UNITS[i].symbol); - } - currency::resolve_currency_code(&lower) -} - -pub fn lookup_unit(alias: &str) -> Option<(usize, &UnitDef)> { - let lower = alias.to_lowercase(); - ALIAS_MAP.get(&lower).map(|&i| (i, &UNITS[i])) -} - -pub fn convert_to(value: &f64, from: &str, to: &str) -> Option { - // Try currency first — currency aliases (dollar, euro, etc.) aren't in the UNITS table - if currency::is_currency_alias(from) || currency::is_currency_alias(to) { - return convert_currency(*value, from, to); - } - - let (_, from_def) = lookup_unit(from)?; - let (_, to_def) = lookup_unit(to)?; - - // Currency via UNITS table (shouldn't reach here, but just in case) - if from_def.category == Category::Currency || to_def.category == Category::Currency { - return convert_currency(*value, from, to); - } - - // Must be same category - if from_def.category != to_def.category { - return None; - } - - let base_value = from_def.to_base(*value); - let result = to_def.convert_from_base(base_value); - - Some(format_result(result, to_def.symbol)) -} - -pub fn convert_common(value: &f64, from: &str) -> Vec { - // Try currency first — currency aliases (dollar, euro, etc.) aren't in the UNITS table - if currency::is_currency_alias(from) { - return convert_currency_common(*value, from); - } - - let (_, from_def) = match lookup_unit(from) { - Some(u) => u, - None => return vec![], - }; - - let category = from_def.category; - let from_symbol = from_def.symbol; - - if category == Category::Currency { - return convert_currency_common(*value, from); - } - - let targets = match COMMON_TARGETS.get(&category) { - Some(t) => t, - None => return vec![], - }; - - let base_value = from_def.to_base(*value); - - targets - .iter() - .filter(|&&sym| sym != from_symbol) - .filter_map(|&sym| { - let (_, to_def) = lookup_unit(sym)?; - let result = to_def.convert_from_base(base_value); - Some(format_result(result, to_def.symbol)) - }) - .take(5) - .collect() -} - -fn convert_currency(value: f64, from: &str, to: &str) -> Option { - let rates = currency::get_rates()?; - let from_code = currency::resolve_currency_code(from)?; - let to_code = currency::resolve_currency_code(to)?; - - let from_rate = if from_code == "EUR" { 1.0 } else { *rates.rates.get(from_code)? }; - let to_rate = if to_code == "EUR" { 1.0 } else { *rates.rates.get(to_code)? }; - - let result = value / from_rate * to_rate; - Some(format_currency_result(result, to_code)) -} - -fn convert_currency_common(value: f64, from: &str) -> Vec { - let rates = match currency::get_rates() { - Some(r) => r, - None => return vec![], - }; - let from_code = match currency::resolve_currency_code(from) { - Some(c) => c, - None => return vec![], - }; - - let targets = COMMON_TARGETS.get(&Category::Currency).unwrap(); - let from_rate = if from_code == "EUR" { - 1.0 - } else { - match rates.rates.get(from_code) { - Some(&r) => r, - None => return vec![], - } - }; - - targets - .iter() - .filter(|&&sym| sym != from_code) - .filter_map(|&sym| { - let to_rate = if sym == "EUR" { 1.0 } else { *rates.rates.get(sym)? }; - let result = value / from_rate * to_rate; - Some(format_currency_result(result, sym)) - }) - .take(5) - .collect() -} - -fn format_result(value: f64, symbol: &str) -> ConversionResult { - let raw = if value.fract() == 0.0 && value.abs() < 1e15 { - format!("{}", value as i64) - } else { - format!("{:.4}", value) - .trim_end_matches('0') - .trim_end_matches('.') - .to_string() - }; - - let display = if value.abs() >= 1000.0 && value.fract() == 0.0 && value.abs() < 1e15 { - crate::format_with_separators(value as i64) - } else { - raw.clone() - }; - - ConversionResult { - value, - raw_value: raw, - display_value: format!("{} {}", display, symbol), - target_symbol: symbol.to_string(), - } -} - -fn format_currency_result(value: f64, code: &str) -> ConversionResult { - let raw = format!("{:.2}", value); - let display = raw.clone(); - - ConversionResult { - value, - raw_value: raw, - display_value: format!("{} {}", display, code), - target_symbol: code.to_string(), - } -} - -fn build_unit_table() -> Vec { - vec![ - // Temperature (base: Kelvin) - UnitDef { - _id: "celsius", - symbol: "°C", - aliases: &["c", "°c", "celsius", "degc", "centigrade"], - category: Category::Temperature, - conversion: Conversion::Custom { - to_base: |v| v + 273.15, - from_base: |v| v - 273.15, - }, - }, - UnitDef { - _id: "fahrenheit", - symbol: "°F", - aliases: &["f", "°f", "fahrenheit", "degf"], - category: Category::Temperature, - conversion: Conversion::Custom { - to_base: |v| (v - 32.0) * 5.0 / 9.0 + 273.15, - from_base: |v| (v - 273.15) * 9.0 / 5.0 + 32.0, - }, - }, - UnitDef { - _id: "kelvin", - symbol: "K", - aliases: &["k", "kelvin"], - category: Category::Temperature, - conversion: Conversion::Factor(1.0), // base - }, - // Length (base: meter) - UnitDef { - _id: "millimeter", - symbol: "mm", - aliases: &["mm", "millimeter", "millimeters", "millimetre"], - category: Category::Length, - conversion: Conversion::Factor(0.001), - }, - UnitDef { - _id: "centimeter", - symbol: "cm", - aliases: &["cm", "centimeter", "centimeters", "centimetre"], - category: Category::Length, - conversion: Conversion::Factor(0.01), - }, - UnitDef { - _id: "meter", - symbol: "m", - aliases: &["m", "meter", "meters", "metre", "metres"], - category: Category::Length, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "kilometer", - symbol: "km", - aliases: &["km", "kms", "kilometer", "kilometers", "kilometre"], - category: Category::Length, - conversion: Conversion::Factor(1000.0), - }, - UnitDef { - _id: "inch", - symbol: "in", - aliases: &["in", "inch", "inches"], - category: Category::Length, - conversion: Conversion::Factor(0.0254), - }, - UnitDef { - _id: "foot", - symbol: "ft", - aliases: &["ft", "foot", "feet"], - category: Category::Length, - conversion: Conversion::Factor(0.3048), - }, - UnitDef { - _id: "yard", - symbol: "yd", - aliases: &["yd", "yard", "yards"], - category: Category::Length, - conversion: Conversion::Factor(0.9144), - }, - UnitDef { - _id: "mile", - symbol: "mi", - aliases: &["mi", "mile", "miles"], - category: Category::Length, - conversion: Conversion::Factor(1609.344), - }, - UnitDef { - _id: "nautical_mile", - symbol: "nmi", - aliases: &["nmi", "nautical_mile", "nautical_miles"], - category: Category::Length, - conversion: Conversion::Factor(1852.0), - }, - // Weight (base: kg) - UnitDef { - _id: "milligram", - symbol: "mg", - aliases: &["mg", "milligram", "milligrams"], - category: Category::Weight, - conversion: Conversion::Factor(0.000001), - }, - UnitDef { - _id: "gram", - symbol: "g", - aliases: &["g", "gram", "grams"], - category: Category::Weight, - conversion: Conversion::Factor(0.001), - }, - UnitDef { - _id: "kilogram", - symbol: "kg", - aliases: &["kg", "kilogram", "kilograms", "kilo", "kilos"], - category: Category::Weight, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "tonne", - symbol: "t", - aliases: &["t", "ton", "tons", "tonne", "tonnes", "metric_ton"], - category: Category::Weight, - conversion: Conversion::Factor(1000.0), - }, - UnitDef { - _id: "short_ton", - symbol: "short_ton", - aliases: &["short_ton", "ton_us"], - category: Category::Weight, - conversion: Conversion::Factor(907.185), - }, - UnitDef { - _id: "ounce", - symbol: "oz", - aliases: &["oz", "ounce", "ounces"], - category: Category::Weight, - conversion: Conversion::Factor(0.0283495), - }, - UnitDef { - _id: "pound", - symbol: "lb", - aliases: &["lb", "lbs", "pound", "pounds"], - category: Category::Weight, - conversion: Conversion::Factor(0.453592), - }, - UnitDef { - _id: "stone", - symbol: "st", - aliases: &["st", "stone", "stones"], - category: Category::Weight, - conversion: Conversion::Factor(6.35029), - }, - // Volume (base: liter) - UnitDef { - _id: "milliliter", - symbol: "ml", - aliases: &["ml", "milliliter", "milliliters", "millilitre"], - category: Category::Volume, - conversion: Conversion::Factor(0.001), - }, - UnitDef { - _id: "liter", - symbol: "l", - aliases: &["l", "liter", "liters", "litre", "litres"], - category: Category::Volume, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "us_gallon", - symbol: "gal", - aliases: &["gal", "gallon", "gallons"], - category: Category::Volume, - conversion: Conversion::Factor(3.78541), - }, - UnitDef { - _id: "imp_gallon", - symbol: "imp gal", - aliases: &["imp_gal", "gal_uk", "imperial_gallon"], - category: Category::Volume, - conversion: Conversion::Factor(4.54609), - }, - UnitDef { - _id: "quart", - symbol: "qt", - aliases: &["qt", "quart", "quarts"], - category: Category::Volume, - conversion: Conversion::Factor(0.946353), - }, - UnitDef { - _id: "pint", - symbol: "pt", - aliases: &["pt", "pint", "pints"], - category: Category::Volume, - conversion: Conversion::Factor(0.473176), - }, - UnitDef { - _id: "cup", - symbol: "cup", - aliases: &["cup", "cups"], - category: Category::Volume, - conversion: Conversion::Factor(0.236588), - }, - UnitDef { - _id: "fluid_ounce", - symbol: "fl oz", - aliases: &["floz", "fl_oz", "fluid_ounce", "fluid_ounces"], - category: Category::Volume, - conversion: Conversion::Factor(0.0295735), - }, - UnitDef { - _id: "tablespoon", - symbol: "tbsp", - aliases: &["tbsp", "tablespoon", "tablespoons"], - category: Category::Volume, - conversion: Conversion::Factor(0.0147868), - }, - UnitDef { - _id: "teaspoon", - symbol: "tsp", - aliases: &["tsp", "teaspoon", "teaspoons"], - category: Category::Volume, - conversion: Conversion::Factor(0.00492892), - }, - // Speed (base: m/s) - UnitDef { - _id: "mps", - symbol: "m/s", - aliases: &["m/s", "mps", "meters_per_second"], - category: Category::Speed, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "kmh", - symbol: "km/h", - aliases: &["km/h", "kmh", "kph", "kilometers_per_hour"], - category: Category::Speed, - conversion: Conversion::Factor(0.277778), - }, - UnitDef { - _id: "mph", - symbol: "mph", - aliases: &["mph", "miles_per_hour"], - category: Category::Speed, - conversion: Conversion::Factor(0.44704), - }, - UnitDef { - _id: "knot", - symbol: "kn", - aliases: &["kn", "kt", "knot", "knots"], - category: Category::Speed, - conversion: Conversion::Factor(0.514444), - }, - UnitDef { - _id: "fps", - symbol: "ft/s", - aliases: &["ft/s", "fps", "feet_per_second"], - category: Category::Speed, - conversion: Conversion::Factor(0.3048), - }, - // Area (base: m²) - UnitDef { - _id: "sqmm", - symbol: "mm²", - aliases: &["mm2", "sqmm", "square_millimeter"], - category: Category::Area, - conversion: Conversion::Factor(0.000001), - }, - UnitDef { - _id: "sqcm", - symbol: "cm²", - aliases: &["cm2", "sqcm", "square_centimeter"], - category: Category::Area, - conversion: Conversion::Factor(0.0001), - }, - UnitDef { - _id: "sqm", - symbol: "m²", - aliases: &["m2", "sqm", "square_meter", "square_meters"], - category: Category::Area, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "sqkm", - symbol: "km²", - aliases: &["km2", "sqkm", "square_kilometer"], - category: Category::Area, - conversion: Conversion::Factor(1000000.0), - }, - UnitDef { - _id: "sqft", - symbol: "ft²", - aliases: &["ft2", "sqft", "square_foot", "square_feet"], - category: Category::Area, - conversion: Conversion::Factor(0.092903), - }, - UnitDef { - _id: "acre", - symbol: "ac", - aliases: &["ac", "acre", "acres"], - category: Category::Area, - conversion: Conversion::Factor(4046.86), - }, - UnitDef { - _id: "hectare", - symbol: "ha", - aliases: &["ha", "hectare", "hectares"], - category: Category::Area, - conversion: Conversion::Factor(10000.0), - }, - // Data (base: byte) - UnitDef { - _id: "byte", - symbol: "B", - aliases: &["b", "byte", "bytes"], - category: Category::Data, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "kilobyte", - symbol: "KB", - aliases: &["kb", "kilobyte", "kilobytes"], - category: Category::Data, - conversion: Conversion::Factor(1000.0), - }, - UnitDef { - _id: "megabyte", - symbol: "MB", - aliases: &["mb", "megabyte", "megabytes"], - category: Category::Data, - conversion: Conversion::Factor(1_000_000.0), - }, - UnitDef { - _id: "gigabyte", - symbol: "GB", - aliases: &["gb", "gigabyte", "gigabytes"], - category: Category::Data, - conversion: Conversion::Factor(1_000_000_000.0), - }, - UnitDef { - _id: "terabyte", - symbol: "TB", - aliases: &["tb", "terabyte", "terabytes"], - category: Category::Data, - conversion: Conversion::Factor(1_000_000_000_000.0), - }, - UnitDef { - _id: "kibibyte", - symbol: "KiB", - aliases: &["kib", "kibibyte", "kibibytes"], - category: Category::Data, - conversion: Conversion::Factor(1024.0), - }, - UnitDef { - _id: "mebibyte", - symbol: "MiB", - aliases: &["mib", "mebibyte", "mebibytes"], - category: Category::Data, - conversion: Conversion::Factor(1_048_576.0), - }, - UnitDef { - _id: "gibibyte", - symbol: "GiB", - aliases: &["gib", "gibibyte", "gibibytes"], - category: Category::Data, - conversion: Conversion::Factor(1_073_741_824.0), - }, - UnitDef { - _id: "tebibyte", - symbol: "TiB", - aliases: &["tib", "tebibyte", "tebibytes"], - category: Category::Data, - conversion: Conversion::Factor(1_099_511_627_776.0), - }, - // Time (base: second) - UnitDef { - _id: "second", - symbol: "s", - aliases: &["s", "sec", "second", "seconds"], - category: Category::Time, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "minute", - symbol: "min", - aliases: &["min", "minute", "minutes"], - category: Category::Time, - conversion: Conversion::Factor(60.0), - }, - UnitDef { - _id: "hour", - symbol: "h", - aliases: &["h", "hr", "hour", "hours"], - category: Category::Time, - conversion: Conversion::Factor(3600.0), - }, - UnitDef { - _id: "day", - symbol: "d", - aliases: &["d", "day", "days"], - category: Category::Time, - conversion: Conversion::Factor(86400.0), - }, - UnitDef { - _id: "week", - symbol: "wk", - aliases: &["wk", "week", "weeks"], - category: Category::Time, - conversion: Conversion::Factor(604800.0), - }, - UnitDef { - _id: "month", - symbol: "mo", - aliases: &["mo", "month", "months"], - category: Category::Time, - conversion: Conversion::Factor(2_592_000.0), - }, - UnitDef { - _id: "year", - symbol: "yr", - aliases: &["yr", "year", "years"], - category: Category::Time, - conversion: Conversion::Factor(31_536_000.0), - }, - // Pressure (base: Pa) - UnitDef { - _id: "pascal", - symbol: "Pa", - aliases: &["pa", "pascal", "pascals"], - category: Category::Pressure, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "hectopascal", - symbol: "hPa", - aliases: &["hpa", "hectopascal"], - category: Category::Pressure, - conversion: Conversion::Factor(100.0), - }, - UnitDef { - _id: "kilopascal", - symbol: "kPa", - aliases: &["kpa", "kilopascal"], - category: Category::Pressure, - conversion: Conversion::Factor(1000.0), - }, - UnitDef { - _id: "bar", - symbol: "bar", - aliases: &["bar", "bars"], - category: Category::Pressure, - conversion: Conversion::Factor(100_000.0), - }, - UnitDef { - _id: "millibar", - symbol: "mbar", - aliases: &["mbar", "millibar"], - category: Category::Pressure, - conversion: Conversion::Factor(100.0), - }, - UnitDef { - _id: "psi", - symbol: "psi", - aliases: &["psi", "pounds_per_square_inch"], - category: Category::Pressure, - conversion: Conversion::Factor(6894.76), - }, - UnitDef { - _id: "atmosphere", - symbol: "atm", - aliases: &["atm", "atmosphere", "atmospheres"], - category: Category::Pressure, - conversion: Conversion::Factor(101_325.0), - }, - UnitDef { - _id: "mmhg", - symbol: "mmHg", - aliases: &["mmhg", "torr"], - category: Category::Pressure, - conversion: Conversion::Factor(133.322), - }, - // Energy (base: Joule) - UnitDef { - _id: "joule", - symbol: "J", - aliases: &["j", "joule", "joules"], - category: Category::Energy, - conversion: Conversion::Factor(1.0), - }, - UnitDef { - _id: "kilojoule", - symbol: "kJ", - aliases: &["kj", "kilojoule", "kilojoules"], - category: Category::Energy, - conversion: Conversion::Factor(1000.0), - }, - UnitDef { - _id: "calorie", - symbol: "cal", - aliases: &["cal", "calorie", "calories"], - category: Category::Energy, - conversion: Conversion::Factor(4.184), - }, - UnitDef { - _id: "kilocalorie", - symbol: "kcal", - aliases: &["kcal", "kilocalorie", "kilocalories"], - category: Category::Energy, - conversion: Conversion::Factor(4184.0), - }, - UnitDef { - _id: "watt_hour", - symbol: "Wh", - aliases: &["wh", "watt_hour"], - category: Category::Energy, - conversion: Conversion::Factor(3600.0), - }, - UnitDef { - _id: "kilowatt_hour", - symbol: "kWh", - aliases: &["kwh", "kilowatt_hour"], - category: Category::Energy, - conversion: Conversion::Factor(3_600_000.0), - }, - UnitDef { - _id: "btu", - symbol: "BTU", - aliases: &["btu", "british_thermal_unit"], - category: Category::Energy, - conversion: Conversion::Factor(1055.06), - }, - ] -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_celsius_to_fahrenheit() { - let r = convert_to(&100.0, "c", "f").unwrap(); - assert!((r.value - 212.0).abs() < 0.01); - } - - #[test] - fn test_fahrenheit_to_celsius() { - let r = convert_to(&32.0, "f", "c").unwrap(); - assert!((r.value - 0.0).abs() < 0.01); - } - - #[test] - fn test_body_temp_f_to_c() { - let r = convert_to(&98.6, "f", "c").unwrap(); - assert!((r.value - 37.0).abs() < 0.01); - } - - #[test] - fn test_km_to_miles() { - let r = convert_to(&100.0, "km", "mi").unwrap(); - assert!((r.value - 62.1371).abs() < 0.01); - } - - #[test] - fn test_miles_to_km() { - let r = convert_to(&1.0, "mi", "km").unwrap(); - assert!((r.value - 1.60934).abs() < 0.01); - } - - #[test] - fn test_kg_to_lb() { - let r = convert_to(&1.0, "kg", "lb").unwrap(); - assert!((r.value - 2.20462).abs() < 0.01); - } - - #[test] - fn test_lb_to_kg() { - let r = convert_to(&100.0, "lbs", "kg").unwrap(); - assert!((r.value - 45.3592).abs() < 0.01); - } - - #[test] - fn test_liters_to_gallons() { - let r = convert_to(&3.78541, "l", "gal").unwrap(); - assert!((r.value - 1.0).abs() < 0.01); - } - - #[test] - fn test_kmh_to_mph() { - let r = convert_to(&100.0, "kmh", "mph").unwrap(); - assert!((r.value - 62.1371).abs() < 0.01); - } - - #[test] - fn test_gb_to_mb() { - let r = convert_to(&1.0, "gb", "mb").unwrap(); - assert!((r.value - 1000.0).abs() < 0.01); - } - - #[test] - fn test_gib_to_mib() { - let r = convert_to(&1.0, "gib", "mib").unwrap(); - assert!((r.value - 1024.0).abs() < 0.01); - } - - #[test] - fn test_hours_to_minutes() { - let r = convert_to(&2.5, "h", "min").unwrap(); - assert!((r.value - 150.0).abs() < 0.01); - } - - #[test] - fn test_bar_to_psi() { - let r = convert_to(&1.0, "bar", "psi").unwrap(); - assert!((r.value - 14.5038).abs() < 0.01); - } - - #[test] - fn test_kcal_to_kj() { - let r = convert_to(&1.0, "kcal", "kj").unwrap(); - assert!((r.value - 4.184).abs() < 0.01); - } - - #[test] - fn test_sqm_to_sqft() { - let r = convert_to(&1.0, "m2", "ft2").unwrap(); - assert!((r.value - 10.7639).abs() < 0.01); - } - - #[test] - fn test_unknown_unit_returns_none() { - assert!(convert_to(&100.0, "xyz", "abc").is_none()); - } - - #[test] - fn test_cross_category_returns_none() { - assert!(convert_to(&100.0, "km", "kg").is_none()); - } - - #[test] - fn test_convert_common_returns_results() { - let results = convert_common(&100.0, "km"); - assert!(!results.is_empty()); - assert!(results.len() <= 5); - } - - #[test] - fn test_convert_common_excludes_source() { - let results = convert_common(&100.0, "km"); - for r in &results { - assert_ne!(r.target_symbol, "km"); - } - } - - #[test] - fn test_alias_case_insensitive() { - let r1 = convert_to(&100.0, "KM", "MI").unwrap(); - let r2 = convert_to(&100.0, "km", "mi").unwrap(); - assert!((r1.value - r2.value).abs() < 0.001); - } - - #[test] - fn test_full_name_alias() { - let r = convert_to(&100.0, "kilometers", "miles").unwrap(); - assert!((r.value - 62.1371).abs() < 0.01); - } - - #[test] - fn test_format_currency_two_decimals() { - let r = convert_to(&1.0, "km", "mi").unwrap(); - // display_value should have reasonable formatting - assert!(!r.display_value.is_empty()); - } - - #[test] - fn test_currency_alias_convert_to() { - // "dollar" and "euro" are aliases, not in the UNITS table - let r = convert_to(&20.0, "dollar", "euro"); - // May return None if ECB rates unavailable (network), but should not panic - // In a network-available environment, this should return Some - if let Some(r) = r { - assert!(r.value > 0.0); - assert_eq!(r.target_symbol, "EUR"); - } - } - - #[test] - fn test_currency_alias_convert_common() { - let results = convert_common(&20.0, "dollar"); - // May be empty if ECB rates unavailable, but should not panic - for r in &results { - assert!(r.value > 0.0); - } - } - - #[test] - fn test_display_value_no_double_unit() { - let r = convert_to(&100.0, "km", "mi").unwrap(); - // display_value should contain the symbol exactly once - let count = r.display_value.matches(&r.target_symbol).count(); - assert_eq!(count, 1, "display_value '{}' should contain '{}' exactly once", r.display_value, r.target_symbol); - } -} diff --git a/crates/owlry-plugin-system/Cargo.toml b/crates/owlry-plugin-system/Cargo.toml deleted file mode 100644 index 0ea277f..0000000 --- a/crates/owlry-plugin-system/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "owlry-plugin-system" -version = "1.0.0" -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -description = "System plugin for owlry - power and session management commands" -keywords = ["owlry", "plugin", "system", "power"] -categories = ["os"] - -[lib] -crate-type = ["cdylib"] # Compile as dynamic library (.so) - -[dependencies] -# Plugin API for owlry -owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugin-api-v1.0.0" } - -# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc) -abi_stable = "0.11" diff --git a/crates/owlry-plugin-system/src/lib.rs b/crates/owlry-plugin-system/src/lib.rs deleted file mode 100644 index 5e5b3e9..0000000 --- a/crates/owlry-plugin-system/src/lib.rs +++ /dev/null @@ -1,257 +0,0 @@ -//! System Plugin for Owlry -//! -//! A static provider that provides system power and session management commands. -//! -//! Commands: -//! - Shutdown - Power off the system -//! - Reboot - Restart the system -//! - Reboot into BIOS - Restart into UEFI/BIOS setup -//! - Suspend - Suspend to RAM -//! - Hibernate - Suspend to disk -//! - Lock Screen - Lock the session -//! - Log Out - End the current session - -use abi_stable::std_types::{ROption, RStr, RString, RVec}; -use owlry_plugin_api::{ - API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, - ProviderPosition, owlry_plugin, -}; - -// Plugin metadata -const PLUGIN_ID: &str = "system"; -const PLUGIN_NAME: &str = "System"; -const PLUGIN_VERSION: &str = env!("CARGO_PKG_VERSION"); -const PLUGIN_DESCRIPTION: &str = "Power and session management commands"; - -// Provider metadata -const PROVIDER_ID: &str = "system"; -const PROVIDER_NAME: &str = "System"; -const PROVIDER_PREFIX: &str = ":sys"; -const PROVIDER_ICON: &str = "system-shutdown"; -const PROVIDER_TYPE_ID: &str = "system"; - -/// System provider state - holds cached items -struct SystemState { - items: Vec, -} - -impl SystemState { - fn new() -> Self { - Self { items: Vec::new() } - } - - fn load_commands(&mut self) { - self.items.clear(); - - // Define system commands - // Format: (id, name, description, icon, command) - let commands: &[(&str, &str, &str, &str, &str)] = &[ - ( - "system:shutdown", - "Shutdown", - "Power off the system", - "system-shutdown", - "systemctl poweroff", - ), - ( - "system:reboot", - "Reboot", - "Restart the system", - "system-reboot", - "systemctl reboot", - ), - ( - "system:reboot-bios", - "Reboot into BIOS", - "Restart into UEFI/BIOS setup", - "system-reboot", - "systemctl reboot --firmware-setup", - ), - ( - "system:suspend", - "Suspend", - "Suspend to RAM", - "system-suspend", - "systemctl suspend", - ), - ( - "system:hibernate", - "Hibernate", - "Suspend to disk", - "system-suspend-hibernate", - "systemctl hibernate", - ), - ( - "system:lock", - "Lock Screen", - "Lock the session", - "system-lock-screen", - "loginctl lock-session", - ), - ( - "system:logout", - "Log Out", - "End the current session", - "system-log-out", - "loginctl terminate-session self", - ), - ]; - - for (id, name, description, icon, command) in commands { - self.items.push( - PluginItem::new(*id, *name, *command) - .with_description(*description) - .with_icon(*icon) - .with_keywords(vec!["power".to_string(), "system".to_string()]), - ); - } - } -} - -// ============================================================================ -// Plugin Interface Implementation -// ============================================================================ - -extern "C" fn plugin_info() -> PluginInfo { - PluginInfo { - id: RString::from(PLUGIN_ID), - name: RString::from(PLUGIN_NAME), - version: RString::from(PLUGIN_VERSION), - description: RString::from(PLUGIN_DESCRIPTION), - api_version: API_VERSION, - } -} - -extern "C" fn plugin_providers() -> RVec { - vec![ProviderInfo { - id: RString::from(PROVIDER_ID), - name: RString::from(PROVIDER_NAME), - prefix: ROption::RSome(RString::from(PROVIDER_PREFIX)), - icon: RString::from(PROVIDER_ICON), - provider_type: ProviderKind::Static, - type_id: RString::from(PROVIDER_TYPE_ID), - position: ProviderPosition::Normal, - priority: 0, // Static: use frecency ordering - }] - .into() -} - -extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle { - let state = Box::new(SystemState::new()); - ProviderHandle::from_box(state) -} - -extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec { - if handle.ptr.is_null() { - return RVec::new(); - } - - // SAFETY: We created this handle from Box - let state = unsafe { &mut *(handle.ptr as *mut SystemState) }; - - // Load/reload commands - state.load_commands(); - - // Return items - state.items.to_vec().into() -} - -extern "C" fn provider_query(_handle: ProviderHandle, _query: RStr<'_>) -> RVec { - // Static provider - query is handled by the core using cached items - RVec::new() -} - -extern "C" fn provider_drop(handle: ProviderHandle) { - if !handle.ptr.is_null() { - // SAFETY: We created this handle from Box - unsafe { - handle.drop_as::(); - } - } -} - -// Register the plugin vtable -owlry_plugin! { - info: plugin_info, - providers: plugin_providers, - init: provider_init, - refresh: provider_refresh, - query: provider_query, - drop: provider_drop, -} - -// ============================================================================ -// Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_system_state_new() { - let state = SystemState::new(); - assert!(state.items.is_empty()); - } - - #[test] - fn test_system_commands_loaded() { - let mut state = SystemState::new(); - state.load_commands(); - - assert!(state.items.len() >= 6); - - // Check for specific commands - let names: Vec<&str> = state.items.iter().map(|i| i.name.as_str()).collect(); - assert!(names.contains(&"Shutdown")); - assert!(names.contains(&"Reboot")); - assert!(names.contains(&"Suspend")); - assert!(names.contains(&"Lock Screen")); - assert!(names.contains(&"Log Out")); - } - - #[test] - fn test_reboot_bios_command() { - let mut state = SystemState::new(); - state.load_commands(); - - let bios_cmd = state - .items - .iter() - .find(|i| i.name.as_str() == "Reboot into BIOS") - .expect("Reboot into BIOS should exist"); - - assert_eq!( - bios_cmd.command.as_str(), - "systemctl reboot --firmware-setup" - ); - } - - #[test] - fn test_commands_have_icons() { - let mut state = SystemState::new(); - state.load_commands(); - - for item in &state.items { - assert!( - item.icon.is_some(), - "Item '{}' should have an icon", - item.name.as_str() - ); - } - } - - #[test] - fn test_commands_have_descriptions() { - let mut state = SystemState::new(); - state.load_commands(); - - for item in &state.items { - assert!( - item.description.is_some(), - "Item '{}' should have a description", - item.name.as_str() - ); - } - } -}