Skip to content

Commit

Permalink
Add dbus introspect command, for access to introspection data in a …
Browse files Browse the repository at this point in the history
…more comfortable format than XML
  • Loading branch information
devyn committed Feb 1, 2024
1 parent 3f6feab commit bc3d152
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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} <object>

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 <String> - Send to the bus server at the given address
--peer <String> - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization.
--timeout <Duration> - How long to wait for a response
--dest (required parameter) <String> - The name of the connection that owns the object

Parameters:
object <string>: 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
Expand Down
107 changes: 107 additions & 0 deletions src/introspection.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand All @@ -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)]
Expand All @@ -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 {
Expand All @@ -144,6 +199,17 @@ pub struct Signal {
pub annotations: Vec<Annotation>,
}

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 {
Expand All @@ -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 {
Expand All @@ -162,6 +238,18 @@ pub struct Property {
pub annotations: Vec<Annotation>,
}

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 {
Expand All @@ -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 {
Expand All @@ -182,6 +281,14 @@ impl Annotation {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> 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)]
Expand Down
45 changes: 45 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -208,6 +243,16 @@ impl DbusSignatureUtilExt for PluginSignature {
}

impl NuPluginDbus {
fn introspect(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
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<Value, LabeledError> {
let config = DbusClientConfig::try_from(call)?;
let dbus = DbusClient::new(config)?;
Expand Down

0 comments on commit bc3d152

Please sign in to comment.