Nushell plugin for interacting with D-Bus
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

implement latest nushell api updates

+698 -425
+42 -49
src/client.rs
··· 3 3 channel::{BusType, Channel}, 4 4 Message, 5 5 }; 6 - use nu_plugin::LabeledError; 7 - use nu_protocol::{Spanned, Value}; 6 + use nu_protocol::{LabeledError, Spanned, Value}; 8 7 9 8 use crate::{ 10 9 config::{DbusBusChoice, DbusClientConfig}, ··· 23 22 // Convenience macros for error handling 24 23 macro_rules! validate_with { 25 24 ($type:ty, $spanned:expr) => { 26 - <$type>::new(&$spanned.item).map_err(|msg| LabeledError { 27 - label: msg, 28 - msg: "this argument is incorrect".into(), 29 - span: Some($spanned.span), 30 - }) 25 + <$type>::new(&$spanned.item) 26 + .map_err(|msg| LabeledError::new("Invalid argument").with_label(msg, $spanned.span)) 31 27 }; 32 28 } 33 29 ··· 44 40 Ok(ch) 45 41 }), 46 42 } 47 - .map_err(|err| LabeledError { 48 - label: err.to_string(), 49 - msg: "while connecting to D-Bus as specified here".into(), 50 - span: Some(config.bus_choice.span), 43 + .map_err(|err| { 44 + LabeledError::new(err.to_string()).with_label( 45 + "while connecting to D-Bus as specified here", 46 + config.bus_choice.span, 47 + ) 51 48 })?; 52 49 Ok(DbusClient { 53 50 config, ··· 56 53 } 57 54 58 55 fn error(&self, err: impl std::fmt::Display, msg: impl std::fmt::Display) -> LabeledError { 59 - LabeledError { 60 - label: err.to_string(), 61 - msg: msg.to_string(), 62 - span: Some(self.config.span), 63 - } 56 + LabeledError::new(err.to_string()).with_label(msg.to_string(), self.config.span) 64 57 } 65 58 66 59 /// Introspect a D-Bus object ··· 107 100 let node = self.introspect(dest, object)?; 108 101 109 102 if let Some(sig) = node.get_method_args_signature(&interface.item, &method.item) { 110 - DbusType::parse_all(&sig).map_err(|err| LabeledError { 111 - label: format!( 103 + DbusType::parse_all(&sig).map_err(|err| { 104 + LabeledError::new(format!( 112 105 "while getting interface {:?} method {:?} signature: {}", 113 106 interface.item, method.item, err 114 - ), 115 - msg: "try running with --no-introspect or --signature".into(), 116 - span: Some(self.config.span), 107 + )) 108 + .with_label( 109 + "try running with --no-introspect or --signature", 110 + self.config.span, 111 + ) 117 112 }) 118 113 } else { 119 - Err(LabeledError { 120 - label: format!("Method {:?} not found on {:?}", method.item, interface.item), 121 - msg: "check that this method/interface is correct".into(), 122 - span: Some(method.span), 123 - }) 114 + Err(LabeledError::new(format!( 115 + "Method {:?} not found on {:?}", 116 + method.item, interface.item 117 + )) 118 + .with_label("check that this method/interface is correct", method.span)) 124 119 } 125 120 } 126 121 ··· 135 130 let node = self.introspect(dest, object)?; 136 131 137 132 if let Some(sig) = node.get_property_signature(&interface.item, &property.item) { 138 - DbusType::parse_all(sig).map_err(|err| LabeledError { 139 - label: format!( 133 + DbusType::parse_all(sig).map_err(|err| { 134 + LabeledError::new(format!( 140 135 "while getting interface {:?} property {:?} signature: {}", 141 136 interface.item, property.item, err 142 - ), 143 - msg: "try running with --no-introspect or --signature".into(), 144 - span: Some(self.config.span), 137 + )) 138 + .with_label( 139 + "try running with --no-introspect or --signature", 140 + self.config.span, 141 + ) 145 142 }) 146 143 } else { 147 - Err(LabeledError { 148 - label: format!( 149 - "Property {:?} not found on {:?}", 150 - property.item, interface.item 151 - ), 152 - msg: "check that this property/interface is correct".into(), 153 - span: Some(property.span), 154 - }) 144 + Err(LabeledError::new(format!( 145 + "Property {:?} not found on {:?}", 146 + property.item, interface.item 147 + )) 148 + .with_label( 149 + "check that this property or interface is correct", 150 + property.span, 151 + )) 155 152 } 156 153 } 157 154 ··· 176 173 // Parse the signature 177 174 let mut valid_signature = signature 178 175 .map(|s| { 179 - DbusType::parse_all(&s.item).map_err(|err| LabeledError { 180 - label: err, 181 - msg: "in signature specified here".into(), 182 - span: Some(s.span), 176 + DbusType::parse_all(&s.item).map_err(|err| { 177 + LabeledError::new(err).with_label("in signature specified here", s.span) 183 178 }) 184 179 }) 185 180 .transpose()?; ··· 195 190 "Warning: D-Bus introspection failed on {:?}. \ 196 191 Use `--no-introspect` or pass `--signature` to silence this warning. \ 197 192 Cause: {}", 198 - object.item, err.label 193 + object.item, err 199 194 ); 200 195 } 201 196 } ··· 314 309 // Parse the signature 315 310 let mut valid_signature = signature 316 311 .map(|s| { 317 - DbusType::parse_all(&s.item).map_err(|err| LabeledError { 318 - label: err, 319 - msg: "in signature specified here".into(), 320 - span: Some(s.span), 312 + DbusType::parse_all(&s.item).map_err(|err| { 313 + LabeledError::new(err).with_label("in signature specified here", s.span) 321 314 }) 322 315 }) 323 316 .transpose()?; ··· 333 326 "Warning: D-Bus introspection failed on {:?}. \ 334 327 Use `--no-introspect` or pass `--signature` to silence this warning. \ 335 328 Cause: {}", 336 - object.item, err.label 329 + object.item, err 337 330 ); 338 331 } 339 332 }
+127
src/commands/call.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 + 6 + pub struct Call; 7 + 8 + impl SimplePluginCommand for Call { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus call" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::Any) 21 + .named( 22 + "signature", 23 + SyntaxShape::String, 24 + "Signature of the arguments to send, in D-Bus format.\n \ 25 + If not provided, they will be determined from introspection.\n \ 26 + If --no-introspect is specified and this is not provided, they will \ 27 + be guessed (poorly)", 28 + None, 29 + ) 30 + .switch( 31 + "no-flatten", 32 + "Always return a list of all return values", 33 + None, 34 + ) 35 + .switch( 36 + "no-introspect", 37 + "Don't use introspection to determine the correct argument signature", 38 + None, 39 + ) 40 + .required_named( 41 + "dest", 42 + SyntaxShape::String, 43 + "The name of the connection to send the method to", 44 + None, 45 + ) 46 + .required( 47 + "object", 48 + SyntaxShape::String, 49 + "The path to the object to call the method on", 50 + ) 51 + .required( 52 + "interface", 53 + SyntaxShape::String, 54 + "The name of the interface the method belongs to", 55 + ) 56 + .required( 57 + "method", 58 + SyntaxShape::String, 59 + "The name of the method to send", 60 + ) 61 + .rest( 62 + "args", 63 + SyntaxShape::Any, 64 + "Arguments to send with the method call", 65 + ) 66 + } 67 + 68 + fn usage(&self) -> &str { 69 + "Call a method and get its response" 70 + } 71 + 72 + fn extra_usage(&self) -> &str { 73 + "Returns an array if the method call returns more than one value." 74 + } 75 + 76 + fn search_terms(&self) -> Vec<&str> { 77 + vec!["dbus"] 78 + } 79 + 80 + fn examples(&self) -> Vec<Example> { 81 + vec![ 82 + Example { 83 + example: "dbus call --dest=org.freedesktop.DBus \ 84 + /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping", 85 + description: "Ping the D-Bus server itself", 86 + result: None, 87 + }, 88 + Example { 89 + example: "dbus call --dest=org.freedesktop.Notifications \ 90 + /org/freedesktop/Notifications org.freedesktop.Notifications \ 91 + Notify \"Floppy disks\" 0 \"media-floppy\" \"Rarely seen\" \ 92 + \"But sometimes still used\" [] {} 5000", 93 + description: "Show a notification on the desktop for 5 seconds", 94 + result: None, 95 + }, 96 + ] 97 + } 98 + 99 + fn run( 100 + &self, 101 + _plugin: &Self::Plugin, 102 + _engine: &EngineInterface, 103 + call: &EvaluatedCall, 104 + _input: &Value, 105 + ) -> Result<Value, LabeledError> { 106 + let config = DbusClientConfig::try_from(call)?; 107 + let dbus = DbusClient::new(config)?; 108 + let values = dbus.call( 109 + &call.get_flag("dest")?.unwrap(), 110 + &call.req(0)?, 111 + &call.req(1)?, 112 + &call.req(2)?, 113 + call.get_flag("signature")?.as_ref(), 114 + &call.positional[3..], 115 + )?; 116 + 117 + let flatten = !call.get_flag::<bool>("no-flatten")?.unwrap_or(false); 118 + 119 + // Make the output easier to deal with by returning a list only if there are multiple return 120 + // values (not so common) 121 + match values.len() { 122 + 0 if flatten => Ok(Value::nothing(call.head)), 123 + 1 if flatten => Ok(values.into_iter().nth(0).unwrap()), 124 + _ => Ok(Value::list(values, call.head)), 125 + } 126 + } 127 + }
+85
src/commands/get.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 + 6 + pub struct Get; 7 + 8 + impl SimplePluginCommand for Get { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus get" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::Any) 21 + .required_named( 22 + "dest", 23 + SyntaxShape::String, 24 + "The name of the connection to read the property from", 25 + None, 26 + ) 27 + .required( 28 + "object", 29 + SyntaxShape::String, 30 + "The path to the object to read the property from", 31 + ) 32 + .required( 33 + "interface", 34 + SyntaxShape::String, 35 + "The name of the interface the property belongs to", 36 + ) 37 + .required( 38 + "property", 39 + SyntaxShape::String, 40 + "The name of the property to read", 41 + ) 42 + } 43 + 44 + fn usage(&self) -> &str { 45 + "Get a D-Bus property" 46 + } 47 + 48 + fn search_terms(&self) -> Vec<&str> { 49 + vec!["dbus", "property", "read"] 50 + } 51 + 52 + fn examples(&self) -> Vec<Example> { 53 + vec![Example { 54 + example: "dbus get --dest=org.mpris.MediaPlayer2.spotify \ 55 + /org/mpris/MediaPlayer2 \ 56 + org.mpris.MediaPlayer2.Player Metadata", 57 + description: "Get the currently playing song in Spotify", 58 + result: Some(Value::test_record(nu_protocol::record!( 59 + "xesam:title" => Value::test_string("Birdie"), 60 + "xesam:artist" => Value::test_list(vec![ 61 + Value::test_string("LOVE PSYCHEDELICO") 62 + ]), 63 + "xesam:album" => Value::test_string("Love Your Love"), 64 + "xesam:url" => Value::test_string("https://open.spotify.com/track/51748BvzeeMs4PIdPuyZmv"), 65 + ))), 66 + }] 67 + } 68 + 69 + fn run( 70 + &self, 71 + _plugin: &Self::Plugin, 72 + _engine: &EngineInterface, 73 + call: &EvaluatedCall, 74 + _input: &Value, 75 + ) -> Result<Value, LabeledError> { 76 + let config = DbusClientConfig::try_from(call)?; 77 + let dbus = DbusClient::new(config)?; 78 + dbus.get( 79 + &call.get_flag("dest")?.unwrap(), 80 + &call.req(0)?, 81 + &call.req(1)?, 82 + &call.req(2)?, 83 + ) 84 + } 85 + }
+76
src/commands/get_all.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 + 6 + pub struct GetAll; 7 + 8 + impl SimplePluginCommand for GetAll { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus get-all" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::Record(vec![])) 21 + .required_named( 22 + "dest", 23 + SyntaxShape::String, 24 + "The name of the connection to read the property from", 25 + None, 26 + ) 27 + .required( 28 + "object", 29 + SyntaxShape::String, 30 + "The path to the object to read the property from", 31 + ) 32 + .required( 33 + "interface", 34 + SyntaxShape::String, 35 + "The name of the interface the property belongs to", 36 + ) 37 + } 38 + 39 + fn usage(&self) -> &str { 40 + "Get all D-Bus properties for the given object" 41 + } 42 + 43 + fn search_terms(&self) -> Vec<&str> { 44 + vec!["dbus", "properties", "property", "get"] 45 + } 46 + 47 + fn examples(&self) -> Vec<Example> { 48 + vec![Example { 49 + example: "dbus get-all --dest=org.mpris.MediaPlayer2.spotify \ 50 + /org/mpris/MediaPlayer2 \ 51 + org.mpris.MediaPlayer2.Player", 52 + description: "Get the current player state of Spotify", 53 + result: Some(Value::test_record(nu_protocol::record!( 54 + "CanPlay" => Value::test_bool(true), 55 + "Volume" => Value::test_float(0.43), 56 + "PlaybackStatus" => Value::test_string("Paused"), 57 + ))), 58 + }] 59 + } 60 + 61 + fn run( 62 + &self, 63 + _plugin: &Self::Plugin, 64 + _engine: &EngineInterface, 65 + call: &EvaluatedCall, 66 + _input: &Value, 67 + ) -> Result<Value, LabeledError> { 68 + let config = DbusClientConfig::try_from(call)?; 69 + let dbus = DbusClient::new(config)?; 70 + dbus.get_all( 71 + &call.get_flag("dest")?.unwrap(), 72 + &call.req(0)?, 73 + &call.req(1)?, 74 + ) 75 + } 76 + }
+84
src/commands/introspect.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 + 6 + pub struct Introspect; 7 + 8 + impl SimplePluginCommand for Introspect { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus introspect" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::Record(vec![])) 21 + .required_named( 22 + "dest", 23 + SyntaxShape::String, 24 + "The name of the connection that owns the object", 25 + None, 26 + ) 27 + .required( 28 + "object", 29 + SyntaxShape::String, 30 + "The path to the object to introspect", 31 + ) 32 + } 33 + 34 + fn usage(&self) -> &str { 35 + "Introspect a D-Bus object" 36 + } 37 + 38 + fn extra_usage(&self) -> &str { 39 + "Returns information about available nodes, interfaces, methods, \ 40 + signals, and properties on the given object path" 41 + } 42 + 43 + fn search_terms(&self) -> Vec<&str> { 44 + vec!["dbus", "help", "method"] 45 + } 46 + 47 + fn examples(&self) -> Vec<Example> { 48 + vec![ 49 + Example { 50 + example: "dbus introspect --dest=org.mpris.MediaPlayer2.spotify \ 51 + /org/mpris/MediaPlayer2 | explore", 52 + description: "Look at the MPRIS2 interfaces exposed by Spotify", 53 + result: None, 54 + }, 55 + Example { 56 + example: "dbus introspect --dest=org.kde.plasmashell \ 57 + /org/kde/osdService | get interfaces | \ 58 + where name == org.kde.osdService | get 0.methods", 59 + description: "Get methods exposed by KDE Plasma's on-screen display \ 60 + service", 61 + result: None, 62 + }, 63 + Example { 64 + example: "dbus introspect --dest=org.kde.KWin / | get children | \ 65 + select name", 66 + description: "List objects exposed by KWin", 67 + result: None, 68 + }, 69 + ] 70 + } 71 + 72 + fn run( 73 + &self, 74 + _plugin: &Self::Plugin, 75 + _engine: &EngineInterface, 76 + call: &EvaluatedCall, 77 + _input: &Value, 78 + ) -> Result<Value, LabeledError> { 79 + let config = DbusClientConfig::try_from(call)?; 80 + let dbus = DbusClient::new(config)?; 81 + let node = dbus.introspect(&call.get_flag("dest")?.unwrap(), &call.req(0)?)?; 82 + Ok(node.to_value(call.head)) 83 + } 84 + }
+90
src/commands/list.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, pattern::Pattern, DbusSignatureUtilExt}; 5 + 6 + pub struct List; 7 + 8 + impl SimplePluginCommand for List { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus list" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::List(Type::String.into())) 21 + .optional( 22 + "pattern", 23 + SyntaxShape::String, 24 + "An optional glob-like pattern to filter the result by", 25 + ) 26 + } 27 + 28 + fn usage(&self) -> &str { 29 + "List all available connection names on the bus" 30 + } 31 + 32 + fn extra_usage(&self) -> &str { 33 + "These can be used as arguments for --dest on any of the other commands." 34 + } 35 + 36 + fn search_terms(&self) -> Vec<&str> { 37 + vec!["dbus", "list", "find", "search", "help"] 38 + } 39 + 40 + fn examples(&self) -> Vec<Example> { 41 + vec![ 42 + Example { 43 + example: "dbus list", 44 + description: "List all names available on the bus", 45 + result: None, 46 + }, 47 + Example { 48 + example: "dbus list org.freedesktop.*", 49 + description: "List top-level freedesktop.org names on the bus \ 50 + (e.g. matches `org.freedesktop.PowerManagement`, \ 51 + but not `org.freedesktop.Management.Inhibit`)", 52 + result: Some(Value::test_list(vec![ 53 + Value::test_string("org.freedesktop.DBus"), 54 + Value::test_string("org.freedesktop.Flatpak"), 55 + Value::test_string("org.freedesktop.Notifications"), 56 + ])), 57 + }, 58 + Example { 59 + example: "dbus list org.mpris.MediaPlayer2.**", 60 + description: "List all MPRIS2 media players on the bus", 61 + result: Some(Value::test_list(vec![ 62 + Value::test_string("org.mpris.MediaPlayer2.spotify"), 63 + Value::test_string("org.mpris.MediaPlayer2.kdeconnect.mpris_000001"), 64 + ])), 65 + }, 66 + ] 67 + } 68 + 69 + fn run( 70 + &self, 71 + _plugin: &Self::Plugin, 72 + _engine: &EngineInterface, 73 + call: &EvaluatedCall, 74 + _input: &Value, 75 + ) -> Result<Value, LabeledError> { 76 + let config = DbusClientConfig::try_from(call)?; 77 + let dbus = DbusClient::new(config)?; 78 + let pattern = call 79 + .opt::<String>(0)? 80 + .map(|pat| Pattern::new(&pat, Some('.'))); 81 + let result = dbus.list(pattern.as_ref())?; 82 + Ok(Value::list( 83 + result 84 + .into_iter() 85 + .map(|s| Value::string(s, call.head)) 86 + .collect(), 87 + call.head, 88 + )) 89 + } 90 + }
+36
src/commands/main.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{LabeledError, Signature, Value}; 3 + 4 + use crate::DbusSignatureUtilExt; 5 + 6 + pub struct Main; 7 + 8 + impl SimplePluginCommand for Main { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()).dbus_command() 17 + } 18 + 19 + fn usage(&self) -> &str { 20 + "Commands for interacting with D-Bus" 21 + } 22 + 23 + fn search_terms(&self) -> Vec<&str> { 24 + vec!["dbus"] 25 + } 26 + 27 + fn run( 28 + &self, 29 + _plugin: &Self::Plugin, 30 + engine: &EngineInterface, 31 + call: &EvaluatedCall, 32 + _input: &Value, 33 + ) -> Result<Value, LabeledError> { 34 + Ok(Value::string(engine.get_help()?, call.head)) 35 + } 36 + }
+15
src/commands/mod.rs
··· 1 + mod call; 2 + mod get; 3 + mod get_all; 4 + mod introspect; 5 + mod list; 6 + mod main; 7 + mod set; 8 + 9 + pub use call::Call; 10 + pub use get::Get; 11 + pub use get_all::GetAll; 12 + pub use introspect::Introspect; 13 + pub use list::List; 14 + pub use main::Main; 15 + pub use set::Set;
+95
src/commands/set.rs
··· 1 + use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 + use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 + 4 + use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 + 6 + pub struct Set; 7 + 8 + impl SimplePluginCommand for Set { 9 + type Plugin = crate::NuPluginDbus; 10 + 11 + fn name(&self) -> &str { 12 + "dbus set" 13 + } 14 + 15 + fn signature(&self) -> Signature { 16 + Signature::build(self.name()) 17 + .dbus_command() 18 + .accepts_dbus_client_options() 19 + .accepts_timeout() 20 + .input_output_type(Type::Nothing, Type::Nothing) 21 + .named( 22 + "signature", 23 + SyntaxShape::String, 24 + "Signature of the value to set, in D-Bus format.\n \ 25 + If not provided, it will be determined from introspection.\n \ 26 + If --no-introspect is specified and this is not provided, it will \ 27 + be guessed (poorly)", 28 + None, 29 + ) 30 + .required_named( 31 + "dest", 32 + SyntaxShape::String, 33 + "The name of the connection to write the property on", 34 + None, 35 + ) 36 + .required( 37 + "object", 38 + SyntaxShape::String, 39 + "The path to the object to write the property on", 40 + ) 41 + .required( 42 + "interface", 43 + SyntaxShape::String, 44 + "The name of the interface the property belongs to", 45 + ) 46 + .required( 47 + "property", 48 + SyntaxShape::String, 49 + "The name of the property to write", 50 + ) 51 + .required( 52 + "value", 53 + SyntaxShape::Any, 54 + "The value to write to the property", 55 + ) 56 + } 57 + 58 + fn usage(&self) -> &str { 59 + "Set a D-Bus property" 60 + } 61 + 62 + fn search_terms(&self) -> Vec<&str> { 63 + vec!["dbus", "property", "write", "put"] 64 + } 65 + 66 + fn examples(&self) -> Vec<Example> { 67 + vec![Example { 68 + example: "dbus set --dest=org.mpris.MediaPlayer2.spotify \ 69 + /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player \ 70 + Volume 0.5", 71 + description: "Set the volume of Spotify to 50%", 72 + result: None, 73 + }] 74 + } 75 + 76 + fn run( 77 + &self, 78 + _plugin: &Self::Plugin, 79 + _engine: &EngineInterface, 80 + call: &EvaluatedCall, 81 + _input: &Value, 82 + ) -> Result<Value, LabeledError> { 83 + let config = DbusClientConfig::try_from(call)?; 84 + let dbus = DbusClient::new(config)?; 85 + dbus.set( 86 + &call.get_flag("dest")?.unwrap(), 87 + &call.req(0)?, 88 + &call.req(1)?, 89 + &call.req(2)?, 90 + call.get_flag("signature")?.as_ref(), 91 + &call.req(3)?, 92 + )?; 93 + Ok(Value::nothing(call.head)) 94 + } 95 + }
+6 -8
src/config.rs
··· 1 1 use std::time::Duration; 2 2 3 - use nu_plugin::{EvaluatedCall, LabeledError}; 4 - use nu_protocol::{Span, Spanned}; 3 + use nu_plugin::EvaluatedCall; 4 + use nu_protocol::{LabeledError, Span, Spanned}; 5 5 6 6 /// General configuration related to the D-Bus client connection 7 7 #[derive(Debug, Clone)] ··· 81 81 } 82 82 "timeout" => { 83 83 if let Some(value) = value { 84 - let nanos: u64 = 85 - value.as_duration()?.try_into().map_err(|_| LabeledError { 86 - label: "Timeout must be a positive duration".into(), 87 - msg: "invalid timeout specified here".into(), 88 - span: Some(value.span()), 89 - })?; 84 + let nanos: u64 = value.as_duration()?.try_into().map_err(|_| { 85 + LabeledError::new("Timeout must be a positive duration") 86 + .with_label("invalid timeout specified here", value.span()) 87 + })?; 90 88 let item = Duration::from_nanos(nanos); 91 89 config.timeout = Spanned { 92 90 item,
+28 -33
src/convert.rs
··· 5 5 }, 6 6 Message, Signature, 7 7 }; 8 - use nu_plugin::LabeledError; 9 - use nu_protocol::{Record, Span, Value}; 8 + use nu_protocol::{LabeledError, Record, Span, Value}; 10 9 use std::str::FromStr; 11 10 12 11 use crate::dbus_type::DbusType; ··· 101 100 // Report errors from conversion. Error must support Display 102 101 macro_rules! try_convert { 103 102 ($result_expr:expr) => { 104 - $result_expr.map_err(|err| LabeledError { 105 - label: format!( 103 + $result_expr.map_err(|err| { 104 + LabeledError::new(format!( 106 105 "Failed to convert value to the D-Bus `{:?}` type", 107 106 expected_type.unwrap() 108 - ), 109 - msg: err.to_string(), 110 - span: Some(value.span()), 107 + )) 108 + .with_label(err.to_string(), value.span()) 111 109 })? 112 110 }; 113 111 } ··· 209 207 // Struct 210 208 (Value::List { vals, .. }, Some(DbusType::Struct(types))) => { 211 209 if vals.len() != types.len() { 212 - return Err(LabeledError { 213 - label: format!( 214 - "expected struct with {} element(s) ({:?})", 215 - types.len(), 216 - types 217 - ), 218 - msg: format!("this list has {} element(s) instead", vals.len()), 219 - span: Some(value.span()), 220 - }); 210 + return Err(LabeledError::new(format!( 211 + "expected struct with {} element(s) ({:?})", 212 + types.len(), 213 + types 214 + )) 215 + .with_label( 216 + format!("this list has {} element(s) instead", vals.len()), 217 + value.span(), 218 + )); 221 219 } 222 220 let items = vals 223 221 .iter() ··· 257 255 ))), 258 256 259 257 // Value not compatible with expected type 260 - (other_value, Some(expectation)) => Err(LabeledError { 261 - label: format!( 262 - "`{}` can not be converted to the D-Bus `{:?}` type", 263 - other_value.get_type(), 264 - expectation 265 - ), 266 - msg: format!("expected a `{:?}` here", expectation), 267 - span: Some(other_value.span()), 268 - }), 258 + (other_value, Some(expectation)) => Err(LabeledError::new(format!( 259 + "`{}` can not be converted to the D-Bus `{:?}` type", 260 + other_value.get_type(), 261 + expectation 262 + )) 263 + .with_label( 264 + format!("expected a `{:?}` here", expectation), 265 + other_value.span(), 266 + )), 269 267 270 268 // Automatic types (with no type expectation) 271 269 (Value::String { .. }, None) => to_message_item(value, Some(&DbusType::String)), ··· 283 281 ), 284 282 285 283 // No expected type, but can't handle this type 286 - _ => Err(LabeledError { 287 - label: format!( 288 - "can not use values of type `{}` in D-Bus calls", 289 - value.get_type() 290 - ), 291 - msg: "use a supported type here instead".into(), 292 - span: Some(value.span()), 293 - }), 284 + _ => Err(LabeledError::new(format!( 285 + "can not use values of type `{}` in D-Bus calls", 286 + value.get_type() 287 + )) 288 + .with_label("use a supported type here instead", value.span())), 294 289 } 295 290 }
+14 -335
src/main.rs
··· 1 - use nu_plugin::{ 2 - serve_plugin, EngineInterface, EvaluatedCall, LabeledError, MsgPackSerializer, Plugin, 3 - }; 4 - use nu_protocol::{PluginExample, PluginSignature, Span, SyntaxShape, Type, Value}; 1 + use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin, PluginCommand}; 2 + use nu_protocol::SyntaxShape; 5 3 6 4 mod client; 5 + mod commands; 7 6 mod config; 8 7 mod convert; 9 8 mod dbus_type; 10 9 mod introspection; 11 10 mod pattern; 12 11 13 - use client::*; 14 - use config::*; 15 - 16 - use crate::pattern::Pattern; 17 - 18 12 fn main() { 19 13 serve_plugin(&NuPluginDbus, MsgPackSerializer) 20 14 } 21 15 22 16 /// The main plugin interface for nushell 23 - struct NuPluginDbus; 17 + pub struct NuPluginDbus; 24 18 25 19 impl Plugin for NuPluginDbus { 26 - fn signature(&self) -> Vec<PluginSignature> { 27 - macro_rules! str { 28 - ($s:expr) => { 29 - Value::string($s, Span::unknown()) 30 - }; 31 - } 20 + fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> { 32 21 vec![ 33 - PluginSignature::build("dbus") 34 - .dbus_command() 35 - .usage("Commands for interacting with D-Bus"), 36 - PluginSignature::build("dbus introspect") 37 - .dbus_command() 38 - .accepts_dbus_client_options() 39 - .accepts_timeout() 40 - .usage("Introspect a D-Bus object") 41 - .input_output_type(Type::Nothing, Type::Record(vec![])) 42 - .extra_usage("Returns information about available nodes, interfaces, methods, \ 43 - signals, and properties on the given object path") 44 - .required_named("dest", SyntaxShape::String, 45 - "The name of the connection that owns the object", 46 - None) 47 - .required("object", SyntaxShape::String, 48 - "The path to the object to introspect") 49 - .plugin_examples(vec![ 50 - PluginExample { 51 - example: "dbus introspect --dest=org.mpris.MediaPlayer2.spotify \ 52 - /org/mpris/MediaPlayer2 | explore".into(), 53 - description: "Look at the MPRIS2 interfaces exposed by Spotify".into(), 54 - result: None, 55 - }, 56 - PluginExample { 57 - example: "dbus introspect --dest=org.kde.plasmashell \ 58 - /org/kde/osdService | get interfaces | \ 59 - where name == org.kde.osdService | get 0.methods".into(), 60 - description: "Get methods exposed by KDE Plasma's on-screen display \ 61 - service".into(), 62 - result: None, 63 - }, 64 - PluginExample { 65 - example: "dbus introspect --dest=org.kde.KWin / | get children | \ 66 - select name".into(), 67 - description: "List objects exposed by KWin".into(), 68 - result: None, 69 - }, 70 - ]), 71 - PluginSignature::build("dbus call") 72 - .dbus_command() 73 - .accepts_dbus_client_options() 74 - .accepts_timeout() 75 - .usage("Call a method and get its response") 76 - .extra_usage("Returns an array if the method call returns more than one value.") 77 - .input_output_type(Type::Nothing, Type::Any) 78 - .named("signature", SyntaxShape::String, 79 - "Signature of the arguments to send, in D-Bus format.\n \ 80 - If not provided, they will be determined from introspection.\n \ 81 - If --no-introspect is specified and this is not provided, they will \ 82 - be guessed (poorly)", None) 83 - .switch("no-flatten", 84 - "Always return a list of all return values", None) 85 - .switch("no-introspect", 86 - "Don't use introspection to determine the correct argument signature", None) 87 - .required_named("dest", SyntaxShape::String, 88 - "The name of the connection to send the method to", 89 - None) 90 - .required("object", SyntaxShape::String, 91 - "The path to the object to call the method on") 92 - .required("interface", SyntaxShape::String, 93 - "The name of the interface the method belongs to") 94 - .required("method", SyntaxShape::String, 95 - "The name of the method to send") 96 - .rest("args", SyntaxShape::Any, 97 - "Arguments to send with the method call") 98 - .plugin_examples(vec![ 99 - PluginExample { 100 - example: "dbus call --dest=org.freedesktop.DBus \ 101 - /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping".into(), 102 - description: "Ping the D-Bus server itself".into(), 103 - result: None 104 - }, 105 - PluginExample { 106 - example: "dbus call --dest=org.freedesktop.Notifications \ 107 - /org/freedesktop/Notifications org.freedesktop.Notifications \ 108 - Notify \"Floppy disks\" 0 \"media-floppy\" \"Rarely seen\" \ 109 - \"But sometimes still used\" [] {} 5000".into(), 110 - description: "Show a notification on the desktop for 5 seconds".into(), 111 - result: None 112 - }, 113 - ]), 114 - PluginSignature::build("dbus get") 115 - .dbus_command() 116 - .accepts_dbus_client_options() 117 - .accepts_timeout() 118 - .usage("Get a D-Bus property") 119 - .input_output_type(Type::Nothing, Type::Any) 120 - .required_named("dest", SyntaxShape::String, 121 - "The name of the connection to read the property from", 122 - None) 123 - .required("object", SyntaxShape::String, 124 - "The path to the object to read the property from") 125 - .required("interface", SyntaxShape::String, 126 - "The name of the interface the property belongs to") 127 - .required("property", SyntaxShape::String, 128 - "The name of the property to read") 129 - .plugin_examples(vec![ 130 - PluginExample { 131 - example: "dbus get --dest=org.mpris.MediaPlayer2.spotify \ 132 - /org/mpris/MediaPlayer2 \ 133 - org.mpris.MediaPlayer2.Player Metadata".into(), 134 - description: "Get the currently playing song in Spotify".into(), 135 - result: Some(Value::record(nu_protocol::record!( 136 - "xesam:title" => str!("Birdie"), 137 - "xesam:artist" => Value::list(vec![ 138 - str!("LOVE PSYCHEDELICO") 139 - ], Span::unknown()), 140 - "xesam:album" => str!("Love Your Love"), 141 - "xesam:url" => str!("https://open.spotify.com/track/51748BvzeeMs4PIdPuyZmv"), 142 - ), Span::unknown())) 143 - }, 144 - ]), 145 - PluginSignature::build("dbus get-all") 146 - .dbus_command() 147 - .accepts_dbus_client_options() 148 - .accepts_timeout() 149 - .usage("Get all D-Bus properties for the given object") 150 - .input_output_type(Type::Nothing, Type::Record(vec![])) 151 - .required_named("dest", SyntaxShape::String, 152 - "The name of the connection to read the property from", 153 - None) 154 - .required("object", SyntaxShape::String, 155 - "The path to the object to read the property from") 156 - .required("interface", SyntaxShape::String, 157 - "The name of the interface the property belongs to") 158 - .plugin_examples(vec![ 159 - PluginExample { 160 - example: "dbus get-all --dest=org.mpris.MediaPlayer2.spotify \ 161 - /org/mpris/MediaPlayer2 \ 162 - org.mpris.MediaPlayer2.Player".into(), 163 - description: "Get the current player state of Spotify".into(), 164 - result: Some(Value::record(nu_protocol::record!( 165 - "CanPlay" => Value::bool(true, Span::unknown()), 166 - "Volume" => Value::float(0.43, Span::unknown()), 167 - "PlaybackStatus" => str!("Paused"), 168 - ), Span::unknown())) 169 - }, 170 - ]), 171 - PluginSignature::build("dbus set") 172 - .dbus_command() 173 - .accepts_dbus_client_options() 174 - .accepts_timeout() 175 - .usage("Set a D-Bus property") 176 - .input_output_type(Type::Nothing, Type::Nothing) 177 - .named("signature", SyntaxShape::String, 178 - "Signature of the value to set, in D-Bus format.\n \ 179 - If not provided, it will be determined from introspection.\n \ 180 - If --no-introspect is specified and this is not provided, it will \ 181 - be guessed (poorly)", None) 182 - .required_named("dest", SyntaxShape::String, 183 - "The name of the connection to write the property on", 184 - None) 185 - .required("object", SyntaxShape::String, 186 - "The path to the object to write the property on") 187 - .required("interface", SyntaxShape::String, 188 - "The name of the interface the property belongs to") 189 - .required("property", SyntaxShape::String, 190 - "The name of the property to write") 191 - .required("value", SyntaxShape::Any, 192 - "The value to write to the property") 193 - .plugin_examples(vec![ 194 - PluginExample { 195 - example: "dbus set --dest=org.mpris.MediaPlayer2.spotify \ 196 - /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player \ 197 - Volume 0.5".into(), 198 - description: "Set the volume of Spotify to 50%".into(), 199 - result: None, 200 - }, 201 - ]), 202 - PluginSignature::build("dbus list") 203 - .dbus_command() 204 - .accepts_dbus_client_options() 205 - .accepts_timeout() 206 - .usage("List all available connection names on the bus") 207 - .extra_usage("These can be used as arguments for --dest on any of the other commands.") 208 - .input_output_type(Type::Nothing, Type::List(Type::String.into())) 209 - .optional("pattern", SyntaxShape::String, 210 - "An optional glob-like pattern to filter the result by") 211 - .plugin_examples(vec![ 212 - PluginExample { 213 - example: "dbus list".into(), 214 - description: "List all names available on the bus".into(), 215 - result: None, 216 - }, 217 - PluginExample { 218 - example: "dbus list org.freedesktop.*".into(), 219 - description: "List top-level freedesktop.org names on the bus \ 220 - (e.g. matches `org.freedesktop.PowerManagement`, \ 221 - but not `org.freedesktop.Management.Inhibit`)".into(), 222 - result: Some(Value::list(vec![ 223 - str!("org.freedesktop.DBus"), 224 - str!("org.freedesktop.Flatpak"), 225 - str!("org.freedesktop.Notifications"), 226 - ], Span::unknown())), 227 - }, 228 - PluginExample { 229 - example: "dbus list org.mpris.MediaPlayer2.**".into(), 230 - description: "List all MPRIS2 media players on the bus".into(), 231 - result: Some(Value::list(vec![ 232 - str!("org.mpris.MediaPlayer2.spotify"), 233 - str!("org.mpris.MediaPlayer2.kdeconnect.mpris_000001"), 234 - ], Span::unknown())), 235 - }, 236 - ]) 22 + Box::new(commands::Main), 23 + Box::new(commands::Introspect), 24 + Box::new(commands::Call), 25 + Box::new(commands::Get), 26 + Box::new(commands::GetAll), 27 + Box::new(commands::Set), 28 + Box::new(commands::List), 237 29 ] 238 30 } 239 - 240 - fn run( 241 - &self, 242 - name: &str, 243 - _engine: &EngineInterface, 244 - call: &EvaluatedCall, 245 - _input: &Value, 246 - ) -> Result<Value, LabeledError> { 247 - match name { 248 - "dbus" => Err(LabeledError { 249 - label: "The `dbus` command requires a subcommand".into(), 250 - msg: "add --help to see subcommands".into(), 251 - span: Some(call.head), 252 - }), 253 - 254 - "dbus introspect" => self.introspect(call), 255 - "dbus call" => self.call(call), 256 - "dbus get" => self.get(call), 257 - "dbus get-all" => self.get_all(call), 258 - "dbus set" => self.set(call), 259 - "dbus list" => self.list(call), 260 - 261 - _ => Err(LabeledError { 262 - label: "Plugin invoked with unknown command name".into(), 263 - msg: "unknown command".into(), 264 - span: Some(call.head), 265 - }), 266 - } 267 - } 268 31 } 269 32 270 33 /// For conveniently adding the base options to a dbus command ··· 274 37 fn accepts_timeout(self) -> Self; 275 38 } 276 39 277 - impl DbusSignatureUtilExt for PluginSignature { 40 + impl DbusSignatureUtilExt for nu_protocol::Signature { 278 41 fn dbus_command(self) -> Self { 279 - self.search_terms(vec!["dbus".into()]) 280 - .category(nu_protocol::Category::Platform) 42 + self.category(nu_protocol::Category::Platform) 281 43 } 282 44 283 45 fn accepts_dbus_client_options(self) -> Self { ··· 312 74 ) 313 75 } 314 76 } 315 - 316 - impl NuPluginDbus { 317 - fn introspect(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 318 - let config = DbusClientConfig::try_from(call)?; 319 - let dbus = DbusClient::new(config)?; 320 - let node = dbus.introspect(&call.get_flag("dest")?.unwrap(), &call.req(0)?)?; 321 - Ok(node.to_value(call.head)) 322 - } 323 - 324 - fn call(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 325 - let config = DbusClientConfig::try_from(call)?; 326 - let dbus = DbusClient::new(config)?; 327 - let values = dbus.call( 328 - &call.get_flag("dest")?.unwrap(), 329 - &call.req(0)?, 330 - &call.req(1)?, 331 - &call.req(2)?, 332 - call.get_flag("signature")?.as_ref(), 333 - &call.positional[3..], 334 - )?; 335 - 336 - let flatten = !call.get_flag::<bool>("no-flatten")?.unwrap_or(false); 337 - 338 - // Make the output easier to deal with by returning a list only if there are multiple return 339 - // values (not so common) 340 - match values.len() { 341 - 0 if flatten => Ok(Value::nothing(call.head)), 342 - 1 if flatten => Ok(values.into_iter().nth(0).unwrap()), 343 - _ => Ok(Value::list(values, call.head)), 344 - } 345 - } 346 - 347 - fn get(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 348 - let config = DbusClientConfig::try_from(call)?; 349 - let dbus = DbusClient::new(config)?; 350 - dbus.get( 351 - &call.get_flag("dest")?.unwrap(), 352 - &call.req(0)?, 353 - &call.req(1)?, 354 - &call.req(2)?, 355 - ) 356 - } 357 - 358 - fn get_all(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 359 - let config = DbusClientConfig::try_from(call)?; 360 - let dbus = DbusClient::new(config)?; 361 - dbus.get_all( 362 - &call.get_flag("dest")?.unwrap(), 363 - &call.req(0)?, 364 - &call.req(1)?, 365 - ) 366 - } 367 - 368 - fn set(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 369 - let config = DbusClientConfig::try_from(call)?; 370 - let dbus = DbusClient::new(config)?; 371 - dbus.set( 372 - &call.get_flag("dest")?.unwrap(), 373 - &call.req(0)?, 374 - &call.req(1)?, 375 - &call.req(2)?, 376 - call.get_flag("signature")?.as_ref(), 377 - &call.req(3)?, 378 - )?; 379 - Ok(Value::nothing(call.head)) 380 - } 381 - 382 - fn list(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> { 383 - let config = DbusClientConfig::try_from(call)?; 384 - let dbus = DbusClient::new(config)?; 385 - let pattern = call 386 - .opt::<String>(0)? 387 - .map(|pat| Pattern::new(&pat, Some('.'))); 388 - let result = dbus.list(pattern.as_ref())?; 389 - Ok(Value::list( 390 - result 391 - .into_iter() 392 - .map(|s| Value::string(s, call.head)) 393 - .collect(), 394 - call.head, 395 - )) 396 - } 397 - }