Nushell plugin for interacting with D-Bus
at main 413 lines 12 kB view raw
1use nu_protocol::{Span, Value, record}; 2use serde::Deserialize; 3use serde_xml_rs::SerdeXml; 4 5macro_rules! list_to_value { 6 ($list:expr, $span:expr) => { 7 Value::list($list.iter().map(|i| i.to_value($span)).collect(), $span) 8 }; 9} 10 11#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] 12#[serde(rename_all = "kebab-case")] 13pub struct Node { 14 #[serde(default, rename = "@name")] 15 pub name: Option<String>, 16 #[serde(default, rename = "interface")] 17 pub interfaces: Vec<Interface>, 18 #[serde(default, rename = "node")] 19 pub children: Vec<Node>, 20} 21 22impl Node { 23 pub fn from_xml(xml: &str) -> Result<Node, serde_xml_rs::Error> { 24 dbg!(xml); 25 let config = SerdeXml::new().overlapping_sequences(true); 26 let mut deserializer = serde_xml_rs::de::Deserializer::from_config(config, xml.as_bytes()); 27 Node::deserialize(&mut deserializer) 28 } 29 30 #[cfg(test)] 31 pub fn with_name(name: impl Into<String>) -> Node { 32 Node { 33 name: Some(name.into()), 34 interfaces: vec![], 35 children: vec![], 36 } 37 } 38 39 pub fn get_interface(&self, name: &str) -> Option<&Interface> { 40 self.interfaces.iter().find(|i| i.name == name) 41 } 42 43 /// Find a method on an interface on this node, and then generate the signature of the method 44 /// args 45 pub fn get_method_args_signature(&self, interface: &str, method: &str) -> Option<String> { 46 Some( 47 self.get_interface(interface)? 48 .get_method(method)? 49 .in_signature(), 50 ) 51 } 52 53 /// Find the signature of a property on an interface on this node 54 pub fn get_property_signature(&self, interface: &str, property: &str) -> Option<&str> { 55 Some( 56 &self 57 .get_interface(interface)? 58 .get_property(property)? 59 .r#type, 60 ) 61 } 62 63 /// Represent the node as a nushell [Value] 64 pub fn to_value(&self, span: Span) -> Value { 65 Value::record( 66 record! { 67 "name" => self.name.as_ref().map(|s| Value::string(s, span)).unwrap_or_default(), 68 "interfaces" => list_to_value!(self.interfaces, span), 69 "children" => list_to_value!(self.children, span), 70 }, 71 span, 72 ) 73 } 74} 75 76#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 77#[serde(rename_all = "kebab-case")] 78pub struct Interface { 79 #[serde(rename = "@name")] 80 pub name: String, 81 #[serde(default, rename = "method")] 82 pub methods: Vec<Method>, 83 #[serde(default, rename = "signal")] 84 pub signals: Vec<Signal>, 85 #[serde(default, rename = "property")] 86 pub properties: Vec<Property>, 87 #[serde(default, rename = "annotation")] 88 pub annotations: Vec<Annotation>, 89} 90 91impl Interface { 92 pub fn get_method(&self, name: &str) -> Option<&Method> { 93 self.methods.iter().find(|m| m.name == name) 94 } 95 96 #[allow(dead_code)] 97 pub fn get_signal(&self, name: &str) -> Option<&Signal> { 98 self.signals.iter().find(|s| s.name == name) 99 } 100 101 pub fn get_property(&self, name: &str) -> Option<&Property> { 102 self.properties.iter().find(|p| p.name == name) 103 } 104 105 /// Represent the interface as a nushell [Value] 106 pub fn to_value(&self, span: Span) -> Value { 107 Value::record( 108 record! { 109 "name" => Value::string(&self.name, span), 110 "methods" => list_to_value!(self.methods, span), 111 "signals" => list_to_value!(self.signals, span), 112 "properties" => list_to_value!(self.properties, span), 113 "annotations" => list_to_value!(self.annotations, span), 114 }, 115 span, 116 ) 117 } 118} 119 120#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 121#[serde(rename_all = "kebab-case")] 122pub struct Method { 123 #[serde(rename = "@name")] 124 pub name: String, 125 #[serde(default, rename = "arg")] 126 pub args: Vec<MethodArg>, 127 #[serde(default, rename = "annotation")] 128 pub annotations: Vec<Annotation>, 129} 130 131impl Method { 132 /// Get the signature of the method args 133 pub fn in_signature(&self) -> String { 134 self.args 135 .iter() 136 .filter(|arg| arg.direction == Direction::In) 137 .map(|arg| &arg.r#type[..]) 138 .collect() 139 } 140 141 #[allow(dead_code)] 142 /// Get the signature of the method result 143 pub fn out_signature(&self) -> String { 144 self.args 145 .iter() 146 .filter(|arg| arg.direction == Direction::Out) 147 .map(|arg| &arg.r#type[..]) 148 .collect() 149 } 150 151 /// Represent the method as a nushell [Value] 152 pub fn to_value(&self, span: Span) -> Value { 153 Value::record( 154 record! { 155 "name" => Value::string(&self.name, span), 156 "args" => list_to_value!(self.args, span), 157 "annotations" => list_to_value!(self.annotations, span), 158 }, 159 span, 160 ) 161 } 162} 163 164#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 165#[serde(rename_all = "kebab-case")] 166pub struct MethodArg { 167 #[serde(default, rename = "@name")] 168 pub name: Option<String>, 169 #[serde(rename = "@type")] 170 pub r#type: String, 171 #[serde(default, rename = "@direction")] 172 pub direction: Direction, 173} 174 175impl MethodArg { 176 #[cfg(test)] 177 pub fn new( 178 name: impl Into<String>, 179 r#type: impl Into<String>, 180 direction: Direction, 181 ) -> MethodArg { 182 MethodArg { 183 name: Some(name.into()), 184 r#type: r#type.into(), 185 direction, 186 } 187 } 188 189 /// Represent the method as a nushell [Value] 190 pub fn to_value(&self, span: Span) -> Value { 191 Value::record( 192 record! { 193 "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), 194 "type" => Value::string(&self.r#type, span), 195 "direction" => self.direction.to_value(span), 196 }, 197 span, 198 ) 199 } 200} 201 202#[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq, Eq)] 203#[serde(rename_all = "kebab-case")] 204pub enum Direction { 205 #[default] 206 In, 207 Out, 208} 209 210impl Direction { 211 /// Represent the direction as a nushell [Value] 212 pub fn to_value(self, span: Span) -> Value { 213 match self { 214 Direction::In => Value::string("in", span), 215 Direction::Out => Value::string("out", span), 216 } 217 } 218} 219 220#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 221#[serde(rename_all = "kebab-case")] 222pub struct Signal { 223 #[serde(rename = "@name")] 224 pub name: String, 225 #[serde(default, rename = "arg")] 226 pub args: Vec<SignalArg>, 227 #[serde(default, rename = "annotation")] 228 pub annotations: Vec<Annotation>, 229} 230 231impl Signal { 232 /// Represent the signal as a nushell [Value] 233 pub fn to_value(&self, span: Span) -> Value { 234 Value::record( 235 record! { 236 "name" => Value::string(&self.name, span), 237 "args" => list_to_value!(self.args, span), 238 "annotations" => list_to_value!(self.annotations, span), 239 }, 240 span, 241 ) 242 } 243} 244 245#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 246#[serde(rename_all = "kebab-case")] 247pub struct SignalArg { 248 #[serde(default, rename = "@name")] 249 pub name: Option<String>, 250 #[serde(rename = "@type")] 251 pub r#type: String, 252} 253 254impl SignalArg { 255 /// Represent the argument as a nushell [Value] 256 pub fn to_value(&self, span: Span) -> Value { 257 Value::record( 258 record! { 259 "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), 260 "type" => Value::string(&self.r#type, span), 261 }, 262 span, 263 ) 264 } 265} 266 267#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 268#[serde(rename_all = "kebab-case")] 269pub struct Property { 270 #[serde(rename = "@name")] 271 pub name: String, 272 #[serde(rename = "@type")] 273 pub r#type: String, 274 #[serde(rename = "@access")] 275 pub access: Access, 276 #[serde(default, rename = "annotation")] 277 pub annotations: Vec<Annotation>, 278} 279 280impl Property { 281 /// Represent the property as a nushell [Value] 282 pub fn to_value(&self, span: Span) -> Value { 283 Value::record( 284 record! { 285 "name" => Value::string(&self.name, span), 286 "type" => Value::string(&self.r#type, span), 287 "args" => self.access.to_value(span), 288 "annotations" => list_to_value!(self.annotations, span), 289 }, 290 span, 291 ) 292 } 293} 294 295#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 296#[serde(rename_all = "lowercase")] 297pub enum Access { 298 Read, 299 Write, 300 ReadWrite, 301} 302 303impl Access { 304 /// Represent the access as a nushell [Value] 305 pub fn to_value(&self, span: Span) -> Value { 306 match self { 307 Access::Read => Value::string("read", span), 308 Access::Write => Value::string("write", span), 309 Access::ReadWrite => Value::string("readwrite", span), 310 } 311 } 312} 313 314#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 315#[serde(rename_all = "kebab-case")] 316pub struct Annotation { 317 #[serde(rename = "@name")] 318 pub name: String, 319 #[serde(rename = "@value")] 320 pub value: String, 321} 322 323impl Annotation { 324 #[cfg(test)] 325 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Annotation { 326 Annotation { 327 name: name.into(), 328 value: value.into(), 329 } 330 } 331 332 /// Represent the annotation as a nushell [Value] 333 pub fn to_value(&self, span: Span) -> Value { 334 Value::record( 335 record! { 336 "name" => Value::string(&self.name, span), 337 "value" => Value::string(&self.value, span), 338 }, 339 span, 340 ) 341 } 342} 343 344#[cfg(test)] 345pub fn test_introspection_doc_rs() -> Node { 346 Node { 347 name: Some("/com/example/sample_object0".into()), 348 interfaces: vec![Interface { 349 name: "com.example.SampleInterface0".into(), 350 methods: vec![ 351 Method { 352 name: "Frobate".into(), 353 args: vec![ 354 MethodArg::new("foo", "i", Direction::In), 355 MethodArg::new("bar", "as", Direction::In), 356 MethodArg::new("baz", "a{us}", Direction::Out), 357 ], 358 annotations: vec![Annotation::new("org.freedesktop.DBus.Deprecated", "true")], 359 }, 360 Method { 361 name: "Bazify".into(), 362 args: vec![ 363 MethodArg::new("bar", "(iiu)", Direction::In), 364 MethodArg::new("len", "u", Direction::Out), 365 MethodArg::new("bar", "v", Direction::Out), 366 ], 367 annotations: vec![], 368 }, 369 Method { 370 name: "Mogrify".into(), 371 args: vec![MethodArg::new("bar", "(iiav)", Direction::In)], 372 annotations: vec![], 373 }, 374 ], 375 signals: vec![Signal { 376 name: "Changed".into(), 377 args: vec![SignalArg { 378 name: Some("new_value".into()), 379 r#type: "b".into(), 380 }], 381 annotations: vec![], 382 }], 383 properties: vec![Property { 384 name: "Bar".into(), 385 r#type: "y".into(), 386 access: Access::ReadWrite, 387 annotations: vec![], 388 }], 389 annotations: vec![], 390 }], 391 children: vec![ 392 Node::with_name("child_of_sample_object"), 393 Node::with_name("another_child_of_sample_object"), 394 ], 395 } 396} 397 398#[test] 399pub fn test_parse_introspection_doc() -> Result<(), serde_xml_rs::Error> { 400 let xml = include_str!("test_introspection_doc.xml"); 401 let result = Node::from_xml(xml)?; 402 assert_eq!(result, test_introspection_doc_rs()); 403 Ok(()) 404} 405 406#[test] 407pub fn test_get_method_args_signature() { 408 assert_eq!( 409 test_introspection_doc_rs() 410 .get_method_args_signature("com.example.SampleInterface0", "Frobate"), 411 Some("ias".into()) 412 ); 413}