From bc3d15258d4bd659fd14cfeb22b1ef1444126764 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 31 Jan 2024 17:35:07 -0800 Subject: [PATCH] Add `dbus introspect` command, for access to introspection data in a more comfortable format than XML --- README.md | 35 ++++++++++++++ src/introspection.rs | 107 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 45 ++++++++++++++++++ 3 files changed, 187 insertions(+) diff --git a/README.md b/README.md index 9d46e63..cfe6b84 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,46 @@ Then add `register ~/.cargo/bin/nu_plugin_dbus` to your `~/.config/nushell/confi dbus call - Call a method and get its response dbus get - Get a D-Bus property dbus get-all - Get all D-Bus property for the given objects + dbus introspect - Introspect a D-Bus object dbus set - Get all D-Bus property for the given objects Flags: -h, --help - Display the help message for this command +## `dbus introspect` + + Introspect a D-Bus object + + Returns information about available nodes, interfaces, methods, signals, and properties on the given object path + + Search terms: dbus + + Usage: + > dbus introspect {flags} + + Flags: + -h, --help - Display the help message for this command + --session - Send to the session message bus (default) + --system - Send to the system message bus + --started - Send to the bus that started this process, if applicable + --bus - Send to the bus server at the given address + --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. + --timeout - How long to wait for a response + --dest (required parameter) - The name of the connection that owns the object + + Parameters: + object : The path to the object to introspect + + Examples: + Look at the MPRIS2 interfaces exposed by Spotify + > dbus introspect --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 | explore + + Get methods exposed by KDE Plasma's on-screen display service + > dbus introspect --dest=org.kde.plasmashell /org/kde/osdService | get interfaces | where name == org.kde.osdService | get 0.methods + + List objects exposed by KWin + > dbus introspect --dest=org.kde.KWin / | get children | select name + ## `dbus call` Call a method and get its response diff --git a/src/introspection.rs b/src/introspection.rs index fe93aa0..e2fa549 100644 --- a/src/introspection.rs +++ b/src/introspection.rs @@ -1,5 +1,12 @@ +use nu_protocol::{Value, record, Span}; use serde::Deserialize; +macro_rules! list_to_value { + ($list:expr, $span:expr) => ( + Value::list($list.iter().map(|i| i.to_value($span)).collect(), $span) + ) +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "kebab-case")] pub struct Node { @@ -41,6 +48,15 @@ impl Node { pub fn get_property_signature(&self, interface: &str, property: &str) -> Option<&str> { Some(&self.get_interface(interface)?.get_property(property)?.r#type) } + + /// Represent the node as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => self.name.as_ref().map(|s| Value::string(s, span)).unwrap_or_default(), + "interfaces" => list_to_value!(self.interfaces, span), + "children" => list_to_value!(self.children, span), + }, span) + } } #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] @@ -70,6 +86,17 @@ impl Interface { pub fn get_property(&self, name: &str) -> Option<&Property> { self.properties.iter().find(|p| p.name == name) } + + /// Represent the interface as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => Value::string(&self.name, span), + "methods" => list_to_value!(self.methods, span), + "signals" => list_to_value!(self.signals, span), + "properties" => list_to_value!(self.properties, span), + "signals" => list_to_value!(self.signals, span), + }, span) + } } #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] @@ -99,6 +126,15 @@ impl Method { .map(|arg| &arg.r#type[..]) .collect() } + + /// Represent the method as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => Value::string(&self.name, span), + "args" => list_to_value!(self.args, span), + "annotations" => list_to_value!(self.annotations, span), + }, span) + } } #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] @@ -124,6 +160,15 @@ impl MethodArg { direction, } } + + /// Represent the method as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), + "type" => Value::string(&self.r#type, span), + "direction" => self.direction.to_value(span), + }, span) + } } #[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq, Eq)] @@ -134,6 +179,16 @@ pub enum Direction { Out, } +impl Direction { + /// Represent the direction as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + match self { + Direction::In => Value::string("in", span), + Direction::Out => Value::string("out", span), + } + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct Signal { @@ -144,6 +199,17 @@ pub struct Signal { pub annotations: Vec, } +impl Signal { + /// Represent the signal as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => Value::string(&self.name, span), + "args" => list_to_value!(self.args, span), + "annotations" => list_to_value!(self.annotations, span), + }, span) + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct SignalArg { @@ -152,6 +218,16 @@ pub struct SignalArg { pub r#type: String, } +impl SignalArg { + /// Represent the argument as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), + "type" => Value::string(&self.r#type, span), + }, span) + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct Property { @@ -162,6 +238,18 @@ pub struct Property { pub annotations: Vec, } +impl Property { + /// Represent the property as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => Value::string(&self.name, span), + "type" => Value::string(&self.r#type, span), + "args" => self.access.to_value(span), + "annotations" => list_to_value!(self.annotations, span), + }, span) + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum Access { @@ -170,6 +258,17 @@ pub enum Access { ReadWrite, } +impl Access { + /// Represent the access as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + match self { + Access::Read => Value::string("read", span), + Access::Write => Value::string("write", span), + Access::ReadWrite => Value::string("readwrite", span), + } + } +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct Annotation { @@ -182,6 +281,14 @@ impl Annotation { pub fn new(name: impl Into, value: impl Into) -> Annotation { Annotation { name: name.into(), value: value.into() } } + + /// Represent the annotation as a nushell [Value] + pub fn to_value(&self, span: Span) -> Value { + Value::record(record!{ + "name" => Value::string(&self.name, span), + "value" => Value::string(&self.value, span), + }, span) + } } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index 99ff895..af7df06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,40 @@ impl Plugin for NuPluginDbus { PluginSignature::build("dbus") .is_dbus_command() .usage("Commands for interacting with D-Bus"), + PluginSignature::build("dbus introspect") + .is_dbus_command() + .accepts_dbus_client_options() + .usage("Introspect a D-Bus object") + .extra_usage("Returns information about available nodes, interfaces, methods, \ + signals, and properties on the given object path") + .named("timeout", SyntaxShape::Duration, "How long to wait for a response", None) + .required_named("dest", SyntaxShape::String, + "The name of the connection that owns the object", + None) + .required("object", SyntaxShape::String, + "The path to the object to introspect") + .plugin_examples(vec![ + PluginExample { + example: "dbus introspect --dest=org.mpris.MediaPlayer2.spotify \ + /org/mpris/MediaPlayer2 | explore".into(), + description: "Look at the MPRIS2 interfaces exposed by Spotify".into(), + result: None, + }, + PluginExample { + example: "dbus introspect --dest=org.kde.plasmashell \ + /org/kde/osdService | get interfaces | \ + where name == org.kde.osdService | get 0.methods".into(), + description: "Get methods exposed by KDE Plasma's on-screen display \ + service".into(), + result: None, + }, + PluginExample { + example: "dbus introspect --dest=org.kde.KWin / | get children | \ + select name".into(), + description: "List objects exposed by KWin".into(), + result: None, + }, + ]), PluginSignature::build("dbus call") .is_dbus_command() .accepts_dbus_client_options() @@ -169,6 +203,7 @@ impl Plugin for NuPluginDbus { span: Some(call.head) }), + "dbus introspect" => self.introspect(call), "dbus call" => self.call(call), "dbus get" => self.get(call), "dbus get-all" => self.get_all(call), @@ -208,6 +243,16 @@ impl DbusSignatureUtilExt for PluginSignature { } impl NuPluginDbus { + fn introspect(&self, call: &EvaluatedCall) -> Result { + let config = DbusClientConfig::try_from(call)?; + let dbus = DbusClient::new(config)?; + let node = dbus.introspect( + &call.get_flag("dest")?.unwrap(), + &call.req(0)?, + )?; + Ok(node.to_value(call.head)) + } + fn call(&self, call: &EvaluatedCall) -> Result { let config = DbusClientConfig::try_from(call)?; let dbus = DbusClient::new(config)?;