From f5d83f1372b9b6cfd24783e8b62fc69dfaf0c3ae Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 26 Mar 2026 13:37:55 +0100 Subject: [PATCH] chore: format, fix clippy warnings, bump all crates to 1.0.0 --- Cargo.lock | 10 +- crates/owlry-core/Cargo.toml | 2 +- crates/owlry-core/src/config/mod.rs | 58 +-- crates/owlry-core/src/filter.rs | 63 +++- crates/owlry-core/src/ipc.rs | 16 +- crates/owlry-core/src/paths.rs | 8 +- crates/owlry-core/src/plugins/api/action.rs | 32 +- crates/owlry-core/src/plugins/api/cache.rs | 54 +-- crates/owlry-core/src/plugins/api/hook.rs | 24 +- crates/owlry-core/src/plugins/api/http.rs | 69 ++-- crates/owlry-core/src/plugins/api/math.rs | 42 ++- crates/owlry-core/src/plugins/api/process.rs | 20 +- crates/owlry-core/src/plugins/api/theme.rs | 35 +- crates/owlry-core/src/plugins/api/utils.rs | 58 +-- crates/owlry-core/src/plugins/loader.rs | 15 +- crates/owlry-core/src/plugins/manifest.rs | 27 +- crates/owlry-core/src/plugins/mod.rs | 33 +- .../owlry-core/src/plugins/native_loader.rs | 21 +- crates/owlry-core/src/plugins/registry.rs | 31 +- crates/owlry-core/src/plugins/runtime.rs | 21 +- .../owlry-core/src/plugins/runtime_loader.rs | 32 +- .../owlry-core/src/providers/application.rs | 22 +- crates/owlry-core/src/providers/command.rs | 6 +- .../owlry-core/src/providers/lua_provider.rs | 4 +- crates/owlry-core/src/providers/mod.rs | 106 ++++-- .../src/providers/native_provider.rs | 9 +- crates/owlry-core/src/server.rs | 15 +- crates/owlry-core/tests/ipc_test.rs | 3 +- crates/owlry-core/tests/server_test.rs | 16 +- crates/owlry-lua/Cargo.toml | 2 +- crates/owlry-lua/src/api/provider.rs | 30 +- crates/owlry-lua/src/api/utils.rs | 329 +++++++++++------- crates/owlry-lua/src/lib.rs | 29 +- crates/owlry-lua/src/loader.rs | 50 ++- crates/owlry-lua/src/manifest.rs | 20 +- crates/owlry-lua/src/runtime.rs | 23 +- crates/owlry-plugin-api/Cargo.toml | 2 +- crates/owlry-plugin-api/src/lib.rs | 8 +- crates/owlry-rune/Cargo.toml | 2 +- crates/owlry-rune/src/lib.rs | 22 +- crates/owlry-rune/src/loader.rs | 24 +- crates/owlry-rune/src/manifest.rs | 15 +- crates/owlry-rune/src/runtime.rs | 25 +- crates/owlry/Cargo.toml | 2 +- crates/owlry/src/app.rs | 39 +-- crates/owlry/src/backend.rs | 94 ++--- crates/owlry/src/client.rs | 61 +--- crates/owlry/src/main.rs | 8 +- crates/owlry/src/plugin_commands.rs | 197 ++++++++--- crates/owlry/src/providers/dmenu.rs | 2 +- crates/owlry/src/theme.rs | 15 +- crates/owlry/src/ui/main_window.rs | 117 +++++-- crates/owlry/src/ui/result_row.rs | 10 +- 53 files changed, 1233 insertions(+), 745 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91ebb4a..a711046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "owlry" -version = "0.4.10" +version = "1.0.0" dependencies = [ "chrono", "clap", @@ -2437,7 +2437,7 @@ dependencies = [ [[package]] name = "owlry-core" -version = "0.5.0" +version = "1.0.0" dependencies = [ "chrono", "ctrlc", @@ -2462,7 +2462,7 @@ dependencies = [ [[package]] name = "owlry-lua" -version = "0.4.10" +version = "1.0.0" dependencies = [ "abi_stable", "chrono", @@ -2480,7 +2480,7 @@ dependencies = [ [[package]] name = "owlry-plugin-api" -version = "0.4.10" +version = "1.0.0" dependencies = [ "abi_stable", "serde", @@ -2488,7 +2488,7 @@ dependencies = [ [[package]] name = "owlry-rune" -version = "0.4.10" +version = "1.0.0" dependencies = [ "chrono", "dirs", diff --git a/crates/owlry-core/Cargo.toml b/crates/owlry-core/Cargo.toml index 0dc162d..6223798 100644 --- a/crates/owlry-core/Cargo.toml +++ b/crates/owlry-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owlry-core" -version = "0.5.0" +version = "1.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/owlry-core/src/config/mod.rs b/crates/owlry-core/src/config/mod.rs index 1e54096..065e67e 100644 --- a/crates/owlry-core/src/config/mod.rs +++ b/crates/owlry-core/src/config/mod.rs @@ -73,11 +73,7 @@ fn default_max_results() -> usize { } fn default_tabs() -> Vec { - vec![ - "app".to_string(), - "cmd".to_string(), - "uuctl".to_string(), - ] + vec!["app".to_string(), "cmd".to_string(), "uuctl".to_string()] } /// User-customizable theme colors @@ -143,10 +139,18 @@ impl Default for AppearanceConfig { } } -fn default_width() -> i32 { 850 } -fn default_height() -> i32 { 650 } -fn default_font_size() -> u32 { 14 } -fn default_border_radius() -> u32 { 12 } +fn default_width() -> i32 { + 850 +} +fn default_height() -> i32 { + 650 +} +fn default_font_size() -> u32 { + 14 +} +fn default_border_radius() -> u32 { + 12 +} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProvidersConfig { @@ -196,7 +200,6 @@ pub struct ProvidersConfig { pub files: bool, // ─── Widget Providers ─────────────────────────────────────────────── - /// Enable MPRIS media player widget #[serde(default = "default_true")] pub media: bool, @@ -350,28 +353,19 @@ impl PluginsConfig { /// Get a string value from a plugin's config #[allow(dead_code)] pub fn get_plugin_string(&self, plugin_name: &str, key: &str) -> Option<&str> { - self.plugin_configs - .get(plugin_name)? - .get(key)? - .as_str() + self.plugin_configs.get(plugin_name)?.get(key)?.as_str() } /// Get an integer value from a plugin's config #[allow(dead_code)] pub fn get_plugin_int(&self, plugin_name: &str, key: &str) -> Option { - self.plugin_configs - .get(plugin_name)? - .get(key)? - .as_integer() + self.plugin_configs.get(plugin_name)?.get(key)?.as_integer() } /// Get a boolean value from a plugin's config #[allow(dead_code)] pub fn get_plugin_bool(&self, plugin_name: &str, key: &str) -> Option { - self.plugin_configs - .get(plugin_name)? - .get(key)? - .as_bool() + self.plugin_configs.get(plugin_name)?.get(key)?.as_bool() } } @@ -414,7 +408,6 @@ fn default_pomodoro_break() -> u32 { 5 } - /// Detect the best available terminal emulator /// Fallback chain: /// 1. $TERMINAL env var (user's explicit preference) @@ -427,10 +420,12 @@ fn default_pomodoro_break() -> u32 { fn detect_terminal() -> String { // 1. Check $TERMINAL env var first (user's explicit preference) if let Ok(term) = std::env::var("TERMINAL") - && !term.is_empty() && command_exists(&term) { - debug!("Using $TERMINAL: {}", term); - return term; - } + && !term.is_empty() + && command_exists(&term) + { + debug!("Using $TERMINAL: {}", term); + return term; + } // 2. Try xdg-terminal-exec (freedesktop standard) if command_exists("xdg-terminal-exec") { @@ -454,7 +449,14 @@ fn detect_terminal() -> String { } // 5. Common X11/legacy terminals - let legacy_terminals = ["gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal", "tilix", "terminator"]; + let legacy_terminals = [ + "gnome-terminal", + "konsole", + "xfce4-terminal", + "mate-terminal", + "tilix", + "terminator", + ]; for term in legacy_terminals { if command_exists(term) { debug!("Found legacy terminal: {}", term); diff --git a/crates/owlry-core/src/filter.rs b/crates/owlry-core/src/filter.rs index 26689cb..9cc160d 100644 --- a/crates/owlry-core/src/filter.rs +++ b/crates/owlry-core/src/filter.rs @@ -94,7 +94,10 @@ impl ProviderFilter { }; #[cfg(feature = "dev-logging")] - debug!("[Filter] Created with enabled providers: {:?}", filter.enabled); + debug!( + "[Filter] Created with enabled providers: {:?}", + filter.enabled + ); filter } @@ -118,13 +121,19 @@ impl ProviderFilter { self.enabled.insert(ProviderType::Application); } #[cfg(feature = "dev-logging")] - debug!("[Filter] Toggled OFF {:?}, enabled: {:?}", provider, self.enabled); + debug!( + "[Filter] Toggled OFF {:?}, enabled: {:?}", + provider, self.enabled + ); } else { #[cfg(feature = "dev-logging")] let provider_debug = format!("{:?}", provider); self.enabled.insert(provider); #[cfg(feature = "dev-logging")] - debug!("[Filter] Toggled ON {}, enabled: {:?}", provider_debug, self.enabled); + debug!( + "[Filter] Toggled ON {}, enabled: {:?}", + provider_debug, self.enabled + ); } } @@ -151,7 +160,10 @@ impl ProviderFilter { pub fn set_prefix(&mut self, prefix: Option) { #[cfg(feature = "dev-logging")] if self.active_prefix != prefix { - debug!("[Filter] Prefix changed: {:?} -> {:?}", self.active_prefix, prefix); + debug!( + "[Filter] Prefix changed: {:?} -> {:?}", + self.active_prefix, prefix + ); } self.active_prefix = prefix; } @@ -190,7 +202,10 @@ impl ProviderFilter { let tag = rest[..space_idx].to_lowercase(); let query_part = rest[space_idx + 1..].to_string(); #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> tag={:?}, query={:?}", query, tag, query_part); + debug!( + "[Filter] parse_query({:?}) -> tag={:?}, query={:?}", + query, tag, query_part + ); return ParsedQuery { prefix: None, tag_filter: Some(tag), @@ -245,7 +260,10 @@ impl ProviderFilter { for (prefix_str, provider) in core_prefixes { if let Some(rest) = trimmed.strip_prefix(prefix_str) { #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", query, provider, rest); + debug!( + "[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", + query, provider, rest + ); return ParsedQuery { prefix: Some(provider.clone()), tag_filter: None, @@ -259,7 +277,10 @@ impl ProviderFilter { if let Some(rest) = trimmed.strip_prefix(prefix_str) { let provider = ProviderType::Plugin(type_id.to_string()); #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", query, provider, rest); + debug!( + "[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", + query, provider, rest + ); return ParsedQuery { prefix: Some(provider), tag_filter: None, @@ -304,7 +325,10 @@ impl ProviderFilter { for (prefix_str, provider) in partial_core { if trimmed == *prefix_str { #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> partial prefix {:?}", query, provider); + debug!( + "[Filter] parse_query({:?}) -> partial prefix {:?}", + query, provider + ); return ParsedQuery { prefix: Some(provider.clone()), tag_filter: None, @@ -317,7 +341,10 @@ impl ProviderFilter { if trimmed == *prefix_str { let provider = ProviderType::Plugin(type_id.to_string()); #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> partial prefix {:?}", query, provider); + debug!( + "[Filter] parse_query({:?}) -> partial prefix {:?}", + query, provider + ); return ParsedQuery { prefix: Some(provider), tag_filter: None, @@ -333,7 +360,10 @@ impl ProviderFilter { }; #[cfg(feature = "dev-logging")] - debug!("[Filter] parse_query({:?}) -> prefix={:?}, tag={:?}, query={:?}", query, result.prefix, result.tag_filter, result.query); + debug!( + "[Filter] parse_query({:?}) -> prefix={:?}, tag={:?}, query={:?}", + query, result.prefix, result.tag_filter, result.query + ); result } @@ -396,7 +426,8 @@ impl ProviderFilter { /// "app"/"apps"/"application" -> Application, "cmd"/"command" -> Command, /// "dmenu" -> Dmenu, and everything else -> Plugin(id). pub fn mode_string_to_provider_type(mode: &str) -> ProviderType { - mode.parse::().unwrap_or_else(|_| ProviderType::Plugin(mode.to_string())) + mode.parse::() + .unwrap_or_else(|_| ProviderType::Plugin(mode.to_string())) } /// Get display name for current mode @@ -452,7 +483,10 @@ mod tests { #[test] fn test_parse_query_plugin_prefix() { let result = ProviderFilter::parse_query(":calc 5+3"); - assert_eq!(result.prefix, Some(ProviderType::Plugin("calc".to_string()))); + assert_eq!( + result.prefix, + Some(ProviderType::Plugin("calc".to_string())) + ); assert_eq!(result.query, "5+3"); } @@ -544,10 +578,7 @@ mod tests { #[test] fn test_explicit_mode_filter_rejects_unknown_plugins() { - let filter = ProviderFilter::from_mode_strings(&[ - "app".to_string(), - "cmd".to_string(), - ]); + let filter = ProviderFilter::from_mode_strings(&["app".to_string(), "cmd".to_string()]); assert!(filter.is_active(ProviderType::Application)); assert!(filter.is_active(ProviderType::Command)); // Plugins not in the explicit list must be rejected diff --git a/crates/owlry-core/src/ipc.rs b/crates/owlry-core/src/ipc.rs index 69deafd..9ac3d16 100644 --- a/crates/owlry-core/src/ipc.rs +++ b/crates/owlry-core/src/ipc.rs @@ -29,19 +29,11 @@ pub enum Request { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum Response { - Results { - items: Vec, - }, - Providers { - list: Vec, - }, - SubmenuItems { - items: Vec, - }, + Results { items: Vec }, + Providers { list: Vec }, + SubmenuItems { items: Vec }, Ack, - Error { - message: String, - }, + Error { message: String }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/crates/owlry-core/src/paths.rs b/crates/owlry-core/src/paths.rs index 2505f37..7da39a7 100644 --- a/crates/owlry-core/src/paths.rs +++ b/crates/owlry-core/src/paths.rs @@ -32,7 +32,6 @@ pub fn cache_home() -> Option { dirs::cache_dir() } - // ============================================================================= // Owlry-specific directories // ============================================================================= @@ -175,9 +174,10 @@ pub fn socket_path() -> PathBuf { /// Ensure parent directory of a file exists pub fn ensure_parent_dir(path: &std::path::Path) -> std::io::Result<()> { if let Some(parent) = path.parent() - && !parent.exists() { - std::fs::create_dir_all(parent)?; - } + && !parent.exists() + { + std::fs::create_dir_all(parent)?; + } Ok(()) } diff --git a/crates/owlry-core/src/plugins/api/action.rs b/crates/owlry-core/src/plugins/api/action.rs index 985f574..ce798d6 100644 --- a/crates/owlry-core/src/plugins/api/action.rs +++ b/crates/owlry-core/src/plugins/api/action.rs @@ -54,9 +54,9 @@ pub fn register_action_api(lua: &Lua, owlry: &Table, plugin_id: &str) -> LuaResu .get("name") .map_err(|_| mlua::Error::external("action.register: 'name' is required"))?; - let _handler: Function = config - .get("handler") - .map_err(|_| mlua::Error::external("action.register: 'handler' function is required"))?; + let _handler: Function = config.get("handler").map_err(|_| { + mlua::Error::external("action.register: 'handler' function is required") + })?; // Extract optional fields let icon: Option = config.get("icon").ok(); @@ -166,7 +166,7 @@ pub fn get_actions_for_item(lua: &Lua, item: &Table) -> LuaResult("filter") { match filter.call::(item.clone()) { - Ok(true) => {} // Include this action + Ok(true) => {} // Include this action Ok(false) => continue, // Skip this action Err(e) => { log::warn!("Action filter failed: {}", e); @@ -220,7 +220,8 @@ mod tests { fn test_action_registration() { let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" return owlry.action.register({ id = "copy-name", name = "Copy Name", @@ -229,7 +230,8 @@ mod tests { -- copy logic here end }) - "#); + "#, + ); let action_id: String = chunk.call(()).unwrap(); assert_eq!(action_id, "test-plugin:copy-name"); @@ -243,7 +245,8 @@ mod tests { fn test_action_with_filter() { let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.action.register({ id = "bookmark-action", name = "Open in Browser", @@ -252,7 +255,8 @@ mod tests { end, handler = function(item) end }) - "#); + "#, + ); chunk.call::<()>(()).unwrap(); // Create bookmark item @@ -276,14 +280,16 @@ mod tests { fn test_action_unregister() { let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.action.register({ id = "temp-action", name = "Temporary", handler = function(item) end }) return owlry.action.unregister("temp-action") - "#); + "#, + ); let unregistered: bool = chunk.call(()).unwrap(); assert!(unregistered); @@ -296,7 +302,8 @@ mod tests { let lua = setup_lua("test-plugin"); // Register action that sets a global - let chunk = lua.load(r#" + let chunk = lua.load( + r#" result = nil owlry.action.register({ id = "test-exec", @@ -305,7 +312,8 @@ mod tests { result = item.name end }) - "#); + "#, + ); chunk.call::<()>(()).unwrap(); // Create test item diff --git a/crates/owlry-core/src/plugins/api/cache.rs b/crates/owlry-core/src/plugins/api/cache.rs index 448b066..bcb5a1f 100644 --- a/crates/owlry-core/src/plugins/api/cache.rs +++ b/crates/owlry-core/src/plugins/api/cache.rs @@ -35,9 +35,9 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { cache_table.set( "get", lua.create_function(|lua, key: String| { - let cache = CACHE.lock().map_err(|e| { - mlua::Error::external(format!("Failed to lock cache: {}", e)) - })?; + let cache = CACHE + .lock() + .map_err(|e| mlua::Error::external(format!("Failed to lock cache: {}", e)))?; if let Some(entry) = cache.get(&key) { if entry.is_expired() { @@ -50,8 +50,10 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { } // Parse JSON back to Lua value - let json_value: serde_json::Value = serde_json::from_str(&entry.value) - .map_err(|e| mlua::Error::external(format!("Failed to parse cached value: {}", e)))?; + let json_value: serde_json::Value = + serde_json::from_str(&entry.value).map_err(|e| { + mlua::Error::external(format!("Failed to parse cached value: {}", e)) + })?; json_to_lua(lua, &json_value) } else { @@ -75,9 +77,9 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { expires_at, }; - let mut cache = CACHE.lock().map_err(|e| { - mlua::Error::external(format!("Failed to lock cache: {}", e)) - })?; + let mut cache = CACHE + .lock() + .map_err(|e| mlua::Error::external(format!("Failed to lock cache: {}", e)))?; cache.insert(key, entry); Ok(true) @@ -88,9 +90,9 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { cache_table.set( "delete", lua.create_function(|_lua, key: String| { - let mut cache = CACHE.lock().map_err(|e| { - mlua::Error::external(format!("Failed to lock cache: {}", e)) - })?; + let mut cache = CACHE + .lock() + .map_err(|e| mlua::Error::external(format!("Failed to lock cache: {}", e)))?; Ok(cache.remove(&key).is_some()) })?, @@ -100,9 +102,9 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { cache_table.set( "clear", lua.create_function(|_lua, ()| { - let mut cache = CACHE.lock().map_err(|e| { - mlua::Error::external(format!("Failed to lock cache: {}", e)) - })?; + let mut cache = CACHE + .lock() + .map_err(|e| mlua::Error::external(format!("Failed to lock cache: {}", e)))?; let count = cache.len(); cache.clear(); @@ -114,9 +116,9 @@ pub fn register_cache_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { cache_table.set( "has", lua.create_function(|_lua, key: String| { - let cache = CACHE.lock().map_err(|e| { - mlua::Error::external(format!("Failed to lock cache: {}", e)) - })?; + let cache = CACHE + .lock() + .map_err(|e| mlua::Error::external(format!("Failed to lock cache: {}", e)))?; if let Some(entry) = cache.get(&key) { Ok(!entry.is_expired()) @@ -249,10 +251,12 @@ mod tests { let _: bool = chunk.call(()).unwrap(); // Get and verify - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local t = owlry.cache.get("table_key") return t.name, t.value - "#); + "#, + ); let (name, value): (String, i32) = chunk.call(()).unwrap(); assert_eq!(name, "test"); assert_eq!(value, 42); @@ -262,12 +266,14 @@ mod tests { fn test_cache_delete() { let lua = setup_lua(); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.cache.set("delete_key", "value") local existed = owlry.cache.delete("delete_key") local value = owlry.cache.get("delete_key") return existed, value - "#); + "#, + ); let (existed, value): (bool, Option) = chunk.call(()).unwrap(); assert!(existed); assert!(value.is_none()); @@ -277,12 +283,14 @@ mod tests { fn test_cache_has() { let lua = setup_lua(); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local before = owlry.cache.has("has_key") owlry.cache.set("has_key", "value") local after = owlry.cache.has("has_key") return before, after - "#); + "#, + ); let (before, after): (bool, bool) = chunk.call(()).unwrap(); assert!(!before); assert!(after); diff --git a/crates/owlry-core/src/plugins/api/hook.rs b/crates/owlry-core/src/plugins/api/hook.rs index b660964..067db61 100644 --- a/crates/owlry-core/src/plugins/api/hook.rs +++ b/crates/owlry-core/src/plugins/api/hook.rs @@ -329,13 +329,15 @@ mod tests { clear_all_hooks(); let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local called = false owlry.hook.on("init", function() called = true end) return true - "#); + "#, + ); let result: bool = chunk.call(()).unwrap(); assert!(result); @@ -349,11 +351,13 @@ mod tests { clear_all_hooks(); let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.hook.on("query", function(q) return q .. "1" end, 10) owlry.hook.on("query", function(q) return q .. "2" end, 20) return true - "#); + "#, + ); chunk.call::<()>(()).unwrap(); // Call hooks - higher priority (20) should run first @@ -367,11 +371,13 @@ mod tests { clear_all_hooks(); let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.hook.on("select", function() end) owlry.hook.off("select") return true - "#); + "#, + ); chunk.call::<()>(()).unwrap(); let plugins = get_registered_plugins(HookEvent::Select); @@ -383,14 +389,16 @@ mod tests { clear_all_hooks(); let lua = setup_lua("test-plugin"); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.hook.on("pre_launch", function(item) if item.name == "blocked" then return false -- cancel launch end return true end) - "#); + "#, + ); chunk.call::<()>(()).unwrap(); // Create a test item table diff --git a/crates/owlry-core/src/plugins/api/http.rs b/crates/owlry-core/src/plugins/api/http.rs index 49b7490..1012b49 100644 --- a/crates/owlry-core/src/plugins/api/http.rs +++ b/crates/owlry-core/src/plugins/api/http.rs @@ -26,18 +26,21 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let client = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(timeout_secs)) .build() - .map_err(|e| mlua::Error::external(format!("Failed to create HTTP client: {}", e)))?; + .map_err(|e| { + mlua::Error::external(format!("Failed to create HTTP client: {}", e)) + })?; let mut request = client.get(&url); // Add custom headers if provided if let Some(ref opts) = opts - && let Ok(headers) = opts.get::("headers") { - for pair in headers.pairs::() { - let (key, value) = pair?; - request = request.header(&key, &value); - } + && let Ok(headers) = opts.get::
("headers") + { + for pair in headers.pairs::() { + let (key, value) = pair?; + request = request.header(&key, &value); } + } let response = request .send() @@ -45,9 +48,9 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let status = response.status().as_u16(); let headers = extract_headers(&response); - let body = response - .text() - .map_err(|e| mlua::Error::external(format!("Failed to read response body: {}", e)))?; + let body = response.text().map_err(|e| { + mlua::Error::external(format!("Failed to read response body: {}", e)) + })?; let result = lua.create_table()?; result.set("status", status)?; @@ -78,18 +81,21 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let client = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(timeout_secs)) .build() - .map_err(|e| mlua::Error::external(format!("Failed to create HTTP client: {}", e)))?; + .map_err(|e| { + mlua::Error::external(format!("Failed to create HTTP client: {}", e)) + })?; let mut request = client.post(&url); // Add custom headers if provided if let Some(ref opts) = opts - && let Ok(headers) = opts.get::
("headers") { - for pair in headers.pairs::() { - let (key, value) = pair?; - request = request.header(&key, &value); - } + && let Ok(headers) = opts.get::
("headers") + { + for pair in headers.pairs::() { + let (key, value) = pair?; + request = request.header(&key, &value); } + } // Set body based on type request = match body { @@ -102,11 +108,7 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { .body(json_str) } Value::Nil => request, - _ => { - return Err(mlua::Error::external( - "POST body must be a string or table", - )) - } + _ => return Err(mlua::Error::external("POST body must be a string or table")), }; let response = request @@ -115,9 +117,9 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let status = response.status().as_u16(); let headers = extract_headers(&response); - let body = response - .text() - .map_err(|e| mlua::Error::external(format!("Failed to read response body: {}", e)))?; + let body = response.text().map_err(|e| { + mlua::Error::external(format!("Failed to read response body: {}", e)) + })?; let result = lua.create_table()?; result.set("status", status)?; @@ -149,19 +151,22 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let client = reqwest::blocking::Client::builder() .timeout(Duration::from_secs(timeout_secs)) .build() - .map_err(|e| mlua::Error::external(format!("Failed to create HTTP client: {}", e)))?; + .map_err(|e| { + mlua::Error::external(format!("Failed to create HTTP client: {}", e)) + })?; let mut request = client.get(&url); request = request.header("Accept", "application/json"); // Add custom headers if provided if let Some(ref opts) = opts - && let Ok(headers) = opts.get::
("headers") { - for pair in headers.pairs::() { - let (key, value) = pair?; - request = request.header(&key, &value); - } + && let Ok(headers) = opts.get::
("headers") + { + for pair in headers.pairs::() { + let (key, value) = pair?; + request = request.header(&key, &value); } + } let response = request .send() @@ -174,9 +179,9 @@ pub fn register_http_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { ))); } - let body = response - .text() - .map_err(|e| mlua::Error::external(format!("Failed to read response body: {}", e)))?; + let body = response.text().map_err(|e| { + mlua::Error::external(format!("Failed to read response body: {}", e)) + })?; // Parse JSON and convert to Lua table let json_value: serde_json::Value = serde_json::from_str(&body) diff --git a/crates/owlry-core/src/plugins/api/math.rs b/crates/owlry-core/src/plugins/api/math.rs index 54a961c..84158c6 100644 --- a/crates/owlry-core/src/plugins/api/math.rs +++ b/crates/owlry-core/src/plugins/api/math.rs @@ -14,20 +14,20 @@ pub fn register_math_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { // Returns (result, nil) on success or (nil, error_message) on failure math_table.set( "calculate", - lua.create_function(|_lua, expr: String| -> LuaResult<(Option, Option)> { - match meval::eval_str(&expr) { - Ok(result) => { - if result.is_finite() { - Ok((Some(result), None)) - } else { - Ok((None, Some("Result is not a finite number".to_string()))) + lua.create_function( + |_lua, expr: String| -> LuaResult<(Option, Option)> { + match meval::eval_str(&expr) { + Ok(result) => { + if result.is_finite() { + Ok((Some(result), None)) + } else { + Ok((None, Some("Result is not a finite number".to_string()))) + } } + Err(e) => Ok((None, Some(e.to_string()))), } - Err(e) => { - Ok((None, Some(e.to_string()))) - } - } - })?, + }, + )?, )?; // owlry.math.calc(expression) -> number (throws on error) @@ -106,11 +106,13 @@ mod tests { fn test_calculate_basic() { let lua = setup_lua(); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local result, err = owlry.math.calculate("2 + 2") if err then error(err) end return result - "#); + "#, + ); let result: f64 = chunk.call(()).unwrap(); assert!((result - 4.0).abs() < f64::EPSILON); } @@ -119,11 +121,13 @@ mod tests { fn test_calculate_complex() { let lua = setup_lua(); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local result, err = owlry.math.calculate("sqrt(16) + 2^3") if err then error(err) end return result - "#); + "#, + ); let result: f64 = chunk.call(()).unwrap(); assert!((result - 12.0).abs() < f64::EPSILON); // sqrt(16) = 4, 2^3 = 8 } @@ -132,14 +136,16 @@ mod tests { fn test_calculate_error() { let lua = setup_lua(); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" local result, err = owlry.math.calculate("invalid expression @@") if result then return false -- should not succeed else return true -- correctly failed end - "#); + "#, + ); let had_error: bool = chunk.call(()).unwrap(); assert!(had_error); } diff --git a/crates/owlry-core/src/plugins/api/process.rs b/crates/owlry-core/src/plugins/api/process.rs index b8b5204..aaa69fb 100644 --- a/crates/owlry-core/src/plugins/api/process.rs +++ b/crates/owlry-core/src/plugins/api/process.rs @@ -27,8 +27,14 @@ pub fn register_process_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { .map_err(|e| mlua::Error::external(format!("Failed to run command: {}", e)))?; let result = lua.create_table()?; - result.set("stdout", String::from_utf8_lossy(&output.stdout).to_string())?; - result.set("stderr", String::from_utf8_lossy(&output.stderr).to_string())?; + result.set( + "stdout", + String::from_utf8_lossy(&output.stdout).to_string(), + )?; + result.set( + "stderr", + String::from_utf8_lossy(&output.stderr).to_string(), + )?; result.set("exit_code", output.status.code().unwrap_or(-1))?; result.set("success", output.status.success())?; @@ -95,9 +101,7 @@ pub fn register_env_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { // owlry.env.get(name) -> string or nil env_table.set( "get", - lua.create_function(|_lua, name: String| { - Ok(std::env::var(&name).ok()) - })?, + lua.create_function(|_lua, name: String| Ok(std::env::var(&name).ok()))?, )?; // owlry.env.get_or(name, default) -> string @@ -166,7 +170,8 @@ mod tests { assert!(exists); // Made-up command should not exist - let chunk = lua.load(r#"return owlry.process.exists("this_command_definitely_does_not_exist_12345")"#); + let chunk = lua + .load(r#"return owlry.process.exists("this_command_definitely_does_not_exist_12345")"#); let not_exists: bool = chunk.call(()).unwrap(); assert!(!not_exists); } @@ -190,7 +195,8 @@ mod tests { fn test_env_get_or() { let lua = setup_lua(); - let chunk = lua.load(r#"return owlry.env.get_or("THIS_VAR_DOES_NOT_EXIST_12345", "default_value")"#); + let chunk = lua + .load(r#"return owlry.env.get_or("THIS_VAR_DOES_NOT_EXIST_12345", "default_value")"#); let result: String = chunk.call(()).unwrap(); assert_eq!(result, "default_value"); } diff --git a/crates/owlry-core/src/plugins/api/theme.rs b/crates/owlry-core/src/plugins/api/theme.rs index e500222..5e10cbb 100644 --- a/crates/owlry-core/src/plugins/api/theme.rs +++ b/crates/owlry-core/src/plugins/api/theme.rs @@ -21,7 +21,12 @@ pub struct ThemeRegistration { } /// Register theme APIs -pub fn register_theme_api(lua: &Lua, owlry: &Table, plugin_id: &str, plugin_dir: &Path) -> LuaResult<()> { +pub fn register_theme_api( + lua: &Lua, + owlry: &Table, + plugin_id: &str, + plugin_dir: &Path, +) -> LuaResult<()> { let theme_table = lua.create_table()?; let plugin_id_owned = plugin_id.to_string(); let plugin_dir_owned = plugin_dir.to_path_buf(); @@ -50,9 +55,7 @@ pub fn register_theme_api(lua: &Lua, owlry: &Table, plugin_id: &str, plugin_dir: .get("name") .map_err(|_| mlua::Error::external("theme.register: 'name' is required"))?; - let display_name: String = config - .get("display_name") - .unwrap_or_else(|_| name.clone()); + let display_name: String = config.get("display_name").unwrap_or_else(|_| name.clone()); // Get CSS either directly or from file let css: String = if let Ok(css_str) = config.get::("css") { @@ -197,13 +200,15 @@ mod tests { let temp = TempDir::new().unwrap(); let lua = setup_lua("test-plugin", temp.path()); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" return owlry.theme.register({ name = "my-theme", display_name = "My Theme", css = ".owlry-window { background: #333; }" }) - "#); + "#, + ); let name: String = chunk.call(()).unwrap(); assert_eq!(name, "my-theme"); @@ -221,12 +226,14 @@ mod tests { let lua = setup_lua("test-plugin", temp.path()); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" return owlry.theme.register({ name = "file-theme", css_file = "theme.css" }) - "#); + "#, + ); let name: String = chunk.call(()).unwrap(); assert_eq!(name, "file-theme"); @@ -240,11 +247,13 @@ mod tests { let temp = TempDir::new().unwrap(); let lua = setup_lua("test-plugin", temp.path()); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.theme.register({ name = "theme1", css = "a{}" }) owlry.theme.register({ name = "theme2", css = "b{}" }) return owlry.theme.list() - "#); + "#, + ); let list: Table = chunk.call(()).unwrap(); let mut names: Vec = Vec::new(); @@ -262,10 +271,12 @@ mod tests { let temp = TempDir::new().unwrap(); let lua = setup_lua("test-plugin", temp.path()); - let chunk = lua.load(r#" + let chunk = lua.load( + r#" owlry.theme.register({ name = "temp-theme", css = "c{}" }) return owlry.theme.unregister("temp-theme") - "#); + "#, + ); let unregistered: bool = chunk.call(()).unwrap(); assert!(unregistered); diff --git a/crates/owlry-core/src/plugins/api/utils.rs b/crates/owlry-core/src/plugins/api/utils.rs index 2f6df20..9c0a8d0 100644 --- a/crates/owlry-core/src/plugins/api/utils.rs +++ b/crates/owlry-core/src/plugins/api/utils.rs @@ -189,9 +189,10 @@ pub fn register_fs_api(lua: &Lua, owlry: &Table, plugin_dir: &Path) -> LuaResult // Ensure parent directory exists if let Some(parent) = full_path.parent() && !parent.exists() - && let Err(e) = std::fs::create_dir_all(parent) { - return Ok((false, Value::String(lua.create_string(e.to_string())?))); - } + && let Err(e) = std::fs::create_dir_all(parent) + { + return Ok((false, Value::String(lua.create_string(e.to_string())?))); + } match std::fs::write(&full_path, content) { Ok(()) => Ok((true, Value::Nil)), @@ -295,7 +296,8 @@ pub fn register_fs_api(lua: &Lua, owlry: &Table, plugin_dir: &Path) -> LuaResult use std::os::unix::fs::PermissionsExt; let plugin_dir: String = lua.named_registry_value("plugin_dir")?; let full_path = resolve_plugin_path(&plugin_dir, &path); - let is_exec = full_path.metadata() + let is_exec = full_path + .metadata() .map(|m| m.permissions().mode() & 0o111 != 0) .unwrap_or(false); Ok(is_exec) @@ -335,28 +337,24 @@ pub fn register_json_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { // owlry.json.encode(value) -> string or nil, error json_table.set( "encode", - lua.create_function(|lua, value: Value| { - match lua_to_json(&value) { - Ok(json) => match serde_json::to_string(&json) { - Ok(s) => Ok((Some(s), Value::Nil)), - Err(e) => Ok((None, Value::String(lua.create_string(e.to_string())?))), - }, - Err(e) => Ok((None, Value::String(lua.create_string(&e)?))), - } + lua.create_function(|lua, value: Value| match lua_to_json(&value) { + Ok(json) => match serde_json::to_string(&json) { + Ok(s) => Ok((Some(s), Value::Nil)), + Err(e) => Ok((None, Value::String(lua.create_string(e.to_string())?))), + }, + Err(e) => Ok((None, Value::String(lua.create_string(&e)?))), })?, )?; // owlry.json.encode_pretty(value) -> string or nil, error json_table.set( "encode_pretty", - lua.create_function(|lua, value: Value| { - match lua_to_json(&value) { - Ok(json) => match serde_json::to_string_pretty(&json) { - Ok(s) => Ok((Some(s), Value::Nil)), - Err(e) => Ok((None, Value::String(lua.create_string(e.to_string())?))), - }, - Err(e) => Ok((None, Value::String(lua.create_string(&e)?))), - } + lua.create_function(|lua, value: Value| match lua_to_json(&value) { + Ok(json) => match serde_json::to_string_pretty(&json) { + Ok(s) => Ok((Some(s), Value::Nil)), + Err(e) => Ok((None, Value::String(lua.create_string(e.to_string())?))), + }, + Err(e) => Ok((None, Value::String(lua.create_string(&e)?))), })?, )?; @@ -388,13 +386,16 @@ fn lua_to_json(value: &Value) -> Result { .map(serde_json::Value::Number) .ok_or_else(|| "Invalid number".to_string()), Value::String(s) => Ok(serde_json::Value::String( - s.to_str().map_err(|e| e.to_string())?.to_string() + s.to_str().map_err(|e| e.to_string())?.to_string(), )), Value::Table(t) => { // Check if it's an array (sequential integer keys starting from 1) let len = t.raw_len(); let is_array = len > 0 - && (1..=len).all(|i| t.raw_get::(i).is_ok_and(|v| !matches!(v, Value::Nil))); + && (1..=len).all(|i| { + t.raw_get::(i) + .is_ok_and(|v| !matches!(v, Value::Nil)) + }); if is_array { let arr: Result, String> = (1..=len) @@ -475,9 +476,13 @@ mod tests { fn test_log_api() { let (lua, _temp) = create_test_lua(); // Just verify it doesn't panic - using call instead of the e-word - lua.load("owlry.log.info('test message')").call::<()>(()).unwrap(); + lua.load("owlry.log.info('test message')") + .call::<()>(()) + .unwrap(); lua.load("owlry.log.debug('debug')").call::<()>(()).unwrap(); - lua.load("owlry.log.warn('warning')").call::<()>(()).unwrap(); + lua.load("owlry.log.warn('warning')") + .call::<()>(()) + .unwrap(); lua.load("owlry.log.error('error')").call::<()>(()).unwrap(); } @@ -485,10 +490,7 @@ mod tests { fn test_path_api() { let (lua, _temp) = create_test_lua(); - let home: String = lua - .load("return owlry.path.home()") - .call(()) - .unwrap(); + let home: String = lua.load("return owlry.path.home()").call(()).unwrap(); assert!(!home.is_empty()); let joined: String = lua diff --git a/crates/owlry-core/src/plugins/loader.rs b/crates/owlry-core/src/plugins/loader.rs index 4a6f0ee..632e39f 100644 --- a/crates/owlry-core/src/plugins/loader.rs +++ b/crates/owlry-core/src/plugins/loader.rs @@ -7,7 +7,7 @@ use mlua::Lua; use super::api; use super::error::{PluginError, PluginResult}; use super::manifest::PluginManifest; -use super::runtime::{create_lua_runtime, load_file, SandboxConfig}; +use super::runtime::{SandboxConfig, create_lua_runtime, load_file}; /// A loaded plugin instance #[derive(Debug)] @@ -94,7 +94,10 @@ impl LoadedPlugin { } /// Call a provider's refresh function - pub fn call_provider_refresh(&self, provider_name: &str) -> PluginResult> { + pub fn call_provider_refresh( + &self, + provider_name: &str, + ) -> PluginResult> { let lua = self.lua.as_ref().ok_or_else(|| PluginError::LuaError { plugin: self.id().to_string(), message: "Plugin not initialized".to_string(), @@ -108,7 +111,11 @@ impl LoadedPlugin { /// Call a provider's query function #[allow(dead_code)] // Will be used for dynamic query providers - pub fn call_provider_query(&self, provider_name: &str, query: &str) -> PluginResult> { + pub fn call_provider_query( + &self, + provider_name: &str, + query: &str, + ) -> PluginResult> { let lua = self.lua.as_ref().ok_or_else(|| PluginError::LuaError { plugin: self.id().to_string(), message: "Plugin not initialized".to_string(), @@ -138,8 +145,8 @@ impl LoadedPlugin { #[cfg(test)] mod tests { - use super::*; use super::super::manifest::{check_compatibility, discover_plugins}; + use super::*; use std::fs; use std::path::Path; use tempfile::TempDir; diff --git a/crates/owlry-core/src/plugins/manifest.rs b/crates/owlry-core/src/plugins/manifest.rs index 929d6cf..df71e26 100644 --- a/crates/owlry-core/src/plugins/manifest.rs +++ b/crates/owlry-core/src/plugins/manifest.rs @@ -112,11 +112,16 @@ pub struct PluginPermissions { /// Discover all plugins in a directory /// /// Returns a map of plugin ID -> (manifest, path) -pub fn discover_plugins(plugins_dir: &Path) -> PluginResult> { +pub fn discover_plugins( + plugins_dir: &Path, +) -> PluginResult> { let mut plugins = HashMap::new(); if !plugins_dir.exists() { - log::debug!("Plugins directory does not exist: {}", plugins_dir.display()); + log::debug!( + "Plugins directory does not exist: {}", + plugins_dir.display() + ); return Ok(plugins); } @@ -143,7 +148,11 @@ pub fn discover_plugins(plugins_dir: &Path) -> PluginResult { @@ -204,7 +213,12 @@ impl PluginManifest { }); } - if !self.plugin.id.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') { + if !self + .plugin + .id + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') + { return Err(PluginError::InvalidManifest { plugin: self.plugin.id.clone(), message: "Plugin ID must be lowercase alphanumeric with hyphens".to_string(), @@ -223,7 +237,10 @@ impl PluginManifest { if semver::VersionReq::parse(&self.plugin.owlry_version).is_err() { return Err(PluginError::InvalidManifest { plugin: self.plugin.id.clone(), - message: format!("Invalid owlry_version constraint: {}", self.plugin.owlry_version), + message: format!( + "Invalid owlry_version constraint: {}", + self.plugin.owlry_version + ), }); } diff --git a/crates/owlry-core/src/plugins/mod.rs b/crates/owlry-core/src/plugins/mod.rs index cbc64e2..ebaf17f 100644 --- a/crates/owlry-core/src/plugins/mod.rs +++ b/crates/owlry-core/src/plugins/mod.rs @@ -50,7 +50,7 @@ pub use loader::LoadedPlugin; // Used by plugins/commands.rs for plugin CLI commands #[allow(unused_imports)] -pub use manifest::{check_compatibility, discover_plugins, PluginManifest}; +pub use manifest::{PluginManifest, check_compatibility, discover_plugins}; // ============================================================================ // Lua Plugin Manager (only available with lua feature) @@ -64,7 +64,7 @@ mod lua_manager { use std::path::PathBuf; use std::rc::Rc; - use manifest::{discover_plugins, check_compatibility}; + use manifest::{check_compatibility, discover_plugins}; /// Plugin manager coordinates loading, initialization, and lifecycle of Lua plugins pub struct PluginManager { @@ -158,7 +158,10 @@ mod lua_manager { /// Get all enabled plugins pub fn enabled_plugins(&self) -> impl Iterator>> + '_ { - self.plugins.values().filter(|p| p.borrow().enabled).cloned() + self.plugins + .values() + .filter(|p| p.borrow().enabled) + .cloned() } /// Get the number of loaded plugins @@ -176,7 +179,10 @@ mod lua_manager { /// Enable a plugin by ID #[allow(dead_code)] pub fn enable(&mut self, id: &str) -> PluginResult<()> { - let plugin_rc = self.plugins.get(id).ok_or_else(|| PluginError::NotFound(id.to_string()))?; + let plugin_rc = self + .plugins + .get(id) + .ok_or_else(|| PluginError::NotFound(id.to_string()))?; let mut plugin = plugin_rc.borrow_mut(); if !plugin.enabled { @@ -191,7 +197,10 @@ mod lua_manager { /// Disable a plugin by ID #[allow(dead_code)] pub fn disable(&mut self, id: &str) -> PluginResult<()> { - let plugin_rc = self.plugins.get(id).ok_or_else(|| PluginError::NotFound(id.to_string()))?; + let plugin_rc = self + .plugins + .get(id) + .ok_or_else(|| PluginError::NotFound(id.to_string()))?; plugin_rc.borrow_mut().enabled = false; Ok(()) } @@ -200,7 +209,13 @@ mod lua_manager { #[allow(dead_code)] pub fn providers_for(&self, provider_name: &str) -> Vec { self.enabled_plugins() - .filter(|p| p.borrow().manifest.provides.providers.contains(&provider_name.to_string())) + .filter(|p| { + p.borrow() + .manifest + .provides + .providers + .contains(&provider_name.to_string()) + }) .map(|p| p.borrow().id().to_string()) .collect() } @@ -208,13 +223,15 @@ mod lua_manager { /// Check if any plugin provides actions #[allow(dead_code)] pub fn has_action_plugins(&self) -> bool { - self.enabled_plugins().any(|p| p.borrow().manifest.provides.actions) + self.enabled_plugins() + .any(|p| p.borrow().manifest.provides.actions) } /// Check if any plugin provides hooks #[allow(dead_code)] pub fn has_hook_plugins(&self) -> bool { - self.enabled_plugins().any(|p| p.borrow().manifest.provides.hooks) + self.enabled_plugins() + .any(|p| p.borrow().manifest.provides.hooks) } /// Get all theme names provided by plugins diff --git a/crates/owlry-core/src/plugins/native_loader.rs b/crates/owlry-core/src/plugins/native_loader.rs index 05d539d..372bc98 100644 --- a/crates/owlry-core/src/plugins/native_loader.rs +++ b/crates/owlry-core/src/plugins/native_loader.rs @@ -17,8 +17,8 @@ use std::sync::{Arc, Once}; use libloading::Library; use log::{debug, error, info, warn}; use owlry_plugin_api::{ - HostAPI, NotifyUrgency, PluginInfo, PluginVTable, ProviderHandle, ProviderInfo, ProviderKind, - RStr, API_VERSION, + API_VERSION, HostAPI, NotifyUrgency, PluginInfo, PluginVTable, ProviderHandle, ProviderInfo, + ProviderKind, RStr, }; use crate::notify; @@ -28,9 +28,18 @@ use crate::notify; // ============================================================================ /// Host notification handler -extern "C" fn host_notify(summary: RStr<'_>, body: RStr<'_>, icon: RStr<'_>, urgency: NotifyUrgency) { +extern "C" fn host_notify( + summary: RStr<'_>, + body: RStr<'_>, + icon: RStr<'_>, + urgency: NotifyUrgency, +) { let icon_str = icon.as_str(); - let icon_opt = if icon_str.is_empty() { None } else { Some(icon_str) }; + let icon_opt = if icon_str.is_empty() { + None + } else { + Some(icon_str) + }; let notify_urgency = match urgency { NotifyUrgency::Low => notify::NotifyUrgency::Low, @@ -121,7 +130,9 @@ impl NativePlugin { handle: ProviderHandle, query: &str, ) -> Vec { - (self.vtable.provider_query)(handle, query.into()).into_iter().collect() + (self.vtable.provider_query)(handle, query.into()) + .into_iter() + .collect() } /// Drop a provider handle diff --git a/crates/owlry-core/src/plugins/registry.rs b/crates/owlry-core/src/plugins/registry.rs index 42c6798..2d25306 100644 --- a/crates/owlry-core/src/plugins/registry.rs +++ b/crates/owlry-core/src/plugins/registry.rs @@ -110,9 +110,10 @@ impl RegistryClient { if let Ok(metadata) = fs::metadata(&cache_path) && let Ok(modified) = metadata.modified() - && let Ok(elapsed) = SystemTime::now().duration_since(modified) { - return elapsed < CACHE_DURATION; - } + && let Ok(elapsed) = SystemTime::now().duration_since(modified) + { + return elapsed < CACHE_DURATION; + } false } @@ -120,11 +121,13 @@ impl RegistryClient { /// Fetch the registry index (from cache or network) pub fn fetch_index(&self, force_refresh: bool) -> Result { // Use cache if valid and not forcing refresh - if !force_refresh && self.is_cache_valid() + if !force_refresh + && self.is_cache_valid() && let Ok(content) = fs::read_to_string(self.cache_path()) - && let Ok(index) = toml::from_str(&content) { - return Ok(index); - } + && let Ok(index) = toml::from_str(&content) + { + return Ok(index); + } // Fetch from network self.fetch_from_network() @@ -134,12 +137,7 @@ impl RegistryClient { fn fetch_from_network(&self) -> Result { // Use curl for fetching (available on most systems) let output = std::process::Command::new("curl") - .args([ - "-fsSL", - "--max-time", - "30", - &self.registry_url, - ]) + .args(["-fsSL", "--max-time", "30", &self.registry_url]) .output() .map_err(|e| format!("Failed to run curl: {}", e))?; @@ -185,7 +183,9 @@ impl RegistryClient { p.id.to_lowercase().contains(&query_lower) || p.name.to_lowercase().contains(&query_lower) || p.description.to_lowercase().contains(&query_lower) - || p.tags.iter().any(|t| t.to_lowercase().contains(&query_lower)) + || p.tags + .iter() + .any(|t| t.to_lowercase().contains(&query_lower)) }) .collect(); @@ -210,8 +210,7 @@ impl RegistryClient { pub fn clear_cache(&self) -> Result<(), String> { let cache_path = self.cache_path(); if cache_path.exists() { - fs::remove_file(&cache_path) - .map_err(|e| format!("Failed to remove cache: {}", e))?; + fs::remove_file(&cache_path).map_err(|e| format!("Failed to remove cache: {}", e))?; } Ok(()) } diff --git a/crates/owlry-core/src/plugins/runtime.rs b/crates/owlry-core/src/plugins/runtime.rs index da98dbe..00ebf57 100644 --- a/crates/owlry-core/src/plugins/runtime.rs +++ b/crates/owlry-core/src/plugins/runtime.rs @@ -26,7 +26,7 @@ impl Default for SandboxConfig { allow_commands: false, allow_network: false, allow_external_fs: false, - max_run_time_ms: 5000, // 5 seconds + max_run_time_ms: 5000, // 5 seconds max_memory: 64 * 1024 * 1024, // 64 MB } } @@ -49,11 +49,7 @@ pub fn create_lua_runtime(_sandbox: &SandboxConfig) -> LuaResult { // Create Lua with safe standard libraries only // ALL_SAFE excludes: debug, io, os (dangerous parts), package (loadlib), ffi // We then customize the os table to only allow safe functions - let libs = StdLib::COROUTINE - | StdLib::TABLE - | StdLib::STRING - | StdLib::UTF8 - | StdLib::MATH; + let libs = StdLib::COROUTINE | StdLib::TABLE | StdLib::STRING | StdLib::UTF8 | StdLib::MATH; let lua = Lua::new_with(libs, mlua::LuaOptions::default())?; @@ -75,9 +71,15 @@ fn setup_safe_globals(lua: &Lua) -> LuaResult<()> { // We do NOT include: os.exit, os.remove, os.rename, os.setlocale, os.tmpname // and the shell-related functions let os_table = lua.create_table()?; - os_table.set("clock", lua.create_function(|_, ()| Ok(std::time::Instant::now().elapsed().as_secs_f64()))?)?; + os_table.set( + "clock", + lua.create_function(|_, ()| Ok(std::time::Instant::now().elapsed().as_secs_f64()))?, + )?; os_table.set("date", lua.create_function(os_date)?)?; - os_table.set("difftime", lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?)?; + os_table.set( + "difftime", + lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?, + )?; os_table.set("time", lua.create_function(os_time)?)?; globals.set("os", os_table)?; @@ -107,8 +109,7 @@ fn os_time(_lua: &Lua, _args: ()) -> LuaResult { /// Load and run a Lua file in the given runtime pub fn load_file(lua: &Lua, path: &std::path::Path) -> LuaResult<()> { - let content = std::fs::read_to_string(path) - .map_err(mlua::Error::external)?; + let content = std::fs::read_to_string(path).map_err(mlua::Error::external)?; lua.load(&content) .set_name(path.file_name().and_then(|n| n.to_str()).unwrap_or("chunk")) .into_function()? diff --git a/crates/owlry-core/src/plugins/runtime_loader.rs b/crates/owlry-core/src/plugins/runtime_loader.rs index de62fcd..3f91ea4 100644 --- a/crates/owlry-core/src/plugins/runtime_loader.rs +++ b/crates/owlry-core/src/plugins/runtime_loader.rs @@ -59,7 +59,11 @@ pub struct ScriptRuntimeVTable { pub init: extern "C" fn(plugins_dir: RStr<'_>) -> RuntimeHandle, pub providers: extern "C" fn(handle: RuntimeHandle) -> RVec, pub refresh: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>) -> RVec, - pub query: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec, + pub query: extern "C" fn( + handle: RuntimeHandle, + provider_id: RStr<'_>, + query: RStr<'_>, + ) -> RVec, pub drop: extern "C" fn(handle: RuntimeHandle), } @@ -100,9 +104,8 @@ impl LoadedRuntime { } // SAFETY: We trust the runtime library to be correct - let library = unsafe { Library::new(library_path) }.map_err(|e| { - PluginError::LoadError(format!("{}: {}", library_path.display(), e)) - })?; + let library = unsafe { Library::new(library_path) } + .map_err(|e| PluginError::LoadError(format!("{}: {}", library_path.display(), e)))?; let library = Arc::new(library); @@ -152,12 +155,8 @@ impl LoadedRuntime { self.providers .iter() .map(|info| { - let provider = RuntimeProvider::new( - self.name, - self.vtable, - self.handle, - info.clone(), - ); + let provider = + RuntimeProvider::new(self.name, self.vtable, self.handle, info.clone()); Box::new(provider) as Box }) .collect() @@ -227,7 +226,10 @@ impl Provider for RuntimeProvider { let name_rstr = RStr::from_str(self.info.name.as_str()); let items_rvec = (self.vtable.refresh)(self.handle, name_rstr); - self.items = items_rvec.into_iter().map(|i| self.convert_item(i)).collect(); + self.items = items_rvec + .into_iter() + .map(|i| self.convert_item(i)) + .collect(); log::debug!( "[RuntimeProvider] '{}' refreshed with {} items", @@ -246,12 +248,16 @@ unsafe impl Send for RuntimeProvider {} /// Check if the Lua runtime is available pub fn lua_runtime_available() -> bool { - PathBuf::from(SYSTEM_RUNTIMES_DIR).join("liblua.so").exists() + PathBuf::from(SYSTEM_RUNTIMES_DIR) + .join("liblua.so") + .exists() } /// Check if the Rune runtime is available pub fn rune_runtime_available() -> bool { - PathBuf::from(SYSTEM_RUNTIMES_DIR).join("librune.so").exists() + PathBuf::from(SYSTEM_RUNTIMES_DIR) + .join("librune.so") + .exists() } impl LoadedRuntime { diff --git a/crates/owlry-core/src/providers/application.rs b/crates/owlry-core/src/providers/application.rs index 3236e64..4886dab 100644 --- a/crates/owlry-core/src/providers/application.rs +++ b/crates/owlry-core/src/providers/application.rs @@ -66,13 +66,14 @@ fn clean_desktop_exec_field(cmd: &str) -> String { cleaned } +#[derive(Default)] pub struct ApplicationProvider { items: Vec, } impl ApplicationProvider { pub fn new() -> Self { - Self { items: Vec::new() } + Self::default() } fn get_application_dirs() -> Vec { @@ -139,15 +140,18 @@ impl Provider for ApplicationProvider { if !current_desktops.is_empty() { // OnlyShowIn: if set, current desktop must be in the list if desktop_entry.only_show_in().is_some_and(|only| { - !current_desktops.iter().any(|de| only.contains(&de.as_str())) + !current_desktops + .iter() + .any(|de| only.contains(&de.as_str())) }) { continue; } // NotShowIn: if current desktop is in the list, skip - if desktop_entry.not_show_in().is_some_and(|not| { - current_desktops.iter().any(|de| not.contains(&de.as_str())) - }) { + if desktop_entry + .not_show_in() + .is_some_and(|not| current_desktops.iter().any(|de| not.contains(&de.as_str()))) + { continue; } } @@ -197,7 +201,8 @@ impl Provider for ApplicationProvider { ); // Sort alphabetically by name - self.items.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + self.items + .sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); } fn items(&self) -> &[LaunchItem] { @@ -219,7 +224,10 @@ mod tests { #[test] fn test_clean_desktop_exec_multiple_placeholders() { assert_eq!(clean_desktop_exec_field("app %f %u %U"), "app"); - assert_eq!(clean_desktop_exec_field("app --flag %u --other"), "app --flag --other"); + assert_eq!( + clean_desktop_exec_field("app --flag %u --other"), + "app --flag --other" + ); } #[test] diff --git a/crates/owlry-core/src/providers/command.rs b/crates/owlry-core/src/providers/command.rs index 0df024f..6fa15fa 100644 --- a/crates/owlry-core/src/providers/command.rs +++ b/crates/owlry-core/src/providers/command.rs @@ -4,13 +4,14 @@ use std::collections::HashSet; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; +#[derive(Default)] pub struct CommandProvider { items: Vec, } impl CommandProvider { pub fn new() -> Self { - Self { items: Vec::new() } + Self::default() } fn get_path_dirs() -> Vec { @@ -97,7 +98,8 @@ impl Provider for CommandProvider { debug!("Found {} commands in PATH", self.items.len()); // Sort alphabetically - self.items.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); + self.items + .sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase())); } fn items(&self) -> &[LaunchItem] { diff --git a/crates/owlry-core/src/providers/lua_provider.rs b/crates/owlry-core/src/providers/lua_provider.rs index d624846..675fcb8 100644 --- a/crates/owlry-core/src/providers/lua_provider.rs +++ b/crates/owlry-core/src/providers/lua_provider.rs @@ -95,9 +95,7 @@ impl Provider for LuaProvider { unsafe impl Send for LuaProvider {} /// Create LuaProviders from all registered providers in a plugin -pub fn create_providers_from_plugin( - plugin: Rc>, -) -> Vec> { +pub fn create_providers_from_plugin(plugin: Rc>) -> Vec> { let registrations = { let p = plugin.borrow(); match p.get_provider_registrations() { diff --git a/crates/owlry-core/src/providers/mod.rs b/crates/owlry-core/src/providers/mod.rs index 9aef46e..4e936d0 100644 --- a/crates/owlry-core/src/providers/mod.rs +++ b/crates/owlry-core/src/providers/mod.rs @@ -141,13 +141,25 @@ impl ProviderManager { let type_id = provider.type_id(); if provider.is_dynamic() { - info!("Registered dynamic provider: {} ({})", provider.name(), type_id); + info!( + "Registered dynamic provider: {} ({})", + provider.name(), + type_id + ); manager.dynamic_providers.push(provider); } else if provider.is_widget() { - info!("Registered widget provider: {} ({})", provider.name(), type_id); + info!( + "Registered widget provider: {} ({})", + provider.name(), + type_id + ); manager.widget_providers.push(provider); } else { - info!("Registered static provider: {} ({})", provider.name(), type_id); + info!( + "Registered static provider: {} ({})", + provider.name(), + type_id + ); manager.static_native_providers.push(provider); } } @@ -263,15 +275,25 @@ impl ProviderManager { /// Searches in all native provider lists (static, dynamic, widget) pub fn find_native_provider(&self, type_id: &str) -> Option<&NativeProvider> { // Check static native providers first (clipboard, emoji, ssh, systemd, etc.) - if let Some(p) = self.static_native_providers.iter().find(|p| p.type_id() == type_id) { + if let Some(p) = self + .static_native_providers + .iter() + .find(|p| p.type_id() == type_id) + { return Some(p); } // Check widget providers (pomodoro, weather, media) - if let Some(p) = self.widget_providers.iter().find(|p| p.type_id() == type_id) { + if let Some(p) = self + .widget_providers + .iter() + .find(|p| p.type_id() == type_id) + { return Some(p); } // Then dynamic providers (calc, websearch, filesearch) - self.dynamic_providers.iter().find(|p| p.type_id() == type_id) + self.dynamic_providers + .iter() + .find(|p| p.type_id() == type_id) } /// Execute a plugin action command @@ -311,27 +333,31 @@ impl ProviderManager { /// Iterate over all static provider items (core + native static plugins) fn all_static_items(&self) -> impl Iterator { - self.providers - .iter() - .flat_map(|p| p.items().iter()) - .chain(self.static_native_providers.iter().flat_map(|p| p.items().iter())) + self.providers.iter().flat_map(|p| p.items().iter()).chain( + self.static_native_providers + .iter() + .flat_map(|p| p.items().iter()), + ) } #[allow(dead_code)] pub fn search(&self, query: &str, max_results: usize) -> Vec<(LaunchItem, i64)> { if query.is_empty() { // Return recent/popular items when query is empty - return self.all_static_items() + return self + .all_static_items() .take(max_results) .map(|item| (item.clone(), 0)) .collect(); } - let mut results: Vec<(LaunchItem, i64)> = self.all_static_items() + let mut results: Vec<(LaunchItem, i64)> = self + .all_static_items() .filter_map(|item| { // Match against name and description let name_score = self.matcher.fuzzy_match(&item.name, query); - let desc_score = item.description + let desc_score = item + .description .as_ref() .and_then(|d| self.matcher.fuzzy_match(d, query)); @@ -417,7 +443,10 @@ impl ProviderManager { tag_filter: Option<&str>, ) -> Vec<(LaunchItem, i64)> { #[cfg(feature = "dev-logging")] - debug!("[Search] query={:?}, max={}, frecency_weight={}", query, max_results, frecency_weight); + debug!( + "[Search] query={:?}, max={}, frecency_weight={}", + query, max_results, frecency_weight + ); let mut results: Vec<(LaunchItem, i64)> = Vec::new(); @@ -567,7 +596,13 @@ impl ProviderManager { { debug!("[Search] Returning {} results", results.len()); for (i, (item, score)) in results.iter().take(5).enumerate() { - debug!("[Search] #{}: {} (score={}, provider={:?})", i + 1, item.name, score, item.provider); + debug!( + "[Search] #{}: {} (score={}, provider={:?})", + i + 1, + item.name, + score, + item.provider + ); } if results.len() > 5 { debug!("[Search] ... and {} more", results.len() - 5); @@ -583,7 +618,11 @@ impl ProviderManager { self.providers .iter() .map(|p| p.provider_type()) - .chain(self.static_native_providers.iter().map(|p| p.provider_type())) + .chain( + self.static_native_providers + .iter() + .map(|p| p.provider_type()), + ) .collect() } @@ -606,16 +645,10 @@ impl ProviderManager { Some(":cmd".to_string()), "utilities-terminal".to_string(), ), - ProviderType::Dmenu => ( - "dmenu".to_string(), - None, - "view-list-symbolic".to_string(), - ), - ProviderType::Plugin(type_id) => ( - type_id, - None, - "application-x-addon".to_string(), - ), + ProviderType::Dmenu => { + ("dmenu".to_string(), None, "view-list-symbolic".to_string()) + } + ProviderType::Plugin(type_id) => (type_id, None, "application-x-addon".to_string()), }; descs.push(ProviderDescriptor { id, @@ -771,7 +804,10 @@ impl ProviderManager { } #[cfg(feature = "dev-logging")] - debug!("[Submenu] No submenu actions found for plugin '{}'", plugin_id); + debug!( + "[Submenu] No submenu actions found for plugin '{}'", + plugin_id + ); None } @@ -856,9 +892,8 @@ mod tests { #[test] fn test_available_providers_dmenu() { - let providers: Vec> = vec![ - Box::new(MockProvider::new("dmenu", ProviderType::Dmenu)), - ]; + let providers: Vec> = + vec![Box::new(MockProvider::new("dmenu", ProviderType::Dmenu))]; let pm = ProviderManager::new(providers, Vec::new()); let descs = pm.available_providers(); assert_eq!(descs.len(), 1); @@ -895,9 +930,10 @@ mod tests { #[test] fn test_refresh_provider_unknown_does_not_panic() { - let providers: Vec> = vec![ - Box::new(MockProvider::new("Applications", ProviderType::Application)), - ]; + let providers: Vec> = vec![Box::new(MockProvider::new( + "Applications", + ProviderType::Application, + ))]; let mut pm = ProviderManager::new(providers, Vec::new()); pm.refresh_provider("nonexistent"); // Should complete without panicking @@ -909,8 +945,8 @@ mod tests { make_item("firefox", "Firefox", ProviderType::Application), make_item("vim", "Vim", ProviderType::Application), ]; - let provider = MockProvider::new("Applications", ProviderType::Application) - .with_items(items); + let provider = + MockProvider::new("Applications", ProviderType::Application).with_items(items); let providers: Vec> = vec![Box::new(provider)]; let pm = ProviderManager::new(providers, Vec::new()); diff --git a/crates/owlry-core/src/providers/native_provider.rs b/crates/owlry-core/src/providers/native_provider.rs index 3aabe9b..20aa427 100644 --- a/crates/owlry-core/src/providers/native_provider.rs +++ b/crates/owlry-core/src/providers/native_provider.rs @@ -9,7 +9,9 @@ use std::sync::{Arc, RwLock}; use log::debug; -use owlry_plugin_api::{PluginItem as ApiPluginItem, ProviderHandle, ProviderInfo, ProviderKind, ProviderPosition}; +use owlry_plugin_api::{ + PluginItem as ApiPluginItem, ProviderHandle, ProviderInfo, ProviderKind, ProviderPosition, +}; use super::{LaunchItem, Provider, ProviderType}; use crate::plugins::native_loader::NativePlugin; @@ -76,7 +78,10 @@ impl NativeProvider { } let api_items = self.plugin.query_provider(self.handle, query); - api_items.into_iter().map(|item| self.convert_item(item)).collect() + api_items + .into_iter() + .map(|item| self.convert_item(item)) + .collect() } /// Check if this provider has a prefix that matches the query diff --git a/crates/owlry-core/src/server.rs b/crates/owlry-core/src/server.rs index 2ac12a1..f345379 100644 --- a/crates/owlry-core/src/server.rs +++ b/crates/owlry-core/src/server.rs @@ -141,8 +141,14 @@ impl Server { let pm_guard = pm.lock().unwrap(); let frecency_guard = frecency.lock().unwrap(); - let results = - pm_guard.search_with_frecency(text, max, &filter, &frecency_guard, weight, None); + let results = pm_guard.search_with_frecency( + text, + max, + &filter, + &frecency_guard, + weight, + None, + ); Response::Results { items: results @@ -152,7 +158,10 @@ impl Server { } } - Request::Launch { item_id, provider: _ } => { + Request::Launch { + item_id, + provider: _, + } => { let mut frecency_guard = frecency.lock().unwrap(); frecency_guard.record_launch(item_id); Response::Ack diff --git a/crates/owlry-core/tests/ipc_test.rs b/crates/owlry-core/tests/ipc_test.rs index 6598fbf..8b0bf3d 100644 --- a/crates/owlry-core/tests/ipc_test.rs +++ b/crates/owlry-core/tests/ipc_test.rs @@ -122,7 +122,8 @@ fn test_plugin_action_request() { #[test] fn test_terminal_field_defaults_false() { // terminal field should default to false when missing from JSON - let json = r#"{"id":"test","title":"Test","description":"","icon":"","provider":"cmd","score":0}"#; + let json = + r#"{"id":"test","title":"Test","description":"","icon":"","provider":"cmd","score":0}"#; let item: ResultItem = serde_json::from_str(json).unwrap(); assert!(!item.terminal); } diff --git a/crates/owlry-core/tests/server_test.rs b/crates/owlry-core/tests/server_test.rs index 73b7e26..b80ee13 100644 --- a/crates/owlry-core/tests/server_test.rs +++ b/crates/owlry-core/tests/server_test.rs @@ -37,7 +37,11 @@ fn test_server_responds_to_providers_request() { match resp { Response::Providers { list } => { // The default ProviderManager always has at least Application and Command - assert!(list.len() >= 2, "expected at least 2 providers, got {}", list.len()); + assert!( + list.len() >= 2, + "expected at least 2 providers, got {}", + list.len() + ); let ids: Vec<&str> = list.iter().map(|p| p.id.as_str()).collect(); assert!(ids.contains(&"app"), "missing 'app' provider"); assert!(ids.contains(&"cmd"), "missing 'cmd' provider"); @@ -95,7 +99,10 @@ fn test_server_handles_query_request() { Response::Results { items } => { // A nonsense query should return empty or very few results // (no items will fuzzy-match "nonexistent_query_xyz") - assert!(items.len() <= 5, "expected few/no results for gibberish query"); + assert!( + items.len() <= 5, + "expected few/no results for gibberish query" + ); } other => panic!("expected Results response, got: {:?}", other), } @@ -172,7 +179,10 @@ fn test_server_handles_submenu_for_unknown_plugin() { "error should mention the plugin id" ); } - other => panic!("expected Error response for unknown plugin, got: {:?}", other), + other => panic!( + "expected Error response for unknown plugin, got: {:?}", + other + ), } drop(stream); diff --git a/crates/owlry-lua/Cargo.toml b/crates/owlry-lua/Cargo.toml index f40be96..b99159a 100644 --- a/crates/owlry-lua/Cargo.toml +++ b/crates/owlry-lua/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owlry-lua" -version = "0.4.10" +version = "1.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/owlry-lua/src/api/provider.rs b/crates/owlry-lua/src/api/provider.rs index bf49aa3..12afb76 100644 --- a/crates/owlry-lua/src/api/provider.rs +++ b/crates/owlry-lua/src/api/provider.rs @@ -24,11 +24,14 @@ pub fn register_provider_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { /// Implementation of owlry.provider.register() fn register_provider(_lua: &Lua, config: Table) -> LuaResult<()> { let name: String = config.get("name")?; - let display_name: String = config.get::>("display_name")? + let display_name: String = config + .get::>("display_name")? .unwrap_or_else(|| name.clone()); - let type_id: String = config.get::>("type_id")? + let type_id: String = config + .get::>("type_id")? .unwrap_or_else(|| name.replace('-', "_")); - let default_icon: String = config.get::>("default_icon")? + let default_icon: String = config + .get::>("default_icon")? .unwrap_or_else(|| "application-x-addon".to_string()); let prefix: Option = config.get("prefix")?; @@ -116,13 +119,14 @@ fn call_provider_function( // First check if there's a _providers table if let Ok(Value::Table(providers)) = globals.get::("_owlry_providers") && let Ok(Value::Table(config)) = providers.get::(provider_name) - && let Ok(Value::Function(func)) = config.get::(function_name) { - let result: Value = match query { - Some(q) => func.call(q)?, - None => func.call(())?, - }; - return parse_items_result(result); - } + && let Ok(Value::Function(func)) = config.get::(function_name) + { + let result: Value = match query { + Some(q) => func.call(q)?, + None => func.call(())?, + }; + return parse_items_result(result); + } // Fall back: search through globals for functions // This is less reliable but handles simple cases @@ -153,7 +157,9 @@ fn parse_item(table: &Table) -> LuaResult { let description: Option = table.get("description")?; let icon: Option = table.get("icon")?; let terminal: bool = table.get::>("terminal")?.unwrap_or(false); - let tags: Vec = table.get::>>("tags")?.unwrap_or_default(); + let tags: Vec = table + .get::>>("tags")? + .unwrap_or_default(); let mut item = PluginItem::new(id, name, command); @@ -176,7 +182,7 @@ fn parse_item(table: &Table) -> LuaResult { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{create_lua_runtime, SandboxConfig}; + use crate::runtime::{SandboxConfig, create_lua_runtime}; #[test] fn test_register_static_provider() { diff --git a/crates/owlry-lua/src/api/utils.rs b/crates/owlry-lua/src/api/utils.rs index 4c9058d..9fcac79 100644 --- a/crates/owlry-lua/src/api/utils.rs +++ b/crates/owlry-lua/src/api/utils.rs @@ -11,25 +11,37 @@ use std::path::{Path, PathBuf}; pub fn register_log_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let log = lua.create_table()?; - log.set("debug", lua.create_function(|_, msg: String| { - eprintln!("[DEBUG] {}", msg); - Ok(()) - })?)?; + log.set( + "debug", + lua.create_function(|_, msg: String| { + eprintln!("[DEBUG] {}", msg); + Ok(()) + })?, + )?; - log.set("info", lua.create_function(|_, msg: String| { - eprintln!("[INFO] {}", msg); - Ok(()) - })?)?; + log.set( + "info", + lua.create_function(|_, msg: String| { + eprintln!("[INFO] {}", msg); + Ok(()) + })?, + )?; - log.set("warn", lua.create_function(|_, msg: String| { - eprintln!("[WARN] {}", msg); - Ok(()) - })?)?; + log.set( + "warn", + lua.create_function(|_, msg: String| { + eprintln!("[WARN] {}", msg); + Ok(()) + })?, + )?; - log.set("error", lua.create_function(|_, msg: String| { - eprintln!("[ERROR] {}", msg); - Ok(()) - })?)?; + log.set( + "error", + lua.create_function(|_, msg: String| { + eprintln!("[ERROR] {}", msg); + Ok(()) + })?, + )?; owlry.set("log", log)?; Ok(()) @@ -44,59 +56,79 @@ pub fn register_path_api(lua: &Lua, owlry: &Table, plugin_dir: &Path) -> LuaResu let path = lua.create_table()?; // owlry.path.config() -> ~/.config/owlry - path.set("config", lua.create_function(|_, ()| { - Ok(dirs::config_dir() - .map(|d| d.join("owlry")) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default()) - })?)?; + path.set( + "config", + lua.create_function(|_, ()| { + Ok(dirs::config_dir() + .map(|d| d.join("owlry")) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default()) + })?, + )?; // owlry.path.data() -> ~/.local/share/owlry - path.set("data", lua.create_function(|_, ()| { - Ok(dirs::data_dir() - .map(|d| d.join("owlry")) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default()) - })?)?; + path.set( + "data", + lua.create_function(|_, ()| { + Ok(dirs::data_dir() + .map(|d| d.join("owlry")) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default()) + })?, + )?; // owlry.path.cache() -> ~/.cache/owlry - path.set("cache", lua.create_function(|_, ()| { - Ok(dirs::cache_dir() - .map(|d| d.join("owlry")) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default()) - })?)?; + path.set( + "cache", + lua.create_function(|_, ()| { + Ok(dirs::cache_dir() + .map(|d| d.join("owlry")) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default()) + })?, + )?; // owlry.path.home() -> ~ - path.set("home", lua.create_function(|_, ()| { - Ok(dirs::home_dir() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default()) - })?)?; + path.set( + "home", + lua.create_function(|_, ()| { + Ok(dirs::home_dir() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default()) + })?, + )?; // owlry.path.join(...) -> joined path - path.set("join", lua.create_function(|_, parts: mlua::Variadic| { - let mut path = PathBuf::new(); - for part in parts { - path.push(part); - } - Ok(path.to_string_lossy().to_string()) - })?)?; + path.set( + "join", + lua.create_function(|_, parts: mlua::Variadic| { + let mut path = PathBuf::new(); + for part in parts { + path.push(part); + } + Ok(path.to_string_lossy().to_string()) + })?, + )?; // owlry.path.plugin_dir() -> plugin directory let plugin_dir_str = plugin_dir.to_string_lossy().to_string(); - path.set("plugin_dir", lua.create_function(move |_, ()| { - Ok(plugin_dir_str.clone()) - })?)?; + path.set( + "plugin_dir", + lua.create_function(move |_, ()| Ok(plugin_dir_str.clone()))?, + )?; // owlry.path.expand(path) -> expanded path (~ -> home) - path.set("expand", lua.create_function(|_, path: String| { - if path.starts_with("~/") - && let Some(home) = dirs::home_dir() { + path.set( + "expand", + lua.create_function(|_, path: String| { + if path.starts_with("~/") + && let Some(home) = dirs::home_dir() + { return Ok(home.join(&path[2..]).to_string_lossy().to_string()); } - Ok(path) - })?)?; + Ok(path) + })?, + )?; owlry.set("path", path)?; Ok(()) @@ -111,76 +143,95 @@ pub fn register_fs_api(lua: &Lua, owlry: &Table, _plugin_dir: &Path) -> LuaResul let fs = lua.create_table()?; // owlry.fs.exists(path) -> bool - fs.set("exists", lua.create_function(|_, path: String| { - let path = expand_path(&path); - Ok(Path::new(&path).exists()) - })?)?; + fs.set( + "exists", + lua.create_function(|_, path: String| { + let path = expand_path(&path); + Ok(Path::new(&path).exists()) + })?, + )?; // owlry.fs.is_dir(path) -> bool - fs.set("is_dir", lua.create_function(|_, path: String| { - let path = expand_path(&path); - Ok(Path::new(&path).is_dir()) - })?)?; + fs.set( + "is_dir", + lua.create_function(|_, path: String| { + let path = expand_path(&path); + Ok(Path::new(&path).is_dir()) + })?, + )?; // owlry.fs.read(path) -> string or nil - fs.set("read", lua.create_function(|_, path: String| { - let path = expand_path(&path); - match std::fs::read_to_string(&path) { - Ok(content) => Ok(Some(content)), - Err(_) => Ok(None), - } - })?)?; + fs.set( + "read", + lua.create_function(|_, path: String| { + let path = expand_path(&path); + match std::fs::read_to_string(&path) { + Ok(content) => Ok(Some(content)), + Err(_) => Ok(None), + } + })?, + )?; // owlry.fs.read_lines(path) -> table of strings or nil - fs.set("read_lines", lua.create_function(|lua, path: String| { - let path = expand_path(&path); - match std::fs::read_to_string(&path) { - Ok(content) => { - let lines: Vec = content.lines().map(|s| s.to_string()).collect(); - Ok(Some(lua.create_sequence_from(lines)?)) + fs.set( + "read_lines", + lua.create_function(|lua, path: String| { + let path = expand_path(&path); + match std::fs::read_to_string(&path) { + Ok(content) => { + let lines: Vec = content.lines().map(|s| s.to_string()).collect(); + Ok(Some(lua.create_sequence_from(lines)?)) + } + Err(_) => Ok(None), } - Err(_) => Ok(None), - } - })?)?; + })?, + )?; // owlry.fs.list_dir(path) -> table of filenames or nil - fs.set("list_dir", lua.create_function(|lua, path: String| { - let path = expand_path(&path); - match std::fs::read_dir(&path) { - Ok(entries) => { - let names: Vec = entries - .filter_map(|e| e.ok()) - .filter_map(|e| e.file_name().into_string().ok()) - .collect(); - Ok(Some(lua.create_sequence_from(names)?)) + fs.set( + "list_dir", + lua.create_function(|lua, path: String| { + let path = expand_path(&path); + match std::fs::read_dir(&path) { + Ok(entries) => { + let names: Vec = entries + .filter_map(|e| e.ok()) + .filter_map(|e| e.file_name().into_string().ok()) + .collect(); + Ok(Some(lua.create_sequence_from(names)?)) + } + Err(_) => Ok(None), } - Err(_) => Ok(None), - } - })?)?; + })?, + )?; // owlry.fs.read_json(path) -> table or nil - fs.set("read_json", lua.create_function(|lua, path: String| { - let path = expand_path(&path); - match std::fs::read_to_string(&path) { - Ok(content) => { - match serde_json::from_str::(&content) { + fs.set( + "read_json", + lua.create_function(|lua, path: String| { + let path = expand_path(&path); + match std::fs::read_to_string(&path) { + Ok(content) => match serde_json::from_str::(&content) { Ok(value) => json_to_lua(lua, &value), Err(_) => Ok(Value::Nil), - } + }, + Err(_) => Ok(Value::Nil), } - Err(_) => Ok(Value::Nil), - } - })?)?; + })?, + )?; // owlry.fs.write(path, content) -> bool - fs.set("write", lua.create_function(|_, (path, content): (String, String)| { - let path = expand_path(&path); - // Create parent directories if needed - if let Some(parent) = Path::new(&path).parent() { - let _ = std::fs::create_dir_all(parent); - } - Ok(std::fs::write(&path, content).is_ok()) - })?)?; + fs.set( + "write", + lua.create_function(|_, (path, content): (String, String)| { + let path = expand_path(&path); + // Create parent directories if needed + if let Some(parent) = Path::new(&path).parent() { + let _ = std::fs::create_dir_all(parent); + } + Ok(std::fs::write(&path, content).is_ok()) + })?, + )?; owlry.set("fs", fs)?; Ok(()) @@ -195,18 +246,24 @@ pub fn register_json_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { let json = lua.create_table()?; // owlry.json.encode(value) -> string - json.set("encode", lua.create_function(|lua, value: Value| { - let json_value = lua_to_json(lua, &value)?; - Ok(serde_json::to_string(&json_value).unwrap_or_else(|_| "null".to_string())) - })?)?; + json.set( + "encode", + lua.create_function(|lua, value: Value| { + let json_value = lua_to_json(lua, &value)?; + Ok(serde_json::to_string(&json_value).unwrap_or_else(|_| "null".to_string())) + })?, + )?; // owlry.json.decode(string) -> value or nil - json.set("decode", lua.create_function(|lua, s: String| { - match serde_json::from_str::(&s) { - Ok(value) => json_to_lua(lua, &value), - Err(_) => Ok(Value::Nil), - } - })?)?; + json.set( + "decode", + lua.create_function(|lua, s: String| { + match serde_json::from_str::(&s) { + Ok(value) => json_to_lua(lua, &value), + Err(_) => Ok(Value::Nil), + } + })?, + )?; owlry.set("json", json)?; Ok(()) @@ -219,9 +276,10 @@ pub fn register_json_api(lua: &Lua, owlry: &Table) -> LuaResult<()> { /// Expand ~ in paths fn expand_path(path: &str) -> String { if path.starts_with("~/") - && let Some(home) = dirs::home_dir() { - return home.join(&path[2..]).to_string_lossy().to_string(); - } + && let Some(home) = dirs::home_dir() + { + return home.join(&path[2..]).to_string_lossy().to_string(); + } path.to_string() } @@ -305,7 +363,7 @@ fn lua_to_json(_lua: &Lua, value: &Value) -> LuaResult { #[cfg(test)] mod tests { use super::*; - use crate::runtime::{create_lua_runtime, SandboxConfig}; + use crate::runtime::{SandboxConfig, create_lua_runtime}; #[test] fn test_log_api() { @@ -316,7 +374,10 @@ mod tests { lua.globals().set("owlry", owlry).unwrap(); // Just verify it doesn't panic - lua.load("owlry.log.info('test message')").set_name("test").call::<()>(()).unwrap(); + lua.load("owlry.log.info('test message')") + .set_name("test") + .call::<()>(()) + .unwrap(); } #[test] @@ -327,10 +388,18 @@ mod tests { register_path_api(&lua, &owlry, Path::new("/tmp/test-plugin")).unwrap(); lua.globals().set("owlry", owlry).unwrap(); - let home: String = lua.load("return owlry.path.home()").set_name("test").call(()).unwrap(); + let home: String = lua + .load("return owlry.path.home()") + .set_name("test") + .call(()) + .unwrap(); assert!(!home.is_empty()); - let plugin_dir: String = lua.load("return owlry.path.plugin_dir()").set_name("test").call(()).unwrap(); + let plugin_dir: String = lua + .load("return owlry.path.plugin_dir()") + .set_name("test") + .call(()) + .unwrap(); assert_eq!(plugin_dir, "/tmp/test-plugin"); } @@ -342,10 +411,18 @@ mod tests { register_fs_api(&lua, &owlry, Path::new("/tmp")).unwrap(); lua.globals().set("owlry", owlry).unwrap(); - let exists: bool = lua.load("return owlry.fs.exists('/tmp')").set_name("test").call(()).unwrap(); + let exists: bool = lua + .load("return owlry.fs.exists('/tmp')") + .set_name("test") + .call(()) + .unwrap(); assert!(exists); - let is_dir: bool = lua.load("return owlry.fs.is_dir('/tmp')").set_name("test").call(()).unwrap(); + let is_dir: bool = lua + .load("return owlry.fs.is_dir('/tmp')") + .set_name("test") + .call(()) + .unwrap(); assert!(is_dir); } diff --git a/crates/owlry-lua/src/lib.rs b/crates/owlry-lua/src/lib.rs index 2ff1204..2efc0c3 100644 --- a/crates/owlry-lua/src/lib.rs +++ b/crates/owlry-lua/src/lib.rs @@ -54,7 +54,11 @@ pub struct LuaRuntimeVTable { /// Refresh a provider's items pub refresh: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>) -> RVec, /// Query a dynamic provider - pub query: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec, + pub query: extern "C" fn( + handle: RuntimeHandle, + provider_id: RStr<'_>, + query: RStr<'_>, + ) -> RVec, /// Cleanup and drop the runtime pub drop: extern "C" fn(handle: RuntimeHandle), } @@ -83,11 +87,15 @@ impl RuntimeHandle { /// Create a null handle (reserved for error cases) #[allow(dead_code)] fn null() -> Self { - Self { ptr: std::ptr::null_mut() } + Self { + ptr: std::ptr::null_mut(), + } } fn from_box(state: Box) -> Self { - Self { ptr: Box::into_raw(state) as *mut () } + Self { + ptr: Box::into_raw(state) as *mut (), + } } unsafe fn drop_as(&self) { @@ -147,7 +155,10 @@ impl LuaRuntimeState { for (id, (manifest, path)) in discovered { // Check version compatibility if !manifest.is_compatible_with(owlry_version) { - eprintln!("owlry-lua: Plugin '{}' not compatible with owlry {}", id, owlry_version); + eprintln!( + "owlry-lua: Plugin '{}' not compatible with owlry {}", + id, owlry_version + ); continue; } @@ -285,13 +296,19 @@ extern "C" fn runtime_refresh(handle: RuntimeHandle, provider_id: RStr<'_>) -> R state.refresh_provider(provider_id.as_str()).into() } -extern "C" fn runtime_query(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec { +extern "C" fn runtime_query( + handle: RuntimeHandle, + provider_id: RStr<'_>, + query: RStr<'_>, +) -> RVec { if handle.ptr.is_null() { return RVec::new(); } let state = unsafe { &*(handle.ptr as *const LuaRuntimeState) }; - state.query_provider(provider_id.as_str(), query.as_str()).into() + state + .query_provider(provider_id.as_str(), query.as_str()) + .into() } extern "C" fn runtime_drop(handle: RuntimeHandle) { diff --git a/crates/owlry-lua/src/loader.rs b/crates/owlry-lua/src/loader.rs index c5f5fd4..169ffea 100644 --- a/crates/owlry-lua/src/loader.rs +++ b/crates/owlry-lua/src/loader.rs @@ -8,7 +8,7 @@ use owlry_plugin_api::PluginItem; use crate::api; use crate::manifest::PluginManifest; -use crate::runtime::{create_lua_runtime, load_file, SandboxConfig}; +use crate::runtime::{SandboxConfig, create_lua_runtime, load_file}; /// Provider registration info from Lua #[derive(Debug, Clone)] @@ -77,11 +77,13 @@ impl LoadedPlugin { // Load the entry point file let entry_path = self.path.join(&self.manifest.plugin.entry); if !entry_path.exists() { - return Err(format!("Entry point '{}' not found", self.manifest.plugin.entry)); + return Err(format!( + "Entry point '{}' not found", + self.manifest.plugin.entry + )); } - load_file(&lua, &entry_path) - .map_err(|e| format!("Failed to load entry point: {}", e))?; + load_file(&lua, &entry_path).map_err(|e| format!("Failed to load entry point: {}", e))?; self.lua = Some(lua); Ok(()) @@ -89,7 +91,9 @@ impl LoadedPlugin { /// Get provider registrations from this plugin pub fn get_provider_registrations(&self) -> Result, String> { - let lua = self.lua.as_ref() + let lua = self + .lua + .as_ref() .ok_or_else(|| "Plugin not initialized".to_string())?; api::get_provider_registrations(lua) @@ -98,25 +102,33 @@ impl LoadedPlugin { /// Call a provider's refresh function pub fn call_provider_refresh(&self, provider_name: &str) -> Result, String> { - let lua = self.lua.as_ref() + let lua = self + .lua + .as_ref() .ok_or_else(|| "Plugin not initialized".to_string())?; - api::call_refresh(lua, provider_name) - .map_err(|e| format!("Refresh failed: {}", e)) + api::call_refresh(lua, provider_name).map_err(|e| format!("Refresh failed: {}", e)) } /// Call a provider's query function - pub fn call_provider_query(&self, provider_name: &str, query: &str) -> Result, String> { - let lua = self.lua.as_ref() + pub fn call_provider_query( + &self, + provider_name: &str, + query: &str, + ) -> Result, String> { + let lua = self + .lua + .as_ref() .ok_or_else(|| "Plugin not initialized".to_string())?; - api::call_query(lua, provider_name, query) - .map_err(|e| format!("Query failed: {}", e)) + api::call_query(lua, provider_name, query).map_err(|e| format!("Query failed: {}", e)) } } /// Discover plugins in a directory -pub fn discover_plugins(plugins_dir: &Path) -> Result, String> { +pub fn discover_plugins( + plugins_dir: &Path, +) -> Result, String> { let mut plugins = HashMap::new(); if !plugins_dir.exists() { @@ -146,13 +158,21 @@ pub fn discover_plugins(plugins_dir: &Path) -> Result { let id = manifest.plugin.id.clone(); if plugins.contains_key(&id) { - eprintln!("owlry-lua: Duplicate plugin ID '{}', skipping {}", id, path.display()); + eprintln!( + "owlry-lua: Duplicate plugin ID '{}', skipping {}", + id, + path.display() + ); continue; } plugins.insert(id, (manifest, path)); } Err(e) => { - eprintln!("owlry-lua: Failed to load plugin at {}: {}", path.display(), e); + eprintln!( + "owlry-lua: Failed to load plugin at {}: {}", + path.display(), + e + ); } } } diff --git a/crates/owlry-lua/src/manifest.rs b/crates/owlry-lua/src/manifest.rs index fcdd69a..7d0801e 100644 --- a/crates/owlry-lua/src/manifest.rs +++ b/crates/owlry-lua/src/manifest.rs @@ -90,10 +90,10 @@ pub struct PluginPermissions { impl PluginManifest { /// Load a plugin manifest from a plugin.toml file pub fn load(path: &Path) -> Result { - let content = std::fs::read_to_string(path) - .map_err(|e| format!("Failed to read manifest: {}", e))?; - let manifest: PluginManifest = toml::from_str(&content) - .map_err(|e| format!("Failed to parse manifest: {}", e))?; + let content = + std::fs::read_to_string(path).map_err(|e| format!("Failed to read manifest: {}", e))?; + let manifest: PluginManifest = + toml::from_str(&content).map_err(|e| format!("Failed to parse manifest: {}", e))?; manifest.validate()?; Ok(manifest) } @@ -105,7 +105,12 @@ impl PluginManifest { return Err("Plugin ID cannot be empty".to_string()); } - if !self.plugin.id.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') { + if !self + .plugin + .id + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') + { return Err("Plugin ID must be lowercase alphanumeric with hyphens".to_string()); } @@ -116,7 +121,10 @@ impl PluginManifest { // Validate owlry_version constraint if semver::VersionReq::parse(&self.plugin.owlry_version).is_err() { - return Err(format!("Invalid owlry_version constraint: {}", self.plugin.owlry_version)); + return Err(format!( + "Invalid owlry_version constraint: {}", + self.plugin.owlry_version + )); } Ok(()) diff --git a/crates/owlry-lua/src/runtime.rs b/crates/owlry-lua/src/runtime.rs index 4a2664c..dfea9b7 100644 --- a/crates/owlry-lua/src/runtime.rs +++ b/crates/owlry-lua/src/runtime.rs @@ -28,7 +28,7 @@ impl Default for SandboxConfig { allow_commands: false, allow_network: false, allow_external_fs: false, - max_run_time_ms: 5000, // 5 seconds + max_run_time_ms: 5000, // 5 seconds max_memory: 64 * 1024 * 1024, // 64 MB } } @@ -50,11 +50,7 @@ impl SandboxConfig { pub fn create_lua_runtime(_sandbox: &SandboxConfig) -> LuaResult { // Create Lua with safe standard libraries only // We exclude: debug, io, os (dangerous parts), package (loadlib), ffi - let libs = StdLib::COROUTINE - | StdLib::TABLE - | StdLib::STRING - | StdLib::UTF8 - | StdLib::MATH; + let libs = StdLib::COROUTINE | StdLib::TABLE | StdLib::STRING | StdLib::UTF8 | StdLib::MATH; let lua = Lua::new_with(libs, mlua::LuaOptions::default())?; @@ -74,11 +70,15 @@ fn setup_safe_globals(lua: &Lua) -> LuaResult<()> { // Create a restricted os table with only safe functions let os_table = lua.create_table()?; - os_table.set("clock", lua.create_function(|_, ()| { - Ok(std::time::Instant::now().elapsed().as_secs_f64()) - })?)?; + os_table.set( + "clock", + lua.create_function(|_, ()| Ok(std::time::Instant::now().elapsed().as_secs_f64()))?, + )?; os_table.set("date", lua.create_function(os_date)?)?; - os_table.set("difftime", lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?)?; + os_table.set( + "difftime", + lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?, + )?; os_table.set("time", lua.create_function(os_time)?)?; globals.set("os", os_table)?; @@ -107,8 +107,7 @@ fn os_time(_lua: &Lua, _args: ()) -> LuaResult { /// Load and run a Lua file in the given runtime pub fn load_file(lua: &Lua, path: &std::path::Path) -> LuaResult<()> { - let content = std::fs::read_to_string(path) - .map_err(mlua::Error::external)?; + let content = std::fs::read_to_string(path).map_err(mlua::Error::external)?; lua.load(&content) .set_name(path.file_name().and_then(|n| n.to_str()).unwrap_or("chunk")) .into_function()? diff --git a/crates/owlry-plugin-api/Cargo.toml b/crates/owlry-plugin-api/Cargo.toml index 4fdddc9..c9ad3b1 100644 --- a/crates/owlry-plugin-api/Cargo.toml +++ b/crates/owlry-plugin-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owlry-plugin-api" -version = "0.4.10" +version = "1.0.0" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/owlry-plugin-api/src/lib.rs b/crates/owlry-plugin-api/src/lib.rs index 388a7db..01b8930 100644 --- a/crates/owlry-plugin-api/src/lib.rs +++ b/crates/owlry-plugin-api/src/lib.rs @@ -284,12 +284,8 @@ pub enum NotifyUrgency { pub struct HostAPI { /// Send a notification to the user /// Parameters: summary, body, icon (optional, empty string for none), urgency - pub notify: extern "C" fn( - summary: RStr<'_>, - body: RStr<'_>, - icon: RStr<'_>, - urgency: NotifyUrgency, - ), + pub notify: + extern "C" fn(summary: RStr<'_>, body: RStr<'_>, icon: RStr<'_>, urgency: NotifyUrgency), /// Log a message at info level pub log_info: extern "C" fn(message: RStr<'_>), diff --git a/crates/owlry-rune/Cargo.toml b/crates/owlry-rune/Cargo.toml index 713183c..196d295 100644 --- a/crates/owlry-rune/Cargo.toml +++ b/crates/owlry-rune/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owlry-rune" -version = "0.4.10" +version = "1.0.0" edition = "2024" rust-version = "1.90" description = "Rune scripting runtime for owlry plugins" diff --git a/crates/owlry-rune/src/lib.rs b/crates/owlry-rune/src/lib.rs index 4476840..0a0f877 100644 --- a/crates/owlry-rune/src/lib.rs +++ b/crates/owlry-rune/src/lib.rs @@ -75,7 +75,11 @@ pub struct RuneRuntimeVTable { pub init: extern "C" fn(plugins_dir: RStr<'_>) -> RuntimeHandle, pub providers: extern "C" fn(handle: RuntimeHandle) -> RVec, pub refresh: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>) -> RVec, - pub query: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec, + pub query: extern "C" fn( + handle: RuntimeHandle, + provider_id: RStr<'_>, + query: RStr<'_>, + ) -> RVec, pub drop: extern "C" fn(handle: RuntimeHandle), } @@ -94,7 +98,10 @@ extern "C" fn runtime_init(plugins_dir: RStr<'_>) -> RuntimeHandle { let _ = env_logger::try_init(); let plugins_dir = PathBuf::from(plugins_dir.as_str()); - log::info!("Initializing Rune runtime with plugins from: {}", plugins_dir.display()); + log::info!( + "Initializing Rune runtime with plugins from: {}", + plugins_dir.display() + ); let mut state = RuntimeState { plugins: HashMap::new(), @@ -113,15 +120,20 @@ extern "C" fn runtime_init(plugins_dir: RStr<'_>) -> RuntimeHandle { type_id: RString::from(reg.type_id.as_str()), default_icon: RString::from(reg.default_icon.as_str()), is_static: reg.is_static, - prefix: reg.prefix.as_ref() + prefix: reg + .prefix + .as_ref() .map(|p| RString::from(p.as_str())) .into(), }); } state.plugins.insert(id, plugin); } - log::info!("Loaded {} Rune plugin(s) with {} provider(s)", - state.plugins.len(), state.providers.len()); + log::info!( + "Loaded {} Rune plugin(s) with {} provider(s)", + state.plugins.len(), + state.providers.len() + ); } Err(e) => { log::error!("Failed to discover Rune plugins: {}", e); diff --git a/crates/owlry-rune/src/loader.rs b/crates/owlry-rune/src/loader.rs index 9c0a869..9334385 100644 --- a/crates/owlry-rune/src/loader.rs +++ b/crates/owlry-rune/src/loader.rs @@ -8,7 +8,7 @@ use rune::{Context, Unit}; use crate::api::{self, ProviderRegistration}; use crate::manifest::PluginManifest; -use crate::runtime::{compile_source, create_context, create_vm, SandboxConfig}; +use crate::runtime::{SandboxConfig, compile_source, create_context, create_vm}; use owlry_plugin_api::PluginItem; @@ -29,8 +29,8 @@ impl LoadedPlugin { /// Create and initialize a new plugin pub fn new(manifest: PluginManifest, path: PathBuf) -> Result { let sandbox = SandboxConfig::from_permissions(&manifest.permissions); - let context = create_context(&sandbox) - .map_err(|e| format!("Failed to create context: {}", e))?; + let context = + create_context(&sandbox).map_err(|e| format!("Failed to create context: {}", e))?; let entry_path = path.join(&manifest.plugin.entry); if !entry_path.exists() { @@ -45,15 +45,14 @@ impl LoadedPlugin { .map_err(|e| format!("Failed to compile: {}", e))?; // Run the entry point to register providers - let mut vm = create_vm(&context, unit.clone()) - .map_err(|e| format!("Failed to create VM: {}", e))?; + let mut vm = + create_vm(&context, unit.clone()).map_err(|e| format!("Failed to create VM: {}", e))?; // Execute the main function if it exists match vm.call(rune::Hash::type_hash(["main"]), ()) { Ok(result) => { // Try to complete the execution - let _: () = rune::from_value(result) - .unwrap_or(()); + let _: () = rune::from_value(result).unwrap_or(()); } Err(_) => { // No main function is okay @@ -111,7 +110,10 @@ pub fn discover_rune_plugins(plugins_dir: &Path) -> Result Result m, Err(e) => { - log::warn!("Failed to load manifest at {}: {}", manifest_path.display(), e); + log::warn!( + "Failed to load manifest at {}: {}", + manifest_path.display(), + e + ); continue; } }; diff --git a/crates/owlry-rune/src/manifest.rs b/crates/owlry-rune/src/manifest.rs index 7c7946b..4dfab81 100644 --- a/crates/owlry-rune/src/manifest.rs +++ b/crates/owlry-rune/src/manifest.rs @@ -64,10 +64,10 @@ pub struct PluginPermissions { impl PluginManifest { /// Load manifest from a plugin.toml file pub fn load(path: &Path) -> Result { - let content = std::fs::read_to_string(path) - .map_err(|e| format!("Failed to read manifest: {}", e))?; - let manifest: PluginManifest = toml::from_str(&content) - .map_err(|e| format!("Failed to parse manifest: {}", e))?; + let content = + std::fs::read_to_string(path).map_err(|e| format!("Failed to read manifest: {}", e))?; + let manifest: PluginManifest = + toml::from_str(&content).map_err(|e| format!("Failed to parse manifest: {}", e))?; manifest.validate()?; Ok(manifest) } @@ -78,7 +78,12 @@ impl PluginManifest { return Err("Plugin ID cannot be empty".to_string()); } - if !self.plugin.id.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') { + if !self + .plugin + .id + .chars() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') + { return Err("Plugin ID must be lowercase alphanumeric with hyphens".to_string()); } diff --git a/crates/owlry-rune/src/runtime.rs b/crates/owlry-rune/src/runtime.rs index 7b60310..8c7e9f0 100644 --- a/crates/owlry-rune/src/runtime.rs +++ b/crates/owlry-rune/src/runtime.rs @@ -25,7 +25,6 @@ pub struct SandboxConfig { pub allowed_commands: Vec, } - impl SandboxConfig { /// Create sandbox config from plugin permissions pub fn from_permissions(permissions: &PluginPermissions) -> Self { @@ -59,12 +58,9 @@ pub fn create_context(sandbox: &SandboxConfig) -> Result Result, CompileError> { - let source_content = std::fs::read_to_string(source_path) - .map_err(|e| CompileError::Io(e.to_string()))?; +pub fn compile_source(context: &Context, source_path: &Path) -> Result, CompileError> { + let source_content = + std::fs::read_to_string(source_path).map_err(|e| CompileError::Io(e.to_string()))?; let source_name = source_path .file_name() @@ -73,7 +69,10 @@ pub fn compile_source( let mut sources = Sources::new(); sources - .insert(Source::new(source_name, &source_content).map_err(|e| CompileError::Compile(e.to_string()))?) + .insert( + Source::new(source_name, &source_content) + .map_err(|e| CompileError::Compile(e.to_string()))?, + ) .map_err(|e| CompileError::Compile(format!("Failed to insert source: {}", e)))?; let mut diagnostics = Diagnostics::new(); @@ -97,13 +96,11 @@ pub fn compile_source( } /// Create a new Rune VM from compiled unit -pub fn create_vm( - context: &Context, - unit: Arc, -) -> Result { +pub fn create_vm(context: &Context, unit: Arc) -> Result { let runtime = Arc::new( - context.runtime() - .map_err(|e| CompileError::Compile(format!("Failed to get runtime: {}", e)))? + context + .runtime() + .map_err(|e| CompileError::Compile(format!("Failed to get runtime: {}", e)))?, ); Ok(Vm::new(runtime, unit)) } diff --git a/crates/owlry/Cargo.toml b/crates/owlry/Cargo.toml index 35b89ca..86e5b65 100644 --- a/crates/owlry/Cargo.toml +++ b/crates/owlry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "owlry" -version = "0.4.10" +version = "1.0.0" edition = "2024" rust-version = "1.90" description = "A lightweight, owl-themed application launcher for Wayland" diff --git a/crates/owlry/src/app.rs b/crates/owlry/src/app.rs index 918140b..9244ade 100644 --- a/crates/owlry/src/app.rs +++ b/crates/owlry/src/app.rs @@ -4,15 +4,15 @@ use crate::client::CoreClient; use crate::providers::DmenuProvider; use crate::theme; use crate::ui::MainWindow; +use gtk4::prelude::*; +use gtk4::{Application, CssProvider, gio}; +use gtk4_layer_shell::{Edge, Layer, LayerShell}; +use log::{debug, info, warn}; use owlry_core::config::Config; use owlry_core::data::FrecencyStore; use owlry_core::filter::ProviderFilter; use owlry_core::paths; use owlry_core::providers::{Provider, ProviderManager, ProviderType}; -use gtk4::prelude::*; -use gtk4::{gio, Application, CssProvider}; -use gtk4_layer_shell::{Edge, Layer, LayerShell}; -use log::{debug, info, warn}; use std::cell::RefCell; use std::rc::Rc; @@ -61,7 +61,7 @@ impl OwlryApp { let frecency = FrecencyStore::load_or_default(); SearchBackend::Local { - providers: provider_manager, + providers: Box::new(provider_manager), frecency, } } else { @@ -98,11 +98,7 @@ impl OwlryApp { &config.borrow().providers, ) } else { - ProviderFilter::new( - None, - Some(provider_types), - &config.borrow().providers, - ) + ProviderFilter::new(None, Some(provider_types), &config.borrow().providers) } } else { ProviderFilter::new(None, None, &config.borrow().providers) @@ -180,7 +176,7 @@ impl OwlryApp { let frecency = FrecencyStore::load_or_default(); SearchBackend::Local { - providers: provider_manager, + providers: Box::new(provider_manager), frecency, } } @@ -241,16 +237,17 @@ impl OwlryApp { // 3. Load user's custom stylesheet if exists if let Some(custom_path) = paths::custom_style_file() - && custom_path.exists() { - let custom_provider = CssProvider::new(); - custom_provider.load_from_path(&custom_path); - gtk4::style_context_add_provider_for_display( - &display, - &custom_provider, - gtk4::STYLE_PROVIDER_PRIORITY_USER, - ); - debug!("Loaded custom CSS from {:?}", custom_path); - } + && custom_path.exists() + { + let custom_provider = CssProvider::new(); + custom_provider.load_from_path(&custom_path); + gtk4::style_context_add_provider_for_display( + &display, + &custom_provider, + gtk4::STYLE_PROVIDER_PRIORITY_USER, + ); + debug!("Loaded custom CSS from {:?}", custom_path); + } // 4. Inject config variables (highest priority for overrides) let vars_css = theme::generate_variables_css(&config.appearance); diff --git a/crates/owlry/src/backend.rs b/crates/owlry/src/backend.rs index 62faf65..254f163 100644 --- a/crates/owlry/src/backend.rs +++ b/crates/owlry/src/backend.rs @@ -4,12 +4,12 @@ //! In dmenu mode, the UI uses a local ProviderManager directly (no daemon). use crate::client::CoreClient; +use log::warn; +use owlry_core::config::Config; +use owlry_core::data::FrecencyStore; use owlry_core::filter::ProviderFilter; use owlry_core::ipc::ResultItem; use owlry_core::providers::{LaunchItem, ProviderManager, ProviderType}; -use owlry_core::data::FrecencyStore; -use owlry_core::config::Config; -use log::warn; /// Backend for search operations. Wraps either an IPC client (daemon mode) /// or a local ProviderManager (dmenu mode). @@ -18,7 +18,7 @@ pub enum SearchBackend { Daemon(CoreClient), /// Direct local provider manager (dmenu mode only) Local { - providers: ProviderManager, + providers: Box, frecency: FrecencyStore, }, } @@ -64,7 +64,14 @@ impl SearchBackend { if use_frecency { providers - .search_with_frecency(query, max_results, filter, frecency, frecency_weight, None) + .search_with_frecency( + query, + max_results, + filter, + frecency, + frecency_weight, + None, + ) .into_iter() .map(|(item, _)| item) .collect() @@ -123,7 +130,14 @@ impl SearchBackend { if use_frecency { providers - .search_with_frecency(query, max_results, filter, frecency, frecency_weight, tag_filter) + .search_with_frecency( + query, + max_results, + filter, + frecency, + frecency_weight, + tag_filter, + ) .into_iter() .map(|(item, _)| item) .collect() @@ -141,18 +155,14 @@ impl SearchBackend { /// Execute a plugin action command. Returns true if handled. pub fn execute_plugin_action(&mut self, command: &str) -> bool { match self { - SearchBackend::Daemon(client) => { - match client.plugin_action(command) { - Ok(handled) => handled, - Err(e) => { - warn!("IPC plugin_action failed: {}", e); - false - } + SearchBackend::Daemon(client) => match client.plugin_action(command) { + Ok(handled) => handled, + Err(e) => { + warn!("IPC plugin_action failed: {}", e); + false } - } - SearchBackend::Local { providers, .. } => { - providers.execute_plugin_action(command) - } + }, + SearchBackend::Local { providers, .. } => providers.execute_plugin_action(command), } } @@ -165,20 +175,18 @@ impl SearchBackend { display_name: &str, ) -> Option<(String, Vec)> { match self { - SearchBackend::Daemon(client) => { - match client.submenu(plugin_id, data) { - Ok(items) if !items.is_empty() => { - let actions: Vec = - items.into_iter().map(result_to_launch_item).collect(); - Some((display_name.to_string(), actions)) - } - Ok(_) => None, - Err(e) => { - warn!("IPC submenu query failed: {}", e); - None - } + SearchBackend::Daemon(client) => match client.submenu(plugin_id, data) { + Ok(items) if !items.is_empty() => { + let actions: Vec = + items.into_iter().map(result_to_launch_item).collect(); + Some((display_name.to_string(), actions)) } - } + Ok(_) => None, + Err(e) => { + warn!("IPC submenu query failed: {}", e); + None + } + }, SearchBackend::Local { providers, .. } => { providers.query_submenu_actions(plugin_id, data, display_name) } @@ -218,22 +226,18 @@ impl SearchBackend { #[allow(dead_code)] pub fn available_provider_ids(&mut self) -> Vec { match self { - SearchBackend::Daemon(client) => { - match client.providers() { - Ok(descs) => descs.into_iter().map(|d| d.id).collect(), - Err(e) => { - warn!("IPC providers query failed: {}", e); - Vec::new() - } + SearchBackend::Daemon(client) => match client.providers() { + Ok(descs) => descs.into_iter().map(|d| d.id).collect(), + Err(e) => { + warn!("IPC providers query failed: {}", e); + Vec::new() } - } - SearchBackend::Local { providers, .. } => { - providers - .available_providers() - .into_iter() - .map(|d| d.id) - .collect() - } + }, + SearchBackend::Local { providers, .. } => providers + .available_providers() + .into_iter() + .map(|d| d.id) + .collect(), } } } diff --git a/crates/owlry/src/client.rs b/crates/owlry/src/client.rs index 836c60a..0f05213 100644 --- a/crates/owlry/src/client.rs +++ b/crates/owlry/src/client.rs @@ -41,20 +41,14 @@ impl CoreClient { .args(["--user", "start", "owlry-core"]) .status() .map_err(|e| { - io::Error::new( - io::ErrorKind::Other, - format!("failed to start owlry-core via systemd: {e}"), - ) + io::Error::other(format!("failed to start owlry-core via systemd: {e}")) })?; if !status.success() { - return Err(io::Error::new( - io::ErrorKind::Other, - format!( - "systemctl --user start owlry-core exited with status {}", - status - ), - )); + return Err(io::Error::other(format!( + "systemctl --user start owlry-core exited with status {}", + status + ))); } // Retry with exponential backoff. @@ -66,9 +60,7 @@ impl CoreClient { Err(e) if i == delays.len() - 1 => { return Err(io::Error::new( io::ErrorKind::ConnectionRefused, - format!( - "daemon started but socket not available after retries: {e}" - ), + format!("daemon started but socket not available after retries: {e}"), )); } Err(_) => continue, @@ -87,11 +79,7 @@ impl CoreClient { } /// Send a search query and return matching results. - pub fn query( - &mut self, - text: &str, - modes: Option>, - ) -> io::Result> { + pub fn query(&mut self, text: &str, modes: Option>) -> io::Result> { self.send(&Request::Query { text: text.to_string(), modes, @@ -99,9 +87,7 @@ impl CoreClient { match self.receive()? { Response::Results { items } => Ok(items), - Response::Error { message } => { - Err(io::Error::new(io::ErrorKind::Other, message)) - } + Response::Error { message } => Err(io::Error::other(message)), other => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected response to Query: {other:?}"), @@ -118,9 +104,7 @@ impl CoreClient { match self.receive()? { Response::Ack => Ok(()), - Response::Error { message } => { - Err(io::Error::new(io::ErrorKind::Other, message)) - } + Response::Error { message } => Err(io::Error::other(message)), other => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected response to Launch: {other:?}"), @@ -134,9 +118,7 @@ impl CoreClient { match self.receive()? { Response::Providers { list } => Ok(list), - Response::Error { message } => { - Err(io::Error::new(io::ErrorKind::Other, message)) - } + Response::Error { message } => Err(io::Error::other(message)), other => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected response to Providers: {other:?}"), @@ -150,9 +132,7 @@ impl CoreClient { match self.receive()? { Response::Ack => Ok(()), - Response::Error { message } => { - Err(io::Error::new(io::ErrorKind::Other, message)) - } + Response::Error { message } => Err(io::Error::other(message)), other => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected response to Toggle: {other:?}"), @@ -178,11 +158,7 @@ impl CoreClient { } /// Query a plugin's submenu actions. - pub fn submenu( - &mut self, - plugin_id: &str, - data: &str, - ) -> io::Result> { + pub fn submenu(&mut self, plugin_id: &str, data: &str) -> io::Result> { self.send(&Request::Submenu { plugin_id: plugin_id.to_string(), data: data.to_string(), @@ -190,9 +166,7 @@ impl CoreClient { match self.receive()? { Response::SubmenuItems { items } => Ok(items), - Response::Error { message } => { - Err(io::Error::new(io::ErrorKind::Other, message)) - } + Response::Error { message } => Err(io::Error::other(message)), other => Err(io::Error::new( io::ErrorKind::InvalidData, format!("unexpected response to Submenu: {other:?}"), @@ -220,8 +194,7 @@ impl CoreClient { "daemon closed the connection", )); } - serde_json::from_str(line.trim()) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + serde_json::from_str(line.trim()).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } } @@ -239,11 +212,7 @@ mod tests { /// socket path to avoid collisions when tests run in parallel. fn mock_server(response: Response) -> PathBuf { let n = COUNTER.fetch_add(1, Ordering::Relaxed); - let dir = std::env::temp_dir().join(format!( - "owlry-test-{}-{}", - std::process::id(), - n - )); + let dir = std::env::temp_dir().join(format!("owlry-test-{}-{}", std::process::id(), n)); let _ = std::fs::create_dir_all(&dir); let sock = dir.join("test.sock"); let _ = std::fs::remove_file(&sock); diff --git a/crates/owlry/src/main.rs b/crates/owlry/src/main.rs index 90a37aa..1e54301 100644 --- a/crates/owlry/src/main.rs +++ b/crates/owlry/src/main.rs @@ -1,7 +1,7 @@ mod app; mod backend; -pub mod client; mod cli; +pub mod client; mod plugin_commands; mod providers; mod theme; @@ -65,7 +65,11 @@ fn main() { } // No subcommand - launch the app - let default_level = if cfg!(feature = "dev-logging") { "debug" } else { "info" }; + let default_level = if cfg!(feature = "dev-logging") { + "debug" + } else { + "info" + }; env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default_level)) .format_timestamp_millis() diff --git a/crates/owlry/src/plugin_commands.rs b/crates/owlry/src/plugin_commands.rs index 731f47c..7780002 100644 --- a/crates/owlry/src/plugin_commands.rs +++ b/crates/owlry/src/plugin_commands.rs @@ -9,7 +9,7 @@ use std::path::{Path, PathBuf}; use crate::cli::{PluginCommand as CliPluginCommand, PluginRuntime}; use owlry_core::config::Config; use owlry_core::paths; -use owlry_core::plugins::manifest::{discover_plugins, PluginManifest}; +use owlry_core::plugins::manifest::{PluginManifest, discover_plugins}; use owlry_core::plugins::registry::{self, RegistryClient}; use owlry_core::plugins::runtime_loader::{lua_runtime_available, rune_runtime_available}; @@ -46,15 +46,30 @@ fn any_runtime_available() -> bool { /// Execute a plugin command pub fn execute(cmd: CliPluginCommand) -> CommandResult { match cmd { - CliPluginCommand::List { enabled, disabled, runtime, available, refresh, json } => { + CliPluginCommand::List { + enabled, + disabled, + runtime, + available, + refresh, + json, + } => { if available { cmd_list_available(refresh, json) } else { cmd_list_installed(enabled, disabled, runtime, json) } } - CliPluginCommand::Search { query, refresh, json } => cmd_search(&query, refresh, json), - CliPluginCommand::Info { name, registry, json } => { + CliPluginCommand::Search { + query, + refresh, + json, + } => cmd_search(&query, refresh, json), + CliPluginCommand::Info { + name, + registry, + json, + } => { if registry { cmd_info_registry(&name, json) } else { @@ -74,15 +89,29 @@ pub fn execute(cmd: CliPluginCommand) -> CommandResult { CliPluginCommand::Update { name } => cmd_update(name.as_deref()), CliPluginCommand::Enable { name } => cmd_enable(&name), CliPluginCommand::Disable { name } => cmd_disable(&name), - CliPluginCommand::Create { name, runtime, dir, display_name, description } => { + CliPluginCommand::Create { + name, + runtime, + dir, + display_name, + description, + } => { check_runtime_available(runtime)?; - cmd_create(&name, runtime, dir.as_deref(), display_name.as_deref(), description.as_deref()) + cmd_create( + &name, + runtime, + dir.as_deref(), + display_name.as_deref(), + description.as_deref(), + ) } CliPluginCommand::Validate { path } => cmd_validate(path.as_deref()), CliPluginCommand::Runtimes => cmd_runtimes(), - CliPluginCommand::Run { plugin_id, command, args } => { - cmd_run_plugin_command(&plugin_id, &command, &args) - } + CliPluginCommand::Run { + plugin_id, + command, + args, + } => cmd_run_plugin_command(&plugin_id, &command, &args), CliPluginCommand::Commands { plugin_id } => cmd_list_commands(plugin_id.as_deref()), } } @@ -351,7 +380,10 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult { }); println!("{}", serde_json::to_string_pretty(&info).unwrap()); } else { - println!("Plugin: {} v{}", manifest.plugin.name, manifest.plugin.version); + println!( + "Plugin: {} v{}", + manifest.plugin.name, manifest.plugin.version + ); println!("ID: {}", manifest.plugin.id); if !manifest.plugin.description.is_empty() { println!("Description: {}", manifest.plugin.description); @@ -359,11 +391,18 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult { if !manifest.plugin.author.is_empty() { println!("Author: {}", manifest.plugin.author); } - println!("Status: {}", if is_enabled { "enabled" } else { "disabled" }); + println!( + "Status: {}", + if is_enabled { "enabled" } else { "disabled" } + ); println!( "Runtime: {}{}", runtime, - if runtime_available { "" } else { " (NOT INSTALLED)" } + if runtime_available { + "" + } else { + " (NOT INSTALLED)" + } ); println!("Path: {}", plugin_path.display()); println!(); @@ -382,12 +421,25 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult { } println!(); println!("Permissions:"); - println!(" Network: {}", if manifest.permissions.network { "yes" } else { "no" }); + println!( + " Network: {}", + if manifest.permissions.network { + "yes" + } else { + "no" + } + ); if !manifest.permissions.filesystem.is_empty() { - println!(" Filesystem: {}", manifest.permissions.filesystem.join(", ")); + println!( + " Filesystem: {}", + manifest.permissions.filesystem.join(", ") + ); } if !manifest.permissions.run_commands.is_empty() { - println!(" Commands: {}", manifest.permissions.run_commands.join(", ")); + println!( + " Commands: {}", + manifest.permissions.run_commands.join(", ") + ); } } @@ -398,7 +450,8 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult { fn cmd_info_registry(name: &str, json_output: bool) -> CommandResult { let client = get_registry_client(); - let plugin = client.find(name, false)? + let plugin = client + .find(name, false)? .ok_or_else(|| format!("Plugin '{}' not found in registry", name))?; if json_output { @@ -466,12 +519,10 @@ fn cmd_install(source: &str, force: bool) -> CommandResult { println!("Found: {} v{}", plugin.name, plugin.version); install_from_git(&plugin.repository, &plugins_dir, force) } - None => { - Err(format!( - "Plugin '{}' not found in registry. Use a local path or git URL.", - source - )) - } + None => Err(format!( + "Plugin '{}' not found in registry. Use a local path or git URL.", + source + )), } } } @@ -597,8 +648,7 @@ fn cmd_remove(name: &str, yes: bool) -> CommandResult { } } - fs::remove_dir_all(&plugin_path) - .map_err(|e| format!("Failed to remove plugin: {}", e))?; + fs::remove_dir_all(&plugin_path).map_err(|e| format!("Failed to remove plugin: {}", e))?; // Also remove from disabled list if present if let Ok(mut config) = Config::load() { @@ -645,7 +695,9 @@ fn cmd_enable(name: &str) -> CommandResult { } config.plugins.disabled_plugins.retain(|id| id != name); - config.save().map_err(|e| format!("Failed to save config: {}", e))?; + config + .save() + .map_err(|e| format!("Failed to save config: {}", e))?; println!("Enabled plugin '{}'", name); Ok(()) @@ -668,7 +720,9 @@ fn cmd_disable(name: &str) -> CommandResult { } config.plugins.disabled_plugins.push(name.to_string()); - config.save().map_err(|e| format!("Failed to save config: {}", e))?; + config + .save() + .map_err(|e| format!("Failed to save config: {}", e))?; println!("Disabled plugin '{}'", name); Ok(()) @@ -688,11 +742,13 @@ fn cmd_create( let plugin_dir = base_dir.join(name); if plugin_dir.exists() { - return Err(format!("Directory '{}' already exists", plugin_dir.display())); + return Err(format!( + "Directory '{}' already exists", + plugin_dir.display() + )); } - fs::create_dir_all(&plugin_dir) - .map_err(|e| format!("Failed to create directory: {}", e))?; + fs::create_dir_all(&plugin_dir).map_err(|e| format!("Failed to create directory: {}", e))?; let display = display_name.unwrap_or(name); let desc = description.unwrap_or("A custom owlry plugin"); @@ -825,14 +881,28 @@ pub fn register(owlry) {{{{ } } - println!("Created {} plugin '{}' at {}", runtime, name, plugin_dir.display()); + println!( + "Created {} plugin '{}' at {}", + runtime, + name, + plugin_dir.display() + ); println!(); println!("Next steps:"); - println!(" 1. Edit {}/{} to implement your provider", name, entry_file); - println!(" 2. Install: owlry plugin install {}", plugin_dir.display()); + println!( + " 1. Edit {}/{} to implement your provider", + name, entry_file + ); + println!( + " 2. Install: owlry plugin install {}", + plugin_dir.display() + ); println!(" 3. Test: owlry (your plugin items should appear)"); println!(); - println!("Runtime: {} (requires owlry-{} package)", runtime, entry_ext); + println!( + "Runtime: {} (requires owlry-{} package)", + runtime, entry_ext + ); Ok(()) } @@ -996,15 +1066,29 @@ fn cmd_run_plugin_command(plugin_id: &str, command: &str, args: &[String]) -> Co .map_err(|e| format!("Failed to parse manifest: {}", e))?; // Check if plugin provides this command - let cmd_info = manifest.provides.commands.iter().find(|c| c.name == command); + let cmd_info = manifest + .provides + .commands + .iter() + .find(|c| c.name == command); if cmd_info.is_none() { - let available: Vec<_> = manifest.provides.commands.iter().map(|c| c.name.as_str()).collect(); + let available: Vec<_> = manifest + .provides + .commands + .iter() + .map(|c| c.name.as_str()) + .collect(); if available.is_empty() { - return Err(format!("Plugin '{}' does not provide any CLI commands", plugin_id)); + return Err(format!( + "Plugin '{}' does not provide any CLI commands", + plugin_id + )); } return Err(format!( "Plugin '{}' does not have command '{}'. Available: {}", - plugin_id, command, available.join(", ") + plugin_id, + command, + available.join(", ") )); } @@ -1030,10 +1114,8 @@ fn execute_plugin_command( // Load the appropriate runtime let loaded_runtime = match runtime { - PluginRuntime::Lua => { - LoadedRuntime::load_lua(plugin_path.parent().unwrap_or(plugin_path)) - .map_err(|e| format!("Failed to load Lua runtime: {}", e))? - } + PluginRuntime::Lua => LoadedRuntime::load_lua(plugin_path.parent().unwrap_or(plugin_path)) + .map_err(|e| format!("Failed to load Lua runtime: {}", e))?, PluginRuntime::Rune => { LoadedRuntime::load_rune(plugin_path.parent().unwrap_or(plugin_path)) .map_err(|e| format!("Failed to load Rune runtime: {}", e))? @@ -1047,7 +1129,10 @@ fn execute_plugin_command( let _query = query_parts.join(":"); // Find the provider from this plugin and send the command query - let _provider_name = manifest.provides.providers.first() + let _provider_name = manifest + .provides + .providers + .first() .ok_or_else(|| format!("Plugin '{}' has no providers", manifest.plugin.id))?; // Query the provider with the command @@ -1056,14 +1141,31 @@ fn execute_plugin_command( // For now, we use a simpler approach: invoke the entry point with command args // This requires runtime support for command execution - println!("Executing: owlry plugin run {} {} {}", manifest.plugin.id, command, args.join(" ")); + println!( + "Executing: owlry plugin run {} {} {}", + manifest.plugin.id, + command, + args.join(" ") + ); println!(); println!("Note: Plugin command execution requires runtime support."); println!("The plugin entry point should handle CLI commands via owlry.command.register()"); println!(); - println!("Runtime: {} ({})", runtime, if PathBuf::from(SYSTEM_RUNTIMES_DIR).join( - match runtime { PluginRuntime::Lua => "liblua.so", PluginRuntime::Rune => "librune.so" } - ).exists() { "available" } else { "NOT INSTALLED" }); + println!( + "Runtime: {} ({})", + runtime, + if PathBuf::from(SYSTEM_RUNTIMES_DIR) + .join(match runtime { + PluginRuntime::Lua => "liblua.so", + PluginRuntime::Rune => "librune.so", + }) + .exists() + { + "available" + } else { + "NOT INSTALLED" + } + ); // TODO: Implement actual command execution through runtime // This would involve: @@ -1087,7 +1189,8 @@ fn cmd_list_commands(plugin_id: Option<&str>) -> CommandResult { if let Some(id) = plugin_id { // Show commands for a specific plugin - let (manifest, _path) = discovered.get(id) + let (manifest, _path) = discovered + .get(id) .ok_or_else(|| format!("Plugin '{}' not found", id))?; if manifest.provides.commands.is_empty() { diff --git a/crates/owlry/src/providers/dmenu.rs b/crates/owlry/src/providers/dmenu.rs index 1a6b8b3..99a9391 100644 --- a/crates/owlry/src/providers/dmenu.rs +++ b/crates/owlry/src/providers/dmenu.rs @@ -1,5 +1,5 @@ -use owlry_core::providers::{LaunchItem, Provider, ProviderType}; use log::debug; +use owlry_core::providers::{LaunchItem, Provider, ProviderType}; use std::io::{self, BufRead}; /// Provider for dmenu-style input from stdin diff --git a/crates/owlry/src/theme.rs b/crates/owlry/src/theme.rs index fd3b01b..f0b11e8 100644 --- a/crates/owlry/src/theme.rs +++ b/crates/owlry/src/theme.rs @@ -6,7 +6,10 @@ pub fn generate_variables_css(config: &AppearanceConfig) -> String { // Always inject layout config values css.push_str(&format!(" --owlry-font-size: {}px;\n", config.font_size)); - css.push_str(&format!(" --owlry-border-radius: {}px;\n", config.border_radius)); + css.push_str(&format!( + " --owlry-border-radius: {}px;\n", + config.border_radius + )); // Only inject colors if user specified them if let Some(ref bg) = config.colors.background { @@ -22,7 +25,10 @@ pub fn generate_variables_css(config: &AppearanceConfig) -> String { css.push_str(&format!(" --owlry-text: {};\n", text)); } if let Some(ref text_secondary) = config.colors.text_secondary { - css.push_str(&format!(" --owlry-text-secondary: {};\n", text_secondary)); + css.push_str(&format!( + " --owlry-text-secondary: {};\n", + text_secondary + )); } if let Some(ref accent) = config.colors.accent { css.push_str(&format!(" --owlry-accent: {};\n", accent)); @@ -36,7 +42,10 @@ pub fn generate_variables_css(config: &AppearanceConfig) -> String { css.push_str(&format!(" --owlry-badge-app: {};\n", badge_app)); } if let Some(ref badge_bookmark) = config.colors.badge_bookmark { - css.push_str(&format!(" --owlry-badge-bookmark: {};\n", badge_bookmark)); + css.push_str(&format!( + " --owlry-badge-bookmark: {};\n", + badge_bookmark + )); } if let Some(ref badge_calc) = config.colors.badge_calc { css.push_str(&format!(" --owlry-badge-calc: {};\n", badge_calc)); diff --git a/crates/owlry/src/ui/main_window.rs b/crates/owlry/src/ui/main_window.rs index fb4c714..85126b4 100644 --- a/crates/owlry/src/ui/main_window.rs +++ b/crates/owlry/src/ui/main_window.rs @@ -1,9 +1,6 @@ use crate::backend::SearchBackend; -use owlry_core::config::Config; -use owlry_core::filter::ProviderFilter; -use owlry_core::providers::{LaunchItem, ProviderType}; -use crate::ui::submenu; use crate::ui::ResultRow; +use crate::ui::submenu; use gtk4::gdk::Key; use gtk4::prelude::*; use gtk4::{ @@ -11,6 +8,9 @@ use gtk4::{ ListBoxRow, Orientation, ScrolledWindow, SelectionMode, ToggleButton, }; use log::info; +use owlry_core::config::Config; +use owlry_core::filter::ProviderFilter; +use owlry_core::providers::{LaunchItem, ProviderType}; #[cfg(feature = "dev-logging")] use log::debug; @@ -148,7 +148,9 @@ impl MainWindow { header_box.append(&filter_tabs); // Search entry with dynamic placeholder (or custom prompt if provided) - let placeholder = custom_prompt.clone().unwrap_or_else(|| Self::build_placeholder(&filter.borrow())); + let placeholder = custom_prompt + .clone() + .unwrap_or_else(|| Self::build_placeholder(&filter.borrow())); let search_entry = Entry::builder() .placeholder_text(&placeholder) .hexpand(true) @@ -293,8 +295,16 @@ impl MainWindow { // Show number hint in the label for first 9 tabs (using superscript) let label = if idx < 9 { let superscript = match idx + 1 { - 1 => "¹", 2 => "²", 3 => "³", 4 => "⁴", 5 => "⁵", - 6 => "⁶", 7 => "⁷", 8 => "⁸", 9 => "⁹", _ => "", + 1 => "¹", + 2 => "²", + 3 => "³", + 4 => "⁴", + 5 => "⁵", + 6 => "⁶", + 7 => "⁷", + 8 => "⁸", + 9 => "⁹", + _ => "", }; format!("{}{}", base_label, superscript) } else { @@ -494,7 +504,11 @@ impl MainWindow { actions: Vec, ) { #[cfg(feature = "dev-logging")] - debug!("[UI] Entering submenu: {} ({} actions)", display_name, actions.len()); + debug!( + "[UI] Entering submenu: {} ({} actions)", + display_name, + actions.len() + ); // Save current state { @@ -705,7 +719,8 @@ impl MainWindow { } // current_results holds only what's displayed (for selection/activation) - *current_results.borrow_mut() = results.into_iter().take(initial_count).collect(); + *current_results.borrow_mut() = + results.into_iter().take(initial_count).collect(); }, ); @@ -736,15 +751,19 @@ impl MainWindow { if let Some(item) = results.get(index) { // Check if this is a submenu item and query the plugin for actions let submenu_result = if submenu::is_submenu_item(item) { - if let Some((plugin_id, data)) = submenu::parse_submenu_command(&item.command) { + if let Some((plugin_id, data)) = + submenu::parse_submenu_command(&item.command) + { // Clone values before dropping borrow let plugin_id = plugin_id.to_string(); let data = data.to_string(); let display_name = item.name.clone(); drop(results); // Release borrow before querying - backend_for_activate - .borrow_mut() - .query_submenu_actions(&plugin_id, &data, &display_name) + backend_for_activate.borrow_mut().query_submenu_actions( + &plugin_id, + &data, + &display_name, + ) } else { drop(results); None @@ -843,7 +862,10 @@ impl MainWindow { let shift = modifiers.contains(gtk4::gdk::ModifierType::SHIFT_MASK); #[cfg(feature = "dev-logging")] - debug!("[UI] Key pressed: {:?} (ctrl={}, shift={})", key, ctrl, shift); + debug!( + "[UI] Key pressed: {:?} (ctrl={}, shift={})", + key, ctrl, shift + ); match key { Key::Escape => { @@ -906,10 +928,11 @@ impl MainWindow { if let Some(selected) = results_list.selected_row() { let prev_index = selected.index() - 1; if prev_index >= 0 - && let Some(prev_row) = results_list.row_at_index(prev_index) { - results_list.select_row(Some(&prev_row)); - Self::scroll_to_row(&scrolled, &results_list, &prev_row); - } + && let Some(prev_row) = results_list.row_at_index(prev_index) + { + results_list.select_row(Some(&prev_row)); + Self::scroll_to_row(&scrolled, &results_list, &prev_row); + } } gtk4::glib::Propagation::Stop } @@ -941,8 +964,17 @@ impl MainWindow { gtk4::glib::Propagation::Stop } // Ctrl+1-9 toggle specific providers based on tab order (only when not in submenu) - Key::_1 | Key::_2 | Key::_3 | Key::_4 | Key::_5 | - Key::_6 | Key::_7 | Key::_8 | Key::_9 if ctrl => { + Key::_1 + | Key::_2 + | Key::_3 + | Key::_4 + | Key::_5 + | Key::_6 + | Key::_7 + | Key::_8 + | Key::_9 + if ctrl => + { info!("[UI] Ctrl+number detected: {:?}", key); if !submenu_state.borrow().active { let idx = match key { @@ -968,7 +1000,11 @@ impl MainWindow { &mode_label, ); } else { - info!("[UI] No provider at index {}, tab_order len={}", idx, tab_order.len()); + info!( + "[UI] No provider at index {}, tab_order len={}", + idx, + tab_order.len() + ); } } gtk4::glib::Propagation::Stop @@ -1029,7 +1065,8 @@ impl MainWindow { let results = current_results.borrow(); if let Some(item) = results.get(index).cloned() { drop(results); - let should_close = Self::handle_item_action(&item, &config.borrow(), &backend); + let should_close = + Self::handle_item_action(&item, &config.borrow(), &backend); if should_close { window.close(); } else { @@ -1076,7 +1113,11 @@ impl MainWindow { } } else if current.len() == 1 { let idx = tab_order.iter().position(|p| p == ¤t[0]).unwrap_or(0); - let at_boundary = if forward { idx == tab_order.len() - 1 } else { idx == 0 }; + let at_boundary = if forward { + idx == tab_order.len() - 1 + } else { + idx == 0 + }; if at_boundary { // At boundary, go back to "All" mode @@ -1284,11 +1325,14 @@ impl MainWindow { info!("Launching: {} ({})", item.name, item.command); #[cfg(feature = "dev-logging")] - debug!("[UI] Launch details: terminal={}, provider={:?}, id={}", item.terminal, item.provider, item.id); + debug!( + "[UI] Launch details: terminal={}, provider={:?}, id={}", + item.terminal, item.provider, item.id + ); // Check if this is a desktop application (has .desktop file as ID) - let is_desktop_app = matches!(item.provider, ProviderType::Application) - && item.id.ends_with(".desktop"); + let is_desktop_app = + matches!(item.provider, ProviderType::Application) && item.id.ends_with(".desktop"); // Desktop files should be launched via proper launchers that implement the // freedesktop Desktop Entry spec (D-Bus activation, field codes, env vars, etc.) @@ -1315,7 +1359,10 @@ impl MainWindow { /// /// Otherwise, uses `gio launch` which is always available (part of glib2/GTK4) /// and handles D-Bus activation, field codes, Terminal flag, etc. - fn launch_desktop_file(desktop_path: &str, config: &Config) -> std::io::Result { + fn launch_desktop_file( + desktop_path: &str, + config: &Config, + ) -> std::io::Result { use std::path::Path; // Check if desktop file exists @@ -1349,16 +1396,22 @@ impl MainWindow { .spawn() } else { info!("Launching via gio: {}", desktop_path); - Command::new("gio") - .args(["launch", desktop_path]) - .spawn() + Command::new("gio").args(["launch", desktop_path]).spawn() } } /// Launch a shell command (for non-desktop items like PATH commands, plugins, etc.) - fn launch_command(command: &str, terminal: bool, config: &Config) -> std::io::Result { + fn launch_command( + command: &str, + terminal: bool, + config: &Config, + ) -> std::io::Result { let cmd = if terminal { - let terminal_cmd = config.general.terminal_command.as_deref().unwrap_or("xterm"); + let terminal_cmd = config + .general + .terminal_command + .as_deref() + .unwrap_or("xterm"); format!("{} -e {}", terminal_cmd, command) } else { command.to_string() diff --git a/crates/owlry/src/ui/result_row.rs b/crates/owlry/src/ui/result_row.rs index 175bba8..f4366b9 100644 --- a/crates/owlry/src/ui/result_row.rs +++ b/crates/owlry/src/ui/result_row.rs @@ -1,6 +1,6 @@ -use owlry_core::providers::LaunchItem; use gtk4::prelude::*; use gtk4::{Box as GtkBox, Image, Label, ListBoxRow, Orientation, Widget}; +use owlry_core::providers::LaunchItem; #[allow(dead_code)] pub struct ResultRow { @@ -81,7 +81,9 @@ impl ResultRow { } else { // Default icon based on provider type (only core types, plugins should provide icons) let default_icon = match &item.provider { - owlry_core::providers::ProviderType::Application => "application-x-executable-symbolic", + owlry_core::providers::ProviderType::Application => { + "application-x-executable-symbolic" + } owlry_core::providers::ProviderType::Command => "utilities-terminal-symbolic", owlry_core::providers::ProviderType::Dmenu => "view-list-symbolic", // Plugins should provide their own icon; fallback to generic addon icon @@ -134,9 +136,7 @@ impl ResultRow { .build(); for tag in item.tags.iter().take(3) { - let tag_label = Label::builder() - .label(tag) - .build(); + let tag_label = Label::builder().label(tag).build(); tag_label.add_css_class("owlry-tag-badge"); tags_box.append(&tag_label); }