From c44502d0ab3c37b7f16b8d7457cb935df4109207 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 26 Mar 2026 15:15:33 +0100 Subject: [PATCH] feat(converter): scaffold plugin crate with vtable --- Cargo.toml | 1 + crates/owlry-plugin-converter/Cargo.toml | 21 +++ crates/owlry-plugin-converter/src/currency.rs | 17 ++ crates/owlry-plugin-converter/src/lib.rs | 156 ++++++++++++++++++ crates/owlry-plugin-converter/src/parser.rs | 10 ++ crates/owlry-plugin-converter/src/units.rs | 18 ++ 6 files changed, 223 insertions(+) create mode 100644 crates/owlry-plugin-converter/Cargo.toml create mode 100644 crates/owlry-plugin-converter/src/currency.rs create mode 100644 crates/owlry-plugin-converter/src/lib.rs create mode 100644 crates/owlry-plugin-converter/src/parser.rs create mode 100644 crates/owlry-plugin-converter/src/units.rs diff --git a/Cargo.toml b/Cargo.toml index cf70406..fe8ef21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ 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", diff --git a/crates/owlry-plugin-converter/Cargo.toml b/crates/owlry-plugin-converter/Cargo.toml new file mode 100644 index 0000000..18ec388 --- /dev/null +++ b/crates/owlry-plugin-converter/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "owlry-plugin-converter" +version = "1.0.0" +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 new file mode 100644 index 0000000..b243fc8 --- /dev/null +++ b/crates/owlry-plugin-converter/src/currency.rs @@ -0,0 +1,17 @@ +use std::collections::HashMap; + +pub struct CurrencyRates { + pub rates: HashMap, +} + +pub fn get_rates() -> Option { + None +} + +pub fn resolve_currency_code(_alias: &str) -> Option { + None +} + +pub fn is_currency_alias(_alias: &str) -> bool { + false +} diff --git a/crates/owlry-plugin-converter/src/lib.rs b/crates/owlry-plugin-converter/src/lib.rs new file mode 100644 index 0000000..7e11b85 --- /dev/null +++ b/crates/owlry-plugin-converter/src/lib.rs @@ -0,0 +1,156 @@ +//! 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"; +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!("sh -c 'echo -n \"{}\" | wl-copy'", r.raw_value), + ) + .with_description(format!( + "{} {} = {} {}", + format_number(parsed.value), + parsed.from_symbol, + r.display_value, + r.target_symbol, + )) + .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() +} diff --git a/crates/owlry-plugin-converter/src/parser.rs b/crates/owlry-plugin-converter/src/parser.rs new file mode 100644 index 0000000..d3242ff --- /dev/null +++ b/crates/owlry-plugin-converter/src/parser.rs @@ -0,0 +1,10 @@ +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 { + None +} diff --git a/crates/owlry-plugin-converter/src/units.rs b/crates/owlry-plugin-converter/src/units.rs new file mode 100644 index 0000000..4d14da0 --- /dev/null +++ b/crates/owlry-plugin-converter/src/units.rs @@ -0,0 +1,18 @@ +pub struct ConversionResult { + pub value: f64, + pub raw_value: String, + pub display_value: String, + pub target_symbol: String, +} + +pub fn find_unit(_alias: &str) -> Option<&'static str> { + None +} + +pub fn convert_to(_value: &f64, _from: &str, _to: &str) -> Option { + None +} + +pub fn convert_common(_value: &f64, _from: &str) -> Vec { + vec![] +}