tangled
alpha
login
or
join now
bwc9876.dev
/
nu_plugin_dbus
1
fork
atom
Nushell plugin for interacting with D-Bus
1
fork
atom
overview
issues
pulls
pipelines
implement clippy, formatting suggestions
Devyn Cairns
2 years ago
6ce2ffb8
b250d052
+795
-515
7 changed files
expand all
collapse all
unified
split
src
client.rs
config.rs
convert.rs
dbus_type.rs
introspection.rs
main.rs
pattern.rs
+146
-86
src/client.rs
···
1
-
use dbus::{channel::{Channel, BusType}, Message, arg::messageitem::MessageItem};
0
0
0
0
2
use nu_plugin::LabeledError;
3
use nu_protocol::{Spanned, Value};
4
5
-
use crate::{config::{DbusClientConfig, DbusBusChoice}, dbus_type::DbusType, convert::to_message_item, introspection::Node, pattern::Pattern};
0
0
0
0
0
0
6
7
/// Executes D-Bus actions on a connection, handling nushell types
8
pub struct DbusClient {
···
12
13
// Convenience macros for error handling
14
macro_rules! validate_with {
15
-
($type:ty, $spanned:expr) => (<$type>::new(&$spanned.item).map_err(|msg| {
16
-
LabeledError {
17
label: msg,
18
msg: "this argument is incorrect".into(),
19
span: Some($spanned.span),
20
-
}
21
-
}))
22
}
23
24
impl DbusClient {
···
33
ch.register()?;
34
Ok(ch)
35
}),
36
-
}.map_err(|err| {
37
-
LabeledError {
38
-
label: err.to_string(),
39
-
msg: "while connecting to D-Bus as specified here".into(),
40
-
span: Some(config.bus_choice.span),
41
-
}
42
})?;
43
Ok(DbusClient {
44
config,
45
-
conn: channel
46
})
47
}
48
···
50
LabeledError {
51
label: err.to_string(),
52
msg: msg.to_string(),
53
-
span: Some(self.config.span)
54
}
55
}
56
···
69
valid_dest,
70
valid_object,
71
"org.freedesktop.DBus.Introspectable",
72
-
"Introspect"
73
-
).map_err(|err| self.error(err, context))?;
0
74
75
// Send and get the response
76
-
let resp = self.conn.send_with_reply_and_block(message, self.config.timeout.item)
0
0
77
.map_err(|err| self.error(err, context))?;
78
79
// Parse it to a Node
80
-
let xml: &str = resp.get1()
0
81
.ok_or_else(|| self.error("Introspect method returned the wrong type", context))?;
82
83
Node::from_xml(xml).map_err(|err| self.error(err, context))
···
95
96
if let Some(sig) = node.get_method_args_signature(&interface.item, &method.item) {
97
DbusType::parse_all(&sig).map_err(|err| LabeledError {
98
-
label: format!("while getting interface {:?} method {:?} signature: {}",
99
-
interface.item,
100
-
method.item,
101
-
err),
102
msg: "try running with --no-introspect or --signature".into(),
103
span: Some(self.config.span),
104
})
···
122
let node = self.introspect(dest, object)?;
123
124
if let Some(sig) = node.get_property_signature(&interface.item, &property.item) {
125
-
DbusType::parse_all(&sig).map_err(|err| LabeledError {
126
-
label: format!("while getting interface {:?} property {:?} signature: {}",
127
-
interface.item,
128
-
property.item,
129
-
err),
130
msg: "try running with --no-introspect or --signature".into(),
131
span: Some(self.config.span),
132
})
133
} else {
134
Err(LabeledError {
135
-
label: format!("Property {:?} not found on {:?}", property.item, interface.item),
0
0
0
136
msg: "check that this property/interface is correct".into(),
137
span: Some(property.span),
138
})
···
158
let valid_method = validate_with!(dbus::strings::Member, method)?;
159
160
// Parse the signature
161
-
let mut valid_signature = signature.map(|s| DbusType::parse_all(&s.item).map_err(|err| {
162
-
LabeledError {
163
-
label: err,
164
-
msg: "in signature specified here".into(),
165
-
span: Some(s.span),
166
-
}
167
-
})).transpose()?;
0
0
168
169
// If not provided, try introspection (unless disabled)
170
if valid_signature.is_none() && self.config.introspect {
171
match self.get_method_signature_by_introspection(dest, object, interface, method) {
172
Ok(sig) => {
173
valid_signature = Some(sig);
174
-
},
175
Err(err) => {
176
-
eprintln!("Warning: D-Bus introspection failed on {:?}. \
0
177
Use `--no-introspect` or pass `--signature` to silence this warning. \
178
Cause: {}",
179
-
object.item,
180
-
err.label);
181
}
182
}
183
}
184
185
if let Some(sig) = &valid_signature {
186
if sig.len() != args.len() {
187
-
self.error(format!("expected {} arguments, got {}", sig.len(), args.len()), context);
0
0
0
188
}
189
}
190
191
// Construct the method call message
192
-
let mut message = Message::new_method_call(
193
-
valid_dest,
194
-
valid_object,
195
-
valid_interface,
196
-
valid_method,
197
-
).map_err(|err| self.error(err, context))?;
198
199
// Convert the args to message items
200
-
let sigs_iter = valid_signature.iter().flatten().map(Some).chain(std::iter::repeat(None));
0
0
0
0
201
for (val, sig) in args.iter().zip(sigs_iter) {
202
message = message.append1(to_message_item(val, sig)?);
203
}
204
205
// Send it on the channel and get the response
206
-
let resp = self.conn.send_with_reply_and_block(message, self.config.timeout.item)
0
0
207
.map_err(|err| self.error(err, context))?;
208
209
crate::convert::from_message(&resp, self.config.span)
···
224
self.call(
225
dest,
226
object,
227
-
&Spanned { item: "org.freedesktop.DBus.Properties".into(), span: self.config.span },
228
-
&Spanned { item: "Get".into(), span: self.config.span },
229
-
Some(&Spanned { item: "ss".into(), span: self.config.span }),
230
-
&[interface_val, property_val]
231
-
).map(|val| val.into_iter().nth(0).unwrap_or_default())
0
0
0
0
0
0
0
0
0
0
232
}
233
234
/// Get all D-Bus properties from the given object
···
243
self.call(
244
dest,
245
object,
246
-
&Spanned { item: "org.freedesktop.DBus.Properties".into(), span: self.config.span },
247
-
&Spanned { item: "GetAll".into(), span: self.config.span },
248
-
Some(&Spanned { item: "s".into(), span: self.config.span }),
249
-
&[interface_val]
250
-
).map(|val| val.into_iter().nth(0).unwrap_or_default())
0
0
0
0
0
0
0
0
0
0
251
}
252
253
/// Set a D-Bus property on the given object
···
267
let valid_object = validate_with!(dbus::strings::Path, object)?;
268
269
// Parse the signature
270
-
let mut valid_signature = signature.map(|s| DbusType::parse_all(&s.item).map_err(|err| {
271
-
LabeledError {
272
-
label: err,
273
-
msg: "in signature specified here".into(),
274
-
span: Some(s.span),
275
-
}
276
-
})).transpose()?;
0
0
277
278
// If not provided, try introspection (unless disabled)
279
if valid_signature.is_none() && self.config.introspect {
280
match self.get_property_signature_by_introspection(dest, object, interface, property) {
281
Ok(sig) => {
282
valid_signature = Some(sig);
283
-
},
284
Err(err) => {
285
-
eprintln!("Warning: D-Bus introspection failed on {:?}. \
0
286
Use `--no-introspect` or pass `--signature` to silence this warning. \
287
Cause: {}",
288
-
object.item,
289
-
err.label);
290
}
291
}
292
}
293
294
if let Some(sig) = &valid_signature {
295
if sig.len() != 1 {
296
-
self.error(format!(
297
-
"expected single object signature, but there are {}", sig.len()), context);
0
0
0
0
0
298
}
299
}
300
···
304
valid_object,
305
"org.freedesktop.DBus.Properties",
306
"Set",
307
-
).map_err(|err| self.error(err, context))?
308
-
.append2(&interface.item, &property.item)
309
-
.append1(
310
-
// Box it in a variant as required for property setting
311
-
MessageItem::Variant(Box::new(
312
-
to_message_item(value, valid_signature.as_ref().map(|s| &s[0]))?))
313
-
);
0
0
0
314
315
// Send it on the channel and get the response
316
-
self.conn.send_with_reply_and_block(message, self.config.timeout.item)
0
317
.map_err(|err| self.error(err, context))?;
318
319
Ok(())
320
}
321
322
-
pub fn list(&self, pattern: Option<&Pattern>)
323
-
-> Result<Vec<String>, LabeledError>
324
-
{
325
let context = "while listing D-Bus connection names";
326
327
let message = Message::new_method_call(
328
"org.freedesktop.DBus",
329
"/org/freedesktop/DBus",
330
"org.freedesktop.DBus",
331
-
"ListNames"
332
-
).map_err(|err| self.error(err, context))?;
0
333
334
-
self.conn.send_with_reply_and_block(message, self.config.timeout.item)
0
335
.map_err(|err| self.error(err, context))
336
.and_then(|reply| reply.read1().map_err(|err| self.error(err, context)))
337
.map(|names: Vec<String>| {
338
// Filter the names by the pattern
339
if let Some(pattern) = pattern {
340
-
names.into_iter().filter(|name| pattern.is_match(name)).collect()
0
0
0
341
} else {
342
names
343
}
···
1
+
use dbus::{
2
+
arg::messageitem::MessageItem,
3
+
channel::{BusType, Channel},
4
+
Message,
5
+
};
6
use nu_plugin::LabeledError;
7
use nu_protocol::{Spanned, Value};
8
9
+
use crate::{
10
+
config::{DbusBusChoice, DbusClientConfig},
11
+
convert::to_message_item,
12
+
dbus_type::DbusType,
13
+
introspection::Node,
14
+
pattern::Pattern,
15
+
};
16
17
/// Executes D-Bus actions on a connection, handling nushell types
18
pub struct DbusClient {
···
22
23
// Convenience macros for error handling
24
macro_rules! validate_with {
25
+
($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
+
})
31
+
};
32
}
33
34
impl DbusClient {
···
43
ch.register()?;
44
Ok(ch)
45
}),
46
+
}
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),
0
51
})?;
52
Ok(DbusClient {
53
config,
54
+
conn: channel,
55
})
56
}
57
···
59
LabeledError {
60
label: err.to_string(),
61
msg: msg.to_string(),
62
+
span: Some(self.config.span),
63
}
64
}
65
···
78
valid_dest,
79
valid_object,
80
"org.freedesktop.DBus.Introspectable",
81
+
"Introspect",
82
+
)
83
+
.map_err(|err| self.error(err, context))?;
84
85
// Send and get the response
86
+
let resp = self
87
+
.conn
88
+
.send_with_reply_and_block(message, self.config.timeout.item)
89
.map_err(|err| self.error(err, context))?;
90
91
// Parse it to a Node
92
+
let xml: &str = resp
93
+
.get1()
94
.ok_or_else(|| self.error("Introspect method returned the wrong type", context))?;
95
96
Node::from_xml(xml).map_err(|err| self.error(err, context))
···
108
109
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!(
112
+
"while getting interface {:?} method {:?} signature: {}",
113
+
interface.item, method.item, err
114
+
),
115
msg: "try running with --no-introspect or --signature".into(),
116
span: Some(self.config.span),
117
})
···
135
let node = self.introspect(dest, object)?;
136
137
if let Some(sig) = node.get_property_signature(&interface.item, &property.item) {
138
+
DbusType::parse_all(sig).map_err(|err| LabeledError {
139
+
label: format!(
140
+
"while getting interface {:?} property {:?} signature: {}",
141
+
interface.item, property.item, err
142
+
),
143
msg: "try running with --no-introspect or --signature".into(),
144
span: Some(self.config.span),
145
})
146
} 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
})
···
174
let valid_method = validate_with!(dbus::strings::Member, method)?;
175
176
// Parse the signature
177
+
let mut valid_signature = signature
178
+
.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),
183
+
})
184
+
})
185
+
.transpose()?;
186
187
// If not provided, try introspection (unless disabled)
188
if valid_signature.is_none() && self.config.introspect {
189
match self.get_method_signature_by_introspection(dest, object, interface, method) {
190
Ok(sig) => {
191
valid_signature = Some(sig);
192
+
}
193
Err(err) => {
194
+
eprintln!(
195
+
"Warning: D-Bus introspection failed on {:?}. \
196
Use `--no-introspect` or pass `--signature` to silence this warning. \
197
Cause: {}",
198
+
object.item, err.label
199
+
);
200
}
201
}
202
}
203
204
if let Some(sig) = &valid_signature {
205
if sig.len() != args.len() {
206
+
self.error(
207
+
format!("expected {} arguments, got {}", sig.len(), args.len()),
208
+
context,
209
+
);
210
}
211
}
212
213
// Construct the method call message
214
+
let mut message =
215
+
Message::new_method_call(valid_dest, valid_object, valid_interface, valid_method)
216
+
.map_err(|err| self.error(err, context))?;
0
0
0
217
218
// Convert the args to message items
219
+
let sigs_iter = valid_signature
220
+
.iter()
221
+
.flatten()
222
+
.map(Some)
223
+
.chain(std::iter::repeat(None));
224
for (val, sig) in args.iter().zip(sigs_iter) {
225
message = message.append1(to_message_item(val, sig)?);
226
}
227
228
// Send it on the channel and get the response
229
+
let resp = self
230
+
.conn
231
+
.send_with_reply_and_block(message, self.config.timeout.item)
232
.map_err(|err| self.error(err, context))?;
233
234
crate::convert::from_message(&resp, self.config.span)
···
249
self.call(
250
dest,
251
object,
252
+
&Spanned {
253
+
item: "org.freedesktop.DBus.Properties".into(),
254
+
span: self.config.span,
255
+
},
256
+
&Spanned {
257
+
item: "Get".into(),
258
+
span: self.config.span,
259
+
},
260
+
Some(&Spanned {
261
+
item: "ss".into(),
262
+
span: self.config.span,
263
+
}),
264
+
&[interface_val, property_val],
265
+
)
266
+
.map(|val| val.into_iter().nth(0).unwrap_or_default())
267
}
268
269
/// Get all D-Bus properties from the given object
···
278
self.call(
279
dest,
280
object,
281
+
&Spanned {
282
+
item: "org.freedesktop.DBus.Properties".into(),
283
+
span: self.config.span,
284
+
},
285
+
&Spanned {
286
+
item: "GetAll".into(),
287
+
span: self.config.span,
288
+
},
289
+
Some(&Spanned {
290
+
item: "s".into(),
291
+
span: self.config.span,
292
+
}),
293
+
&[interface_val],
294
+
)
295
+
.map(|val| val.into_iter().nth(0).unwrap_or_default())
296
}
297
298
/// Set a D-Bus property on the given object
···
312
let valid_object = validate_with!(dbus::strings::Path, object)?;
313
314
// Parse the signature
315
+
let mut valid_signature = signature
316
+
.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),
321
+
})
322
+
})
323
+
.transpose()?;
324
325
// If not provided, try introspection (unless disabled)
326
if valid_signature.is_none() && self.config.introspect {
327
match self.get_property_signature_by_introspection(dest, object, interface, property) {
328
Ok(sig) => {
329
valid_signature = Some(sig);
330
+
}
331
Err(err) => {
332
+
eprintln!(
333
+
"Warning: D-Bus introspection failed on {:?}. \
334
Use `--no-introspect` or pass `--signature` to silence this warning. \
335
Cause: {}",
336
+
object.item, err.label
337
+
);
338
}
339
}
340
}
341
342
if let Some(sig) = &valid_signature {
343
if sig.len() != 1 {
344
+
self.error(
345
+
format!(
346
+
"expected single object signature, but there are {}",
347
+
sig.len()
348
+
),
349
+
context,
350
+
);
351
}
352
}
353
···
357
valid_object,
358
"org.freedesktop.DBus.Properties",
359
"Set",
360
+
)
361
+
.map_err(|err| self.error(err, context))?
362
+
.append2(&interface.item, &property.item)
363
+
.append1(
364
+
// Box it in a variant as required for property setting
365
+
MessageItem::Variant(Box::new(to_message_item(
366
+
value,
367
+
valid_signature.as_ref().map(|s| &s[0]),
368
+
)?)),
369
+
);
370
371
// Send it on the channel and get the response
372
+
self.conn
373
+
.send_with_reply_and_block(message, self.config.timeout.item)
374
.map_err(|err| self.error(err, context))?;
375
376
Ok(())
377
}
378
379
+
pub fn list(&self, pattern: Option<&Pattern>) -> Result<Vec<String>, LabeledError> {
0
0
380
let context = "while listing D-Bus connection names";
381
382
let message = Message::new_method_call(
383
"org.freedesktop.DBus",
384
"/org/freedesktop/DBus",
385
"org.freedesktop.DBus",
386
+
"ListNames",
387
+
)
388
+
.map_err(|err| self.error(err, context))?;
389
390
+
self.conn
391
+
.send_with_reply_and_block(message, self.config.timeout.item)
392
.map_err(|err| self.error(err, context))
393
.and_then(|reply| reply.read1().map_err(|err| self.error(err, context)))
394
.map(|names: Vec<String>| {
395
// Filter the names by the pattern
396
if let Some(pattern) = pattern {
397
+
names
398
+
.into_iter()
399
+
.filter(|name| pattern.is_match(name))
400
+
.collect()
401
} else {
402
names
403
}
+33
-18
src/config.rs
···
1
use std::time::Duration;
2
3
use nu_plugin::{EvaluatedCall, LabeledError};
4
-
use nu_protocol::{Spanned, Span};
5
6
/// General configuration related to the D-Bus client connection
7
#[derive(Debug, Clone)]
···
37
fn try_from(call: &EvaluatedCall) -> Result<Self, Self::Error> {
38
let mut config = DbusClientConfig {
39
span: call.head,
40
-
bus_choice: Spanned { item: DbusBusChoice::default(), span: call.head },
41
-
timeout: Spanned { item: Duration::from_secs(2), span: call.head },
0
0
0
0
0
0
42
introspect: true,
43
};
44
···
51
"session" => DbusBusChoice::Session,
52
"system" => DbusBusChoice::System,
53
"started" => DbusBusChoice::Started,
54
-
_ => unreachable!()
55
};
56
-
config.bus_choice = Spanned { item: dest, span: name.span };
0
0
0
57
}
58
-
},
59
r#type @ ("bus" | "peer") => {
60
if let Some(value) = value {
61
let address = value.as_str()?;
62
let dest = match r#type {
63
"bus" => DbusBusChoice::Bus(address.to_owned()),
64
"peer" => DbusBusChoice::Peer(address.to_owned()),
65
-
_ => unreachable!()
0
0
0
0
66
};
67
-
config.bus_choice = Spanned { item: dest, span: value.span() };
68
}
69
-
},
70
"timeout" => {
71
if let Some(value) = value {
72
-
let nanos: u64 = value.as_duration()?.try_into().map_err(|_| {
73
-
LabeledError {
74
label: "Timeout must be a positive duration".into(),
75
msg: "invalid timeout specified here".into(),
76
span: Some(value.span()),
77
-
}
78
-
})?;
79
let item = Duration::from_nanos(nanos);
80
-
config.timeout = Spanned { item, span: value.span() };
0
0
0
81
}
82
-
},
83
"no-introspect" => {
84
-
config.introspect = !value.as_ref()
0
85
.and_then(|v| v.as_bool().ok())
86
.unwrap_or(false);
87
-
},
88
-
_ => ()
89
}
90
}
91
···
1
use std::time::Duration;
2
3
use nu_plugin::{EvaluatedCall, LabeledError};
4
+
use nu_protocol::{Span, Spanned};
5
6
/// General configuration related to the D-Bus client connection
7
#[derive(Debug, Clone)]
···
37
fn try_from(call: &EvaluatedCall) -> Result<Self, Self::Error> {
38
let mut config = DbusClientConfig {
39
span: call.head,
40
+
bus_choice: Spanned {
41
+
item: DbusBusChoice::default(),
42
+
span: call.head,
43
+
},
44
+
timeout: Spanned {
45
+
item: Duration::from_secs(2),
46
+
span: call.head,
47
+
},
48
introspect: true,
49
};
50
···
57
"session" => DbusBusChoice::Session,
58
"system" => DbusBusChoice::System,
59
"started" => DbusBusChoice::Started,
60
+
_ => unreachable!(),
61
};
62
+
config.bus_choice = Spanned {
63
+
item: dest,
64
+
span: name.span,
65
+
};
66
}
67
+
}
68
r#type @ ("bus" | "peer") => {
69
if let Some(value) = value {
70
let address = value.as_str()?;
71
let dest = match r#type {
72
"bus" => DbusBusChoice::Bus(address.to_owned()),
73
"peer" => DbusBusChoice::Peer(address.to_owned()),
74
+
_ => unreachable!(),
75
+
};
76
+
config.bus_choice = Spanned {
77
+
item: dest,
78
+
span: value.span(),
79
};
0
80
}
81
+
}
82
"timeout" => {
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
+
})?;
0
90
let item = Duration::from_nanos(nanos);
91
+
config.timeout = Spanned {
92
+
item,
93
+
span: value.span(),
94
+
};
95
}
96
+
}
97
"no-introspect" => {
98
+
config.introspect = !value
99
+
.as_ref()
100
.and_then(|v| v.as_bool().ok())
101
.unwrap_or(false);
102
+
}
103
+
_ => (),
104
}
105
}
106
+175
-118
src/convert.rs
···
1
-
use dbus::{Message, arg::{ArgType, RefArg, messageitem::{MessageItemArray, MessageItem, MessageItemDict}}, Signature};
0
0
0
0
0
0
2
use nu_plugin::LabeledError;
3
-
use nu_protocol::{Value, Span, Record};
4
use std::str::FromStr;
5
6
use crate::dbus_type::DbusType;
···
31
Value::record(record, span)
32
} else if &*refarg.signature() == "ay" {
33
// Byte array - better to return as binary
34
-
let bytes = dbus::arg::cast::<Vec<u8>>(&refarg.box_clone()).unwrap().to_owned();
0
0
35
Value::binary(bytes, span)
36
} else {
37
// It's an array
38
Value::list(
39
-
refarg.as_iter().unwrap().map(|v| from_refarg(v, span)).flatten().collect(),
40
-
span)
0
0
0
0
0
41
}
42
-
},
43
ArgType::Variant => {
44
-
let inner = refarg.as_iter().unwrap().nth(0).unwrap();
45
return from_refarg(inner, span);
46
-
},
47
-
ArgType::Boolean =>
48
-
Value::bool(refarg.as_i64().unwrap() != 0, span),
49
50
// Strings
51
-
ArgType::String | ArgType::ObjectPath | ArgType::Signature =>
52
-
Value::string(refarg.as_str().unwrap(), span),
0
53
// Ints
54
-
ArgType::Byte | ArgType::Int16 | ArgType::UInt16 | ArgType::Int32 |
55
-
ArgType::UInt32 | ArgType::Int64 | ArgType::UnixFd =>
56
-
Value::int(refarg.as_i64().unwrap(), span),
0
0
0
0
57
58
// Nushell doesn't support u64, so present it as a string
59
ArgType::UInt64 => Value::string(refarg.as_u64().unwrap().to_string(), span),
60
61
// Floats
62
-
ArgType::Double =>
63
-
Value::float(refarg.as_f64().unwrap(), span),
64
65
-
ArgType::Struct =>
66
-
Value::list(
67
-
refarg.as_iter().unwrap().map(|v| from_refarg(v, span)).flatten().collect(),
68
-
span),
0
0
0
0
69
70
-
ArgType::DictEntry =>
71
-
return Err("Encountered dictionary entry outside of dictionary".into()),
72
-
ArgType::Invalid =>
73
-
return Err("Encountered invalid D-Bus value".into()),
74
})
75
}
76
77
-
pub fn to_message_item(value: &Value, expected_type: Option<&DbusType>)
78
-
-> Result<MessageItem, LabeledError>
79
-
{
0
80
// Report errors from conversion. Error must support Display
81
macro_rules! try_convert {
82
-
($result_expr:expr) => ($result_expr.map_err(|err| LabeledError {
83
-
label: format!("Failed to convert value to the D-Bus `{:?}` type",
84
-
expected_type.unwrap()),
85
-
msg: err.to_string(),
86
-
span: Some(value.span()),
87
-
})?)
0
0
0
0
88
}
89
90
// Try to match values to expected types
91
match (value, expected_type) {
92
// Boolean
93
-
(Value::Bool { val, .. }, Some(DbusType::Boolean)) =>
94
-
Ok(MessageItem::Bool(*val)),
95
96
// Strings and specialized strings
97
-
(Value::String { val, .. }, Some(DbusType::String)) =>
98
-
Ok(MessageItem::Str(val.to_owned())),
99
-
(Value::String { val, .. }, Some(DbusType::ObjectPath)) =>
100
-
Ok(MessageItem::ObjectPath(try_convert!(dbus::strings::Path::new(val)))),
101
-
(Value::String { val, .. }, Some(DbusType::Signature)) =>
102
-
Ok(MessageItem::Signature(try_convert!(dbus::strings::Signature::new(val)))),
0
103
104
// Signed ints
105
-
(Value::Int { val, .. }, Some(DbusType::Int64)) =>
106
-
Ok(MessageItem::Int64(*val)),
107
-
(Value::Int { val, .. }, Some(DbusType::Int32)) =>
108
-
Ok(MessageItem::Int32(try_convert!(i32::try_from(*val)))),
109
-
(Value::Int { val, .. }, Some(DbusType::Int16)) =>
110
-
Ok(MessageItem::Int16(try_convert!(i16::try_from(*val)))),
0
111
112
// Unsigned ints
113
-
(Value::Int { val, .. }, Some(DbusType::UInt64)) =>
114
-
Ok(MessageItem::UInt64(try_convert!(u64::try_from(*val)))),
115
-
(Value::Int { val, .. }, Some(DbusType::UInt32)) =>
116
-
Ok(MessageItem::UInt32(try_convert!(u32::try_from(*val)))),
117
-
(Value::Int { val, .. }, Some(DbusType::UInt16)) =>
118
-
Ok(MessageItem::UInt16(try_convert!(u16::try_from(*val)))),
119
-
(Value::Int { val, .. }, Some(DbusType::Byte)) =>
120
-
Ok(MessageItem::Byte(try_convert!(u8::try_from(*val)))),
0
0
0
0
121
122
// Ints from string
123
-
(Value::String { val, .. }, Some(DbusType::Int64)) =>
124
-
Ok(MessageItem::Int64(try_convert!(i64::from_str(&val[..])))),
125
-
(Value::String { val, .. }, Some(DbusType::Int32)) =>
126
-
Ok(MessageItem::Int32(try_convert!(i32::from_str(&val[..])))),
127
-
(Value::String { val, .. }, Some(DbusType::Int16)) =>
128
-
Ok(MessageItem::Int16(try_convert!(i16::from_str(&val[..])))),
129
-
(Value::String { val, .. }, Some(DbusType::UInt64)) =>
130
-
Ok(MessageItem::UInt64(try_convert!(u64::from_str(&val[..])))),
131
-
(Value::String { val, .. }, Some(DbusType::UInt32)) =>
132
-
Ok(MessageItem::UInt32(try_convert!(u32::from_str(&val[..])))),
133
-
(Value::String { val, .. }, Some(DbusType::UInt16)) =>
134
-
Ok(MessageItem::UInt16(try_convert!(u16::from_str(&val[..])))),
135
-
(Value::String { val, .. }, Some(DbusType::Byte)) =>
136
-
Ok(MessageItem::Byte(try_convert!(u8::from_str(&val[..])))),
0
0
0
0
0
0
0
137
138
// Float
139
-
(Value::Float { val, .. }, Some(DbusType::Double)) =>
140
-
Ok(MessageItem::Double(*val)),
141
-
(Value::String { val, .. }, Some(DbusType::Double)) =>
142
-
Ok(MessageItem::Double(try_convert!(f64::from_str(&val[..])))),
143
144
// Binary
145
(Value::Binary { val, .. }, Some(r#type @ DbusType::Array(content_type)))
···
147
{
148
// FIXME: this is likely pretty inefficient for a bunch of bytes
149
let sig = Signature::from(r#type.stringify());
150
-
let items = val.iter().cloned().map(MessageItem::Byte).collect::<Vec<_>>();
151
-
Ok(MessageItem::Array(MessageItemArray::new(items, sig).unwrap()))
152
-
},
0
0
0
0
0
0
153
154
// List/array
155
(Value::List { vals, .. }, Some(r#type @ DbusType::Array(content_type))) => {
156
let sig = Signature::from(r#type.stringify());
157
-
let items = vals.iter()
0
158
.map(|content| to_message_item(content, Some(content_type)))
159
.collect::<Result<Vec<MessageItem>, _>>()?;
160
-
Ok(MessageItem::Array(MessageItemArray::new(items, sig).unwrap()))
161
-
},
0
0
162
163
// Struct
164
(Value::List { vals, .. }, Some(DbusType::Struct(types))) => {
165
if vals.len() != types.len() {
166
return Err(LabeledError {
167
-
label: format!("expected struct with {} element(s) ({:?})", types.len(), types),
0
0
0
0
168
msg: format!("this list has {} element(s) instead", vals.len()),
169
-
span: Some(value.span())
170
});
171
}
172
-
let items = vals.iter().zip(types)
0
0
173
.map(|(content, r#type)| to_message_item(content, Some(r#type)))
174
.collect::<Result<Vec<MessageItem>, _>>()?;
175
Ok(MessageItem::Struct(items))
176
-
},
177
178
// Record/dict
179
(Value::Record { val, .. }, Some(DbusType::Array(content_type)))
···
182
if let DbusType::DictEntry(ref key_type, ref val_type) = **content_type {
183
let key_sig = Signature::from(key_type.stringify());
184
let val_sig = Signature::from(val_type.stringify());
185
-
let pairs = val.iter()
0
186
.map(|(key, val)| {
187
let key_as_value = Value::string(key, value.span());
188
let key_message_item = to_message_item(&key_as_value, Some(key_type))?;
···
190
Ok((key_message_item, val_message_item))
191
})
192
.collect::<Result<Vec<_>, LabeledError>>()?;
193
-
Ok(MessageItem::Dict(MessageItemDict::new(pairs, key_sig, val_sig).unwrap()))
0
0
194
} else {
195
unreachable!()
196
}
197
-
},
198
199
// Variant - use automatic type
200
-
(other_value, Some(DbusType::Variant)) =>
201
-
Ok(MessageItem::Variant(Box::new(to_message_item(other_value, None)?))),
0
202
203
// Value not compatible with expected type
204
-
(other_value, Some(expectation)) =>
205
-
Err(LabeledError {
206
-
label: format!("`{}` can not be converted to the D-Bus `{:?}` type",
207
-
other_value.get_type(), expectation),
208
-
msg: format!("expected a `{:?}` here", expectation),
209
-
span: Some(other_value.span()),
210
-
}),
0
0
211
212
// Automatic types (with no type expectation)
213
-
(Value::String { .. }, None) =>
214
-
to_message_item(value, Some(&DbusType::String)),
215
-
(Value::Int { .. }, None) =>
216
-
to_message_item(value, Some(&DbusType::Int64)),
217
-
(Value::Float { .. }, None) =>
218
-
to_message_item(value, Some(&DbusType::Double)),
219
-
(Value::Bool { .. }, None) =>
220
-
to_message_item(value, Some(&DbusType::Boolean)),
221
-
(Value::List { .. }, None) =>
222
-
to_message_item(value, Some(&DbusType::Array(DbusType::Variant.into()))),
223
-
(Value::Record { .. }, None) =>
224
-
to_message_item(value, Some(&DbusType::Array(
225
-
DbusType::DictEntry(
226
-
DbusType::String.into(),
227
-
DbusType::Variant.into()
228
-
).into()))),
229
230
// No expected type, but can't handle this type
231
-
_ =>
232
-
Err(LabeledError {
233
-
label: format!("can not use values of type `{}` in D-Bus calls", value.get_type()),
234
-
msg: "use a supported type here instead".into(),
235
-
span: Some(value.span()),
236
-
})
0
0
237
}
238
}
···
1
+
use dbus::{
2
+
arg::{
3
+
messageitem::{MessageItem, MessageItemArray, MessageItemDict},
4
+
ArgType, RefArg,
5
+
},
6
+
Message, Signature,
7
+
};
8
use nu_plugin::LabeledError;
9
+
use nu_protocol::{Record, Span, Value};
10
use std::str::FromStr;
11
12
use crate::dbus_type::DbusType;
···
37
Value::record(record, span)
38
} else if &*refarg.signature() == "ay" {
39
// Byte array - better to return as binary
40
+
let bytes = dbus::arg::cast::<Vec<u8>>(&refarg.box_clone())
41
+
.unwrap()
42
+
.to_owned();
43
Value::binary(bytes, span)
44
} else {
45
// It's an array
46
Value::list(
47
+
refarg
48
+
.as_iter()
49
+
.unwrap()
50
+
.flat_map(|v| from_refarg(v, span))
51
+
.collect(),
52
+
span,
53
+
)
54
}
55
+
}
56
ArgType::Variant => {
57
+
let inner = refarg.as_iter().unwrap().next().unwrap();
58
return from_refarg(inner, span);
59
+
}
60
+
ArgType::Boolean => Value::bool(refarg.as_i64().unwrap() != 0, span),
0
61
62
// Strings
63
+
ArgType::String | ArgType::ObjectPath | ArgType::Signature => {
64
+
Value::string(refarg.as_str().unwrap(), span)
65
+
}
66
// Ints
67
+
ArgType::Byte
68
+
| ArgType::Int16
69
+
| ArgType::UInt16
70
+
| ArgType::Int32
71
+
| ArgType::UInt32
72
+
| ArgType::Int64
73
+
| ArgType::UnixFd => Value::int(refarg.as_i64().unwrap(), span),
74
75
// Nushell doesn't support u64, so present it as a string
76
ArgType::UInt64 => Value::string(refarg.as_u64().unwrap().to_string(), span),
77
78
// Floats
79
+
ArgType::Double => Value::float(refarg.as_f64().unwrap(), span),
0
80
81
+
ArgType::Struct => Value::list(
82
+
refarg
83
+
.as_iter()
84
+
.unwrap()
85
+
.flat_map(|v| from_refarg(v, span))
86
+
.collect(),
87
+
span,
88
+
),
89
90
+
ArgType::DictEntry => {
91
+
return Err("Encountered dictionary entry outside of dictionary".into())
92
+
}
93
+
ArgType::Invalid => return Err("Encountered invalid D-Bus value".into()),
94
})
95
}
96
97
+
pub fn to_message_item(
98
+
value: &Value,
99
+
expected_type: Option<&DbusType>,
100
+
) -> Result<MessageItem, LabeledError> {
101
// Report errors from conversion. Error must support Display
102
macro_rules! try_convert {
103
+
($result_expr:expr) => {
104
+
$result_expr.map_err(|err| LabeledError {
105
+
label: format!(
106
+
"Failed to convert value to the D-Bus `{:?}` type",
107
+
expected_type.unwrap()
108
+
),
109
+
msg: err.to_string(),
110
+
span: Some(value.span()),
111
+
})?
112
+
};
113
}
114
115
// Try to match values to expected types
116
match (value, expected_type) {
117
// Boolean
118
+
(Value::Bool { val, .. }, Some(DbusType::Boolean)) => Ok(MessageItem::Bool(*val)),
0
119
120
// Strings and specialized strings
121
+
(Value::String { val, .. }, Some(DbusType::String)) => Ok(MessageItem::Str(val.to_owned())),
122
+
(Value::String { val, .. }, Some(DbusType::ObjectPath)) => Ok(MessageItem::ObjectPath(
123
+
try_convert!(dbus::strings::Path::new(val)),
124
+
)),
125
+
(Value::String { val, .. }, Some(DbusType::Signature)) => Ok(MessageItem::Signature(
126
+
try_convert!(dbus::strings::Signature::new(val)),
127
+
)),
128
129
// Signed ints
130
+
(Value::Int { val, .. }, Some(DbusType::Int64)) => Ok(MessageItem::Int64(*val)),
131
+
(Value::Int { val, .. }, Some(DbusType::Int32)) => {
132
+
Ok(MessageItem::Int32(try_convert!(i32::try_from(*val))))
133
+
}
134
+
(Value::Int { val, .. }, Some(DbusType::Int16)) => {
135
+
Ok(MessageItem::Int16(try_convert!(i16::try_from(*val))))
136
+
}
137
138
// Unsigned ints
139
+
(Value::Int { val, .. }, Some(DbusType::UInt64)) => {
140
+
Ok(MessageItem::UInt64(try_convert!(u64::try_from(*val))))
141
+
}
142
+
(Value::Int { val, .. }, Some(DbusType::UInt32)) => {
143
+
Ok(MessageItem::UInt32(try_convert!(u32::try_from(*val))))
144
+
}
145
+
(Value::Int { val, .. }, Some(DbusType::UInt16)) => {
146
+
Ok(MessageItem::UInt16(try_convert!(u16::try_from(*val))))
147
+
}
148
+
(Value::Int { val, .. }, Some(DbusType::Byte)) => {
149
+
Ok(MessageItem::Byte(try_convert!(u8::try_from(*val))))
150
+
}
151
152
// Ints from string
153
+
(Value::String { val, .. }, Some(DbusType::Int64)) => {
154
+
Ok(MessageItem::Int64(try_convert!(i64::from_str(&val[..]))))
155
+
}
156
+
(Value::String { val, .. }, Some(DbusType::Int32)) => {
157
+
Ok(MessageItem::Int32(try_convert!(i32::from_str(&val[..]))))
158
+
}
159
+
(Value::String { val, .. }, Some(DbusType::Int16)) => {
160
+
Ok(MessageItem::Int16(try_convert!(i16::from_str(&val[..]))))
161
+
}
162
+
(Value::String { val, .. }, Some(DbusType::UInt64)) => {
163
+
Ok(MessageItem::UInt64(try_convert!(u64::from_str(&val[..]))))
164
+
}
165
+
(Value::String { val, .. }, Some(DbusType::UInt32)) => {
166
+
Ok(MessageItem::UInt32(try_convert!(u32::from_str(&val[..]))))
167
+
}
168
+
(Value::String { val, .. }, Some(DbusType::UInt16)) => {
169
+
Ok(MessageItem::UInt16(try_convert!(u16::from_str(&val[..]))))
170
+
}
171
+
(Value::String { val, .. }, Some(DbusType::Byte)) => {
172
+
Ok(MessageItem::Byte(try_convert!(u8::from_str(&val[..]))))
173
+
}
174
175
// Float
176
+
(Value::Float { val, .. }, Some(DbusType::Double)) => Ok(MessageItem::Double(*val)),
177
+
(Value::String { val, .. }, Some(DbusType::Double)) => {
178
+
Ok(MessageItem::Double(try_convert!(f64::from_str(&val[..]))))
179
+
}
180
181
// Binary
182
(Value::Binary { val, .. }, Some(r#type @ DbusType::Array(content_type)))
···
184
{
185
// FIXME: this is likely pretty inefficient for a bunch of bytes
186
let sig = Signature::from(r#type.stringify());
187
+
let items = val
188
+
.iter()
189
+
.cloned()
190
+
.map(MessageItem::Byte)
191
+
.collect::<Vec<_>>();
192
+
Ok(MessageItem::Array(
193
+
MessageItemArray::new(items, sig).unwrap(),
194
+
))
195
+
}
196
197
// List/array
198
(Value::List { vals, .. }, Some(r#type @ DbusType::Array(content_type))) => {
199
let sig = Signature::from(r#type.stringify());
200
+
let items = vals
201
+
.iter()
202
.map(|content| to_message_item(content, Some(content_type)))
203
.collect::<Result<Vec<MessageItem>, _>>()?;
204
+
Ok(MessageItem::Array(
205
+
MessageItemArray::new(items, sig).unwrap(),
206
+
))
207
+
}
208
209
// Struct
210
(Value::List { vals, .. }, Some(DbusType::Struct(types))) => {
211
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
});
221
}
222
+
let items = vals
223
+
.iter()
224
+
.zip(types)
225
.map(|(content, r#type)| to_message_item(content, Some(r#type)))
226
.collect::<Result<Vec<MessageItem>, _>>()?;
227
Ok(MessageItem::Struct(items))
228
+
}
229
230
// Record/dict
231
(Value::Record { val, .. }, Some(DbusType::Array(content_type)))
···
234
if let DbusType::DictEntry(ref key_type, ref val_type) = **content_type {
235
let key_sig = Signature::from(key_type.stringify());
236
let val_sig = Signature::from(val_type.stringify());
237
+
let pairs = val
238
+
.iter()
239
.map(|(key, val)| {
240
let key_as_value = Value::string(key, value.span());
241
let key_message_item = to_message_item(&key_as_value, Some(key_type))?;
···
243
Ok((key_message_item, val_message_item))
244
})
245
.collect::<Result<Vec<_>, LabeledError>>()?;
246
+
Ok(MessageItem::Dict(
247
+
MessageItemDict::new(pairs, key_sig, val_sig).unwrap(),
248
+
))
249
} else {
250
unreachable!()
251
}
252
+
}
253
254
// Variant - use automatic type
255
+
(other_value, Some(DbusType::Variant)) => Ok(MessageItem::Variant(Box::new(
256
+
to_message_item(other_value, None)?,
257
+
))),
258
259
// 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
+
}),
269
270
// Automatic types (with no type expectation)
271
+
(Value::String { .. }, None) => to_message_item(value, Some(&DbusType::String)),
272
+
(Value::Int { .. }, None) => to_message_item(value, Some(&DbusType::Int64)),
273
+
(Value::Float { .. }, None) => to_message_item(value, Some(&DbusType::Double)),
274
+
(Value::Bool { .. }, None) => to_message_item(value, Some(&DbusType::Boolean)),
275
+
(Value::List { .. }, None) => {
276
+
to_message_item(value, Some(&DbusType::Array(DbusType::Variant.into())))
277
+
}
278
+
(Value::Record { .. }, None) => to_message_item(
279
+
value,
280
+
Some(&DbusType::Array(
281
+
DbusType::DictEntry(DbusType::String.into(), DbusType::Variant.into()).into(),
282
+
)),
283
+
),
0
0
0
284
285
// 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
+
}),
294
}
295
}
+72
-60
src/dbus_type.rs
···
48
// The next type is the content type of the array
49
let (content_type, remainder) = Self::parse(&input[1..])?;
50
Ok((Array(content_type.into()), remainder))
51
-
},
52
'(' => {
53
// Parse the struct content until we get to the end ) char
54
let mut remainder = &input[1..];
···
56
loop {
57
if remainder.is_empty() {
58
break Err("unexpected end of D-Bus type string \
59
-
before end of array".into());
60
-
} else if remainder.starts_with(')') {
61
-
break Ok((DbusType::Struct(types), &remainder[1..]));
0
62
} else {
63
let (r#type, new_remainder) = Self::parse(remainder)?;
64
types.push(r#type);
65
remainder = new_remainder;
66
}
67
}
68
-
},
69
'v' => Ok((Variant, &input[1..])),
70
'{' => {
71
// Expect two types
72
let (key_type, key_remainder) = Self::parse(&input[1..])?;
73
let (val_type, val_remainder) = Self::parse(key_remainder)?;
74
// Must end with }
75
-
if val_remainder.starts_with('}') {
76
-
Ok((DbusType::DictEntry(key_type.into(), val_type.into()), &val_remainder[1..]))
0
0
0
77
} else {
78
-
Err(format!("expected `}}` char to end dictionary in D-Bus type \
79
-
but remainder is {:?}", val_remainder))
0
0
0
80
}
81
-
},
82
-
other => Err(format!("unexpected char {other:?} in D-Bus type representation"))
0
0
83
}
84
}
85
···
99
use self::DbusType::*;
100
101
match self {
102
-
Byte => 'y'.into(),
103
-
Boolean => 'b'.into(),
104
-
Int16 => 'n'.into(),
105
-
UInt16 => 'q'.into(),
106
-
Int32 => 'i'.into(),
107
-
UInt32 => 'u'.into(),
108
-
Int64 => 'x'.into(),
109
-
UInt64 => 't'.into(),
110
-
Double => 'd'.into(),
111
-
String => 's'.into(),
112
ObjectPath => 'o'.into(),
113
-
Signature => 'g'.into(),
114
115
// a<type>
116
Array(content) => format!("a{}", content.stringify()),
···
131
132
#[cfg(test)]
133
macro_rules! should_parse_to {
134
-
($str:expr, $result:expr) => (
135
assert_eq!(DbusType::parse($str), Ok(($result, "")))
136
-
)
137
}
138
139
#[test]
···
204
use self::DbusType::*;
205
should_parse_to!("((xx))", Struct(vec![Struct(vec![Int64, Int64])]));
206
should_parse_to!("(y(xx))", Struct(vec![Byte, Struct(vec![Int64, Int64])]));
207
-
should_parse_to!("(y(ss)o)", Struct(vec![Byte, Struct(vec![String, String]), ObjectPath]));
0
0
0
208
should_parse_to!("((yy)s)", Struct(vec![Struct(vec![Byte, Byte]), String]));
209
}
210
···
224
fn test_parse_dict_entry() {
225
use self::DbusType::*;
226
should_parse_to!("{ss}", DictEntry(String.into(), String.into()));
227
-
should_parse_to!("{s(bd)}", DictEntry(String.into(), Struct(vec![Boolean, Double]).into()));
0
0
0
228
}
229
230
#[test]
231
fn test_parse_array_dict() {
232
use self::DbusType::*;
233
-
should_parse_to!("a{sd}", Array(DictEntry(String.into(), Double.into()).into()));
0
0
0
234
}
235
236
#[test]
···
249
fn test_parse_all() {
250
use self::DbusType::*;
251
assert_eq!(DbusType::parse_all(""), Ok(vec![]));
252
-
assert_eq!(
253
-
DbusType::parse_all("s"),
254
-
Ok(vec![
255
-
String,
256
-
])
257
-
);
258
assert_eq!(
259
DbusType::parse_all("isbb"),
260
-
Ok(vec![
261
-
Int32,
262
-
String,
263
-
Boolean,
264
-
Boolean,
265
-
])
266
);
267
assert_eq!(
268
DbusType::parse_all("ia{s(bi)}s"),
···
276
277
#[cfg(test)]
278
macro_rules! should_stringify_to {
279
-
($type:expr, $result:expr) => (
280
assert_eq!(DbusType::stringify(&$type), $result)
281
-
)
282
}
283
284
#[test]
285
fn test_stringify_simple_types() {
286
use self::DbusType::*;
287
-
should_stringify_to!(Byte, "y");
288
-
should_stringify_to!(Boolean, "b");
289
-
should_stringify_to!(Int16, "n");
290
-
should_stringify_to!(UInt16, "q");
291
-
should_stringify_to!(Int32, "i");
292
-
should_stringify_to!(UInt32, "u");
293
-
should_stringify_to!(Int64, "x");
294
-
should_stringify_to!(UInt64, "t");
295
-
should_stringify_to!(Double, "d");
296
-
should_stringify_to!(String, "s");
297
should_stringify_to!(ObjectPath, "o");
298
-
should_stringify_to!(Signature, "g");
299
-
should_stringify_to!(Variant, "v");
300
}
301
302
#[test]
···
313
should_stringify_to!(Struct(vec![Int32]), "(i)");
314
should_stringify_to!(Struct(vec![Int32, String]), "(is)");
315
should_stringify_to!(Struct(vec![Byte, Int32, String]), "(yis)");
316
-
should_stringify_to!(Struct(vec![Byte, Struct(vec![String, Boolean]), String]), "(y(sb)s)");
0
0
0
317
}
318
319
#[test]
···
326
#[test]
327
fn test_stringify_nested() {
328
use self::DbusType::*;
329
-
should_stringify_to!(Array(DictEntry(String.into(), Int32.into()).into()), "a{si}");
0
0
0
330
should_stringify_to!(
331
Array(
332
DictEntry(
333
String.into(),
334
-
Struct(vec![
335
-
Byte,
336
-
Array(Int32.into())
337
-
]).into()
338
-
).into()
339
),
340
"a{s(yai)}"
341
);
···
48
// The next type is the content type of the array
49
let (content_type, remainder) = Self::parse(&input[1..])?;
50
Ok((Array(content_type.into()), remainder))
51
+
}
52
'(' => {
53
// Parse the struct content until we get to the end ) char
54
let mut remainder = &input[1..];
···
56
loop {
57
if remainder.is_empty() {
58
break Err("unexpected end of D-Bus type string \
59
+
before end of array"
60
+
.into());
61
+
} else if let Some(new_remainder) = remainder.strip_prefix(')') {
62
+
break Ok((DbusType::Struct(types), new_remainder));
63
} else {
64
let (r#type, new_remainder) = Self::parse(remainder)?;
65
types.push(r#type);
66
remainder = new_remainder;
67
}
68
}
69
+
}
70
'v' => Ok((Variant, &input[1..])),
71
'{' => {
72
// Expect two types
73
let (key_type, key_remainder) = Self::parse(&input[1..])?;
74
let (val_type, val_remainder) = Self::parse(key_remainder)?;
75
// Must end with }
76
+
if let Some(new_remainder) = val_remainder.strip_prefix('}') {
77
+
Ok((
78
+
DbusType::DictEntry(key_type.into(), val_type.into()),
79
+
new_remainder,
80
+
))
81
} else {
82
+
Err(format!(
83
+
"expected `}}` char to end dictionary in D-Bus type \
84
+
but remainder is {:?}",
85
+
val_remainder
86
+
))
87
}
88
+
}
89
+
other => Err(format!(
90
+
"unexpected char {other:?} in D-Bus type representation"
91
+
)),
92
}
93
}
94
···
108
use self::DbusType::*;
109
110
match self {
111
+
Byte => 'y'.into(),
112
+
Boolean => 'b'.into(),
113
+
Int16 => 'n'.into(),
114
+
UInt16 => 'q'.into(),
115
+
Int32 => 'i'.into(),
116
+
UInt32 => 'u'.into(),
117
+
Int64 => 'x'.into(),
118
+
UInt64 => 't'.into(),
119
+
Double => 'd'.into(),
120
+
String => 's'.into(),
121
ObjectPath => 'o'.into(),
122
+
Signature => 'g'.into(),
123
124
// a<type>
125
Array(content) => format!("a{}", content.stringify()),
···
140
141
#[cfg(test)]
142
macro_rules! should_parse_to {
143
+
($str:expr, $result:expr) => {
144
assert_eq!(DbusType::parse($str), Ok(($result, "")))
145
+
};
146
}
147
148
#[test]
···
213
use self::DbusType::*;
214
should_parse_to!("((xx))", Struct(vec![Struct(vec![Int64, Int64])]));
215
should_parse_to!("(y(xx))", Struct(vec![Byte, Struct(vec![Int64, Int64])]));
216
+
should_parse_to!(
217
+
"(y(ss)o)",
218
+
Struct(vec![Byte, Struct(vec![String, String]), ObjectPath])
219
+
);
220
should_parse_to!("((yy)s)", Struct(vec![Struct(vec![Byte, Byte]), String]));
221
}
222
···
236
fn test_parse_dict_entry() {
237
use self::DbusType::*;
238
should_parse_to!("{ss}", DictEntry(String.into(), String.into()));
239
+
should_parse_to!(
240
+
"{s(bd)}",
241
+
DictEntry(String.into(), Struct(vec![Boolean, Double]).into())
242
+
);
243
}
244
245
#[test]
246
fn test_parse_array_dict() {
247
use self::DbusType::*;
248
+
should_parse_to!(
249
+
"a{sd}",
250
+
Array(DictEntry(String.into(), Double.into()).into())
251
+
);
252
}
253
254
#[test]
···
267
fn test_parse_all() {
268
use self::DbusType::*;
269
assert_eq!(DbusType::parse_all(""), Ok(vec![]));
270
+
assert_eq!(DbusType::parse_all("s"), Ok(vec![String,]));
0
0
0
0
0
271
assert_eq!(
272
DbusType::parse_all("isbb"),
273
+
Ok(vec![Int32, String, Boolean, Boolean,])
0
0
0
0
0
274
);
275
assert_eq!(
276
DbusType::parse_all("ia{s(bi)}s"),
···
284
285
#[cfg(test)]
286
macro_rules! should_stringify_to {
287
+
($type:expr, $result:expr) => {
288
assert_eq!(DbusType::stringify(&$type), $result)
289
+
};
290
}
291
292
#[test]
293
fn test_stringify_simple_types() {
294
use self::DbusType::*;
295
+
should_stringify_to!(Byte, "y");
296
+
should_stringify_to!(Boolean, "b");
297
+
should_stringify_to!(Int16, "n");
298
+
should_stringify_to!(UInt16, "q");
299
+
should_stringify_to!(Int32, "i");
300
+
should_stringify_to!(UInt32, "u");
301
+
should_stringify_to!(Int64, "x");
302
+
should_stringify_to!(UInt64, "t");
303
+
should_stringify_to!(Double, "d");
304
+
should_stringify_to!(String, "s");
305
should_stringify_to!(ObjectPath, "o");
306
+
should_stringify_to!(Signature, "g");
307
+
should_stringify_to!(Variant, "v");
308
}
309
310
#[test]
···
321
should_stringify_to!(Struct(vec![Int32]), "(i)");
322
should_stringify_to!(Struct(vec![Int32, String]), "(is)");
323
should_stringify_to!(Struct(vec![Byte, Int32, String]), "(yis)");
324
+
should_stringify_to!(
325
+
Struct(vec![Byte, Struct(vec![String, Boolean]), String]),
326
+
"(y(sb)s)"
327
+
);
328
}
329
330
#[test]
···
337
#[test]
338
fn test_stringify_nested() {
339
use self::DbusType::*;
340
+
should_stringify_to!(
341
+
Array(DictEntry(String.into(), Int32.into()).into()),
342
+
"a{si}"
343
+
);
344
should_stringify_to!(
345
Array(
346
DictEntry(
347
String.into(),
348
+
Struct(vec![Byte, Array(Int32.into())]).into()
349
+
)
350
+
.into()
0
0
351
),
352
"a{s(yai)}"
353
);
+108
-77
src/introspection.rs
···
1
-
use nu_protocol::{Value, record, Span};
2
use serde::Deserialize;
3
4
macro_rules! list_to_value {
5
-
($list:expr, $span:expr) => (
6
Value::list($list.iter().map(|i| i.to_value($span)).collect(), $span)
7
-
)
8
}
9
10
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)]
···
41
/// Find a method on an interface on this node, and then generate the signature of the method
42
/// args
43
pub fn get_method_args_signature(&self, interface: &str, method: &str) -> Option<String> {
44
-
Some(self.get_interface(interface)?.get_method(method)?.in_signature())
0
0
0
0
45
}
46
47
/// Find the signature of a property on an interface on this node
48
pub fn get_property_signature(&self, interface: &str, property: &str) -> Option<&str> {
49
-
Some(&self.get_interface(interface)?.get_property(property)?.r#type)
0
0
0
0
0
50
}
51
52
/// Represent the node as a nushell [Value]
53
pub fn to_value(&self, span: Span) -> Value {
54
-
Value::record(record!{
55
-
"name" => self.name.as_ref().map(|s| Value::string(s, span)).unwrap_or_default(),
56
-
"interfaces" => list_to_value!(self.interfaces, span),
57
-
"children" => list_to_value!(self.children, span),
58
-
}, span)
0
0
0
59
}
60
}
61
···
89
90
/// Represent the interface as a nushell [Value]
91
pub fn to_value(&self, span: Span) -> Value {
92
-
Value::record(record!{
93
-
"name" => Value::string(&self.name, span),
94
-
"methods" => list_to_value!(self.methods, span),
95
-
"signals" => list_to_value!(self.signals, span),
96
-
"properties" => list_to_value!(self.properties, span),
97
-
"signals" => list_to_value!(self.signals, span),
98
-
}, span)
0
0
0
99
}
100
}
101
···
112
impl Method {
113
/// Get the signature of the method args
114
pub fn in_signature(&self) -> String {
115
-
self.args.iter()
0
116
.filter(|arg| arg.direction == Direction::In)
117
.map(|arg| &arg.r#type[..])
118
.collect()
···
121
#[allow(dead_code)]
122
/// Get the signature of the method result
123
pub fn out_signature(&self) -> String {
124
-
self.args.iter()
0
125
.filter(|arg| arg.direction == Direction::Out)
126
.map(|arg| &arg.r#type[..])
127
.collect()
···
129
130
/// Represent the method as a nushell [Value]
131
pub fn to_value(&self, span: Span) -> Value {
132
-
Value::record(record!{
133
-
"name" => Value::string(&self.name, span),
134
-
"args" => list_to_value!(self.args, span),
135
-
"annotations" => list_to_value!(self.annotations, span),
136
-
}, span)
0
0
0
137
}
138
}
139
···
152
pub fn new(
153
name: impl Into<String>,
154
r#type: impl Into<String>,
155
-
direction: Direction
156
) -> MethodArg {
157
MethodArg {
158
name: Some(name.into()),
···
163
164
/// Represent the method as a nushell [Value]
165
pub fn to_value(&self, span: Span) -> Value {
166
-
Value::record(record!{
167
-
"name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(),
168
-
"type" => Value::string(&self.r#type, span),
169
-
"direction" => self.direction.to_value(span),
170
-
}, span)
0
0
0
171
}
172
}
173
···
181
182
impl Direction {
183
/// Represent the direction as a nushell [Value]
184
-
pub fn to_value(&self, span: Span) -> Value {
185
match self {
186
Direction::In => Value::string("in", span),
187
Direction::Out => Value::string("out", span),
···
202
impl Signal {
203
/// Represent the signal as a nushell [Value]
204
pub fn to_value(&self, span: Span) -> Value {
205
-
Value::record(record!{
206
-
"name" => Value::string(&self.name, span),
207
-
"args" => list_to_value!(self.args, span),
208
-
"annotations" => list_to_value!(self.annotations, span),
209
-
}, span)
0
0
0
210
}
211
}
212
···
221
impl SignalArg {
222
/// Represent the argument as a nushell [Value]
223
pub fn to_value(&self, span: Span) -> Value {
224
-
Value::record(record!{
225
-
"name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(),
226
-
"type" => Value::string(&self.r#type, span),
227
-
}, span)
0
0
0
228
}
229
}
230
···
241
impl Property {
242
/// Represent the property as a nushell [Value]
243
pub fn to_value(&self, span: Span) -> Value {
244
-
Value::record(record!{
245
-
"name" => Value::string(&self.name, span),
246
-
"type" => Value::string(&self.r#type, span),
247
-
"args" => self.access.to_value(span),
248
-
"annotations" => list_to_value!(self.annotations, span),
249
-
}, span)
0
0
0
250
}
251
}
252
···
279
impl Annotation {
280
#[cfg(test)]
281
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Annotation {
282
-
Annotation { name: name.into(), value: value.into() }
0
0
0
283
}
284
285
/// Represent the annotation as a nushell [Value]
286
pub fn to_value(&self, span: Span) -> Value {
287
-
Value::record(record!{
288
-
"name" => Value::string(&self.name, span),
289
-
"value" => Value::string(&self.value, span),
290
-
}, span)
0
0
0
291
}
292
}
293
···
305
MethodArg::new("bar", "as", Direction::In),
306
MethodArg::new("baz", "a{us}", Direction::Out),
307
],
308
-
annotations: vec![
309
-
Annotation::new("org.freedesktop.DBus.Deprecated", "true"),
310
-
],
311
},
312
Method {
313
name: "Bazify".into(),
···
320
},
321
Method {
322
name: "Mogrify".into(),
323
-
args: vec![
324
-
MethodArg::new("bar", "(iiav)", Direction::In),
325
-
],
326
-
annotations: vec![]
327
-
},
328
-
],
329
-
signals: vec![
330
-
Signal {
331
-
name: "Changed".into(),
332
-
args: vec![
333
-
SignalArg { name: Some("new_value".into()), r#type: "b".into() },
334
-
],
335
-
annotations: vec![]
336
-
},
337
-
],
338
-
properties: vec![
339
-
Property {
340
-
name: "Bar".into(),
341
-
r#type: "y".into(),
342
-
access: Access::ReadWrite,
343
annotations: vec![],
344
-
}
345
],
346
-
annotations: vec![]
0
0
0
0
0
0
0
0
0
0
0
0
0
0
347
}],
348
children: vec![
349
Node::with_name("child_of_sample_object"),
350
Node::with_name("another_child_of_sample_object"),
351
-
]
352
}
353
}
354
···
1
+
use nu_protocol::{record, Span, Value};
2
use serde::Deserialize;
3
4
macro_rules! list_to_value {
5
+
($list:expr, $span:expr) => {
6
Value::list($list.iter().map(|i| i.to_value($span)).collect(), $span)
7
+
};
8
}
9
10
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)]
···
41
/// Find a method on an interface on this node, and then generate the signature of the method
42
/// args
43
pub fn get_method_args_signature(&self, interface: &str, method: &str) -> Option<String> {
44
+
Some(
45
+
self.get_interface(interface)?
46
+
.get_method(method)?
47
+
.in_signature(),
48
+
)
49
}
50
51
/// Find the signature of a property on an interface on this node
52
pub fn get_property_signature(&self, interface: &str, property: &str) -> Option<&str> {
53
+
Some(
54
+
&self
55
+
.get_interface(interface)?
56
+
.get_property(property)?
57
+
.r#type,
58
+
)
59
}
60
61
/// Represent the node as a nushell [Value]
62
pub fn to_value(&self, span: Span) -> Value {
63
+
Value::record(
64
+
record! {
65
+
"name" => self.name.as_ref().map(|s| Value::string(s, span)).unwrap_or_default(),
66
+
"interfaces" => list_to_value!(self.interfaces, span),
67
+
"children" => list_to_value!(self.children, span),
68
+
},
69
+
span,
70
+
)
71
}
72
}
73
···
101
102
/// Represent the interface as a nushell [Value]
103
pub fn to_value(&self, span: Span) -> Value {
104
+
Value::record(
105
+
record! {
106
+
"name" => Value::string(&self.name, span),
107
+
"methods" => list_to_value!(self.methods, span),
108
+
"signals" => list_to_value!(self.signals, span),
109
+
"properties" => list_to_value!(self.properties, span),
110
+
"signals" => list_to_value!(self.signals, span),
111
+
},
112
+
span,
113
+
)
114
}
115
}
116
···
127
impl Method {
128
/// Get the signature of the method args
129
pub fn in_signature(&self) -> String {
130
+
self.args
131
+
.iter()
132
.filter(|arg| arg.direction == Direction::In)
133
.map(|arg| &arg.r#type[..])
134
.collect()
···
137
#[allow(dead_code)]
138
/// Get the signature of the method result
139
pub fn out_signature(&self) -> String {
140
+
self.args
141
+
.iter()
142
.filter(|arg| arg.direction == Direction::Out)
143
.map(|arg| &arg.r#type[..])
144
.collect()
···
146
147
/// Represent the method as a nushell [Value]
148
pub fn to_value(&self, span: Span) -> Value {
149
+
Value::record(
150
+
record! {
151
+
"name" => Value::string(&self.name, span),
152
+
"args" => list_to_value!(self.args, span),
153
+
"annotations" => list_to_value!(self.annotations, span),
154
+
},
155
+
span,
156
+
)
157
}
158
}
159
···
172
pub fn new(
173
name: impl Into<String>,
174
r#type: impl Into<String>,
175
+
direction: Direction,
176
) -> MethodArg {
177
MethodArg {
178
name: Some(name.into()),
···
183
184
/// Represent the method as a nushell [Value]
185
pub fn to_value(&self, span: Span) -> Value {
186
+
Value::record(
187
+
record! {
188
+
"name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(),
189
+
"type" => Value::string(&self.r#type, span),
190
+
"direction" => self.direction.to_value(span),
191
+
},
192
+
span,
193
+
)
194
}
195
}
196
···
204
205
impl Direction {
206
/// Represent the direction as a nushell [Value]
207
+
pub fn to_value(self, span: Span) -> Value {
208
match self {
209
Direction::In => Value::string("in", span),
210
Direction::Out => Value::string("out", span),
···
225
impl Signal {
226
/// Represent the signal as a nushell [Value]
227
pub fn to_value(&self, span: Span) -> Value {
228
+
Value::record(
229
+
record! {
230
+
"name" => Value::string(&self.name, span),
231
+
"args" => list_to_value!(self.args, span),
232
+
"annotations" => list_to_value!(self.annotations, span),
233
+
},
234
+
span,
235
+
)
236
}
237
}
238
···
247
impl SignalArg {
248
/// Represent the argument as a nushell [Value]
249
pub fn to_value(&self, span: Span) -> Value {
250
+
Value::record(
251
+
record! {
252
+
"name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(),
253
+
"type" => Value::string(&self.r#type, span),
254
+
},
255
+
span,
256
+
)
257
}
258
}
259
···
270
impl Property {
271
/// Represent the property as a nushell [Value]
272
pub fn to_value(&self, span: Span) -> Value {
273
+
Value::record(
274
+
record! {
275
+
"name" => Value::string(&self.name, span),
276
+
"type" => Value::string(&self.r#type, span),
277
+
"args" => self.access.to_value(span),
278
+
"annotations" => list_to_value!(self.annotations, span),
279
+
},
280
+
span,
281
+
)
282
}
283
}
284
···
311
impl Annotation {
312
#[cfg(test)]
313
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Annotation {
314
+
Annotation {
315
+
name: name.into(),
316
+
value: value.into(),
317
+
}
318
}
319
320
/// Represent the annotation as a nushell [Value]
321
pub fn to_value(&self, span: Span) -> Value {
322
+
Value::record(
323
+
record! {
324
+
"name" => Value::string(&self.name, span),
325
+
"value" => Value::string(&self.value, span),
326
+
},
327
+
span,
328
+
)
329
}
330
}
331
···
343
MethodArg::new("bar", "as", Direction::In),
344
MethodArg::new("baz", "a{us}", Direction::Out),
345
],
346
+
annotations: vec![Annotation::new("org.freedesktop.DBus.Deprecated", "true")],
0
0
347
},
348
Method {
349
name: "Bazify".into(),
···
356
},
357
Method {
358
name: "Mogrify".into(),
359
+
args: vec![MethodArg::new("bar", "(iiav)", Direction::In)],
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
360
annotations: vec![],
361
+
},
362
],
363
+
signals: vec![Signal {
364
+
name: "Changed".into(),
365
+
args: vec![SignalArg {
366
+
name: Some("new_value".into()),
367
+
r#type: "b".into(),
368
+
}],
369
+
annotations: vec![],
370
+
}],
371
+
properties: vec![Property {
372
+
name: "Bar".into(),
373
+
r#type: "y".into(),
374
+
access: Access::ReadWrite,
375
+
annotations: vec![],
376
+
}],
377
+
annotations: vec![],
378
}],
379
children: vec![
380
Node::with_name("child_of_sample_object"),
381
Node::with_name("another_child_of_sample_object"),
382
+
],
383
}
384
}
385
+53
-31
src/main.rs
···
1
-
use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin, EvaluatedCall, LabeledError};
2
-
use nu_protocol::{PluginSignature, Value, SyntaxShape, PluginExample, Span, Type};
3
0
4
mod config;
5
-
mod client;
6
mod convert;
7
mod dbus_type;
8
mod introspection;
9
mod pattern;
10
11
-
use config::*;
12
use client::*;
0
13
14
use crate::pattern::Pattern;
15
···
23
impl Plugin for NuPluginDbus {
24
fn signature(&self) -> Vec<PluginSignature> {
25
macro_rules! str {
26
-
($s:expr) => (Value::string($s, Span::unknown()))
0
0
27
}
28
vec![
29
PluginSignature::build("dbus")
30
-
.is_dbus_command()
31
.usage("Commands for interacting with D-Bus"),
32
PluginSignature::build("dbus introspect")
33
-
.is_dbus_command()
34
.accepts_dbus_client_options()
35
.accepts_timeout()
36
.usage("Introspect a D-Bus object")
···
65
},
66
]),
67
PluginSignature::build("dbus call")
68
-
.is_dbus_command()
69
.accepts_dbus_client_options()
70
.accepts_timeout()
71
.usage("Call a method and get its response")
···
108
},
109
]),
110
PluginSignature::build("dbus get")
111
-
.is_dbus_command()
112
.accepts_dbus_client_options()
113
.accepts_timeout()
114
.usage("Get a D-Bus property")
···
139
},
140
]),
141
PluginSignature::build("dbus get-all")
142
-
.is_dbus_command()
143
.accepts_dbus_client_options()
144
.accepts_timeout()
145
.usage("Get all D-Bus properties for the given object")
···
165
},
166
]),
167
PluginSignature::build("dbus set")
168
-
.is_dbus_command()
169
.accepts_dbus_client_options()
170
.accepts_timeout()
171
.usage("Set a D-Bus property")
···
196
},
197
]),
198
PluginSignature::build("dbus list")
199
-
.is_dbus_command()
200
.accepts_dbus_client_options()
201
.accepts_timeout()
202
.usage("List all available connection names on the bus")
···
244
"dbus" => Err(LabeledError {
245
label: "The `dbus` command requires a subcommand".into(),
246
msg: "add --help to see subcommands".into(),
247
-
span: Some(call.head)
248
}),
249
250
"dbus introspect" => self.introspect(call),
···
257
_ => Err(LabeledError {
258
label: "Plugin invoked with unknown command name".into(),
259
msg: "unknown command".into(),
260
-
span: Some(call.head)
261
-
})
262
}
263
}
264
}
265
266
/// For conveniently adding the base options to a dbus command
267
trait DbusSignatureUtilExt {
268
-
fn is_dbus_command(self) -> Self;
269
fn accepts_dbus_client_options(self) -> Self;
270
fn accepts_timeout(self) -> Self;
271
}
272
273
impl DbusSignatureUtilExt for PluginSignature {
274
-
fn is_dbus_command(self) -> Self {
275
self.search_terms(vec!["dbus".into()])
276
.category(nu_protocol::Category::Platform)
277
}
···
279
fn accepts_dbus_client_options(self) -> Self {
280
self.switch("session", "Send to the session message bus (default)", None)
281
.switch("system", "Send to the system message bus", None)
282
-
.switch("started", "Send to the bus that started this process, if applicable", None)
283
-
.named("bus", SyntaxShape::String, "Send to the bus server at the given address", None)
284
-
.named("peer", SyntaxShape::String,
0
0
0
0
0
0
0
0
0
0
0
285
"Send to a non-bus D-Bus server at the given address. \
286
Will not call the Hello method on initialization.",
287
-
None)
0
288
}
289
290
fn accepts_timeout(self) -> Self {
291
-
self.named("timeout", SyntaxShape::Duration, "How long to wait for a response", None)
0
0
0
0
0
292
}
293
}
294
···
296
fn introspect(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
297
let config = DbusClientConfig::try_from(call)?;
298
let dbus = DbusClient::new(config)?;
299
-
let node = dbus.introspect(
300
-
&call.get_flag("dest")?.unwrap(),
301
-
&call.req(0)?,
302
-
)?;
303
Ok(node.to_value(call.head))
304
}
305
···
312
&call.req(1)?,
313
&call.req(2)?,
314
call.get_flag("signature")?.as_ref(),
315
-
&call.positional[3..]
316
)?;
317
318
let flatten = !call.get_flag::<bool>("no-flatten")?.unwrap_or(false);
···
322
match values.len() {
323
0 if flatten => Ok(Value::nothing(call.head)),
324
1 if flatten => Ok(values.into_iter().nth(0).unwrap()),
325
-
_ => Ok(Value::list(values, call.head))
326
}
327
}
328
···
364
fn list(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
365
let config = DbusClientConfig::try_from(call)?;
366
let dbus = DbusClient::new(config)?;
367
-
let pattern = call.opt::<String>(0)?.map(|pat| Pattern::new(&pat, Some('.')));
0
0
368
let result = dbus.list(pattern.as_ref())?;
369
Ok(Value::list(
370
-
result.into_iter().map(|s| Value::string(s, call.head)).collect(),
371
-
call.head))
0
0
0
0
372
}
373
}
···
1
+
use nu_plugin::{serve_plugin, EvaluatedCall, LabeledError, MsgPackSerializer, Plugin};
2
+
use nu_protocol::{PluginExample, PluginSignature, Span, SyntaxShape, Type, Value};
3
4
+
mod client;
5
mod config;
0
6
mod convert;
7
mod dbus_type;
8
mod introspection;
9
mod pattern;
10
0
11
use client::*;
12
+
use config::*;
13
14
use crate::pattern::Pattern;
15
···
23
impl Plugin for NuPluginDbus {
24
fn signature(&self) -> Vec<PluginSignature> {
25
macro_rules! str {
26
+
($s:expr) => {
27
+
Value::string($s, Span::unknown())
28
+
};
29
}
30
vec![
31
PluginSignature::build("dbus")
32
+
.dbus_command()
33
.usage("Commands for interacting with D-Bus"),
34
PluginSignature::build("dbus introspect")
35
+
.dbus_command()
36
.accepts_dbus_client_options()
37
.accepts_timeout()
38
.usage("Introspect a D-Bus object")
···
67
},
68
]),
69
PluginSignature::build("dbus call")
70
+
.dbus_command()
71
.accepts_dbus_client_options()
72
.accepts_timeout()
73
.usage("Call a method and get its response")
···
110
},
111
]),
112
PluginSignature::build("dbus get")
113
+
.dbus_command()
114
.accepts_dbus_client_options()
115
.accepts_timeout()
116
.usage("Get a D-Bus property")
···
141
},
142
]),
143
PluginSignature::build("dbus get-all")
144
+
.dbus_command()
145
.accepts_dbus_client_options()
146
.accepts_timeout()
147
.usage("Get all D-Bus properties for the given object")
···
167
},
168
]),
169
PluginSignature::build("dbus set")
170
+
.dbus_command()
171
.accepts_dbus_client_options()
172
.accepts_timeout()
173
.usage("Set a D-Bus property")
···
198
},
199
]),
200
PluginSignature::build("dbus list")
201
+
.dbus_command()
202
.accepts_dbus_client_options()
203
.accepts_timeout()
204
.usage("List all available connection names on the bus")
···
246
"dbus" => Err(LabeledError {
247
label: "The `dbus` command requires a subcommand".into(),
248
msg: "add --help to see subcommands".into(),
249
+
span: Some(call.head),
250
}),
251
252
"dbus introspect" => self.introspect(call),
···
259
_ => Err(LabeledError {
260
label: "Plugin invoked with unknown command name".into(),
261
msg: "unknown command".into(),
262
+
span: Some(call.head),
263
+
}),
264
}
265
}
266
}
267
268
/// For conveniently adding the base options to a dbus command
269
trait DbusSignatureUtilExt {
270
+
fn dbus_command(self) -> Self;
271
fn accepts_dbus_client_options(self) -> Self;
272
fn accepts_timeout(self) -> Self;
273
}
274
275
impl DbusSignatureUtilExt for PluginSignature {
276
+
fn dbus_command(self) -> Self {
277
self.search_terms(vec!["dbus".into()])
278
.category(nu_protocol::Category::Platform)
279
}
···
281
fn accepts_dbus_client_options(self) -> Self {
282
self.switch("session", "Send to the session message bus (default)", None)
283
.switch("system", "Send to the system message bus", None)
284
+
.switch(
285
+
"started",
286
+
"Send to the bus that started this process, if applicable",
287
+
None,
288
+
)
289
+
.named(
290
+
"bus",
291
+
SyntaxShape::String,
292
+
"Send to the bus server at the given address",
293
+
None,
294
+
)
295
+
.named(
296
+
"peer",
297
+
SyntaxShape::String,
298
"Send to a non-bus D-Bus server at the given address. \
299
Will not call the Hello method on initialization.",
300
+
None,
301
+
)
302
}
303
304
fn accepts_timeout(self) -> Self {
305
+
self.named(
306
+
"timeout",
307
+
SyntaxShape::Duration,
308
+
"How long to wait for a response",
309
+
None,
310
+
)
311
}
312
}
313
···
315
fn introspect(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
316
let config = DbusClientConfig::try_from(call)?;
317
let dbus = DbusClient::new(config)?;
318
+
let node = dbus.introspect(&call.get_flag("dest")?.unwrap(), &call.req(0)?)?;
0
0
0
319
Ok(node.to_value(call.head))
320
}
321
···
328
&call.req(1)?,
329
&call.req(2)?,
330
call.get_flag("signature")?.as_ref(),
331
+
&call.positional[3..],
332
)?;
333
334
let flatten = !call.get_flag::<bool>("no-flatten")?.unwrap_or(false);
···
338
match values.len() {
339
0 if flatten => Ok(Value::nothing(call.head)),
340
1 if flatten => Ok(values.into_iter().nth(0).unwrap()),
341
+
_ => Ok(Value::list(values, call.head)),
342
}
343
}
344
···
380
fn list(&self, call: &EvaluatedCall) -> Result<Value, LabeledError> {
381
let config = DbusClientConfig::try_from(call)?;
382
let dbus = DbusClient::new(config)?;
383
+
let pattern = call
384
+
.opt::<String>(0)?
385
+
.map(|pat| Pattern::new(&pat, Some('.')));
386
let result = dbus.list(pattern.as_ref())?;
387
Ok(Value::list(
388
+
result
389
+
.into_iter()
390
+
.map(|s| Value::string(s, call.head))
391
+
.collect(),
392
+
call.head,
393
+
))
394
}
395
}
+208
-125
src/pattern.rs
···
17
let mut tokens = vec![];
18
for ch in pattern.chars() {
19
match ch {
20
-
'*' =>
21
if tokens.last() == Some(&PatternToken::OneWildcard) {
22
*tokens.last_mut().unwrap() = PatternToken::ManyWildcard;
23
} else {
24
tokens.push(PatternToken::OneWildcard);
25
-
},
26
-
'?' =>
27
-
tokens.push(PatternToken::AnyChar),
28
-
_ =>
29
-
match tokens.last_mut() {
30
-
Some(PatternToken::Exact(ref mut s)) => s.push(ch),
31
-
_ => tokens.push(PatternToken::Exact(ch.into())),
32
-
},
33
}
34
}
35
Pattern { separator, tokens }
···
57
MatchState::Precise => {
58
// Can't possibly match
59
return false;
60
-
},
61
MatchState::ScanAhead { stop_at_separator } => {
62
if search_str.is_empty() {
63
// End of input, can't match
64
return false;
65
}
66
-
if stop_at_separator &&
67
-
self.separator.is_some_and(|sep| search_str.starts_with(sep)) {
0
0
0
68
// Found the separator. Consume a char and revert to precise
69
// mode
70
search_str = &search_str[1..];
···
76
}
77
}
78
}
79
-
},
80
PatternToken::OneWildcard => {
81
// Set the mode to ScanAhead, stopping at separator
82
-
state = MatchState::ScanAhead { stop_at_separator: true };
0
0
83
tokens = &tokens[1..];
84
-
},
85
PatternToken::ManyWildcard => {
86
// Set the mode to ScanAhead, ignoring separator
87
-
state = MatchState::ScanAhead { stop_at_separator: false };
0
0
88
tokens = &tokens[1..];
89
-
},
90
PatternToken::AnyChar => {
91
if !search_str.is_empty() {
92
// Take a char from the search str and continue
···
96
// End of input
97
return false;
98
}
99
-
},
100
}
101
}
102
-
#[cfg(test)] {
103
-
println!("end, state={:?}, search_str={:?}, tokens={:?}", state, search_str, tokens);
0
0
0
0
104
}
105
if !search_str.is_empty() {
106
// If the search str is not empty at the end
···
108
// We didn't end with a wildcard, so this is a fail
109
MatchState::Precise => false,
110
// This could be a match as long as the separator isn't contained in the remainder
111
-
MatchState::ScanAhead { stop_at_separator: true } =>
0
0
112
if let Some(separator) = self.separator {
113
!search_str.contains(separator)
114
} else {
115
// No separator specified, so this is a success
116
true
117
-
},
0
118
// Always a success, no matter what remains
119
-
MatchState::ScanAhead { stop_at_separator: false } => true,
0
0
120
}
121
} else {
122
// The match has succeeded - there is nothing more to match
···
129
fn test_pattern_new() {
130
assert_eq!(
131
Pattern::new("", Some('/')),
132
-
Pattern { separator: Some('/'), tokens: vec![] }
0
0
0
133
);
134
assert_eq!(
135
Pattern::new("", None),
136
-
Pattern { separator: None, tokens: vec![] }
0
0
0
137
);
138
assert_eq!(
139
Pattern::new("org.freedesktop.DBus", Some('.')),
140
-
Pattern { separator: Some('.'), tokens: vec![
141
-
PatternToken::Exact("org.freedesktop.DBus".into()),
142
-
] }
0
143
);
144
assert_eq!(
145
Pattern::new("*", Some('.')),
146
-
Pattern { separator: Some('.'), tokens: vec![
147
-
PatternToken::OneWildcard,
148
-
] }
0
149
);
150
assert_eq!(
151
Pattern::new("**", Some('.')),
152
-
Pattern { separator: Some('.'), tokens: vec![
153
-
PatternToken::ManyWildcard,
154
-
] }
0
155
);
156
assert_eq!(
157
Pattern::new("?", Some('.')),
158
-
Pattern { separator: Some('.'), tokens: vec![
159
-
PatternToken::AnyChar,
160
-
] }
0
161
);
162
assert_eq!(
163
Pattern::new("org.freedesktop.*", Some('.')),
164
-
Pattern { separator: Some('.'), tokens: vec![
165
-
PatternToken::Exact("org.freedesktop.".into()),
166
-
PatternToken::OneWildcard,
167
-
] }
0
0
0
168
);
169
assert_eq!(
170
Pattern::new("org.freedesktop.**", Some('.')),
171
-
Pattern { separator: Some('.'), tokens: vec![
172
-
PatternToken::Exact("org.freedesktop.".into()),
173
-
PatternToken::ManyWildcard,
174
-
] }
0
0
0
175
);
176
assert_eq!(
177
Pattern::new("org.*.DBus", Some('.')),
178
-
Pattern { separator: Some('.'), tokens: vec![
179
-
PatternToken::Exact("org.".into()),
180
-
PatternToken::OneWildcard,
181
-
PatternToken::Exact(".DBus".into()),
182
-
] }
0
0
0
183
);
184
assert_eq!(
185
Pattern::new("org.**.DBus", Some('.')),
186
-
Pattern { separator: Some('.'), tokens: vec![
187
-
PatternToken::Exact("org.".into()),
188
-
PatternToken::ManyWildcard,
189
-
PatternToken::Exact(".DBus".into()),
190
-
] }
0
0
0
191
);
192
assert_eq!(
193
Pattern::new("org.**.?Bus", Some('.')),
194
-
Pattern { separator: Some('.'), tokens: vec![
195
-
PatternToken::Exact("org.".into()),
196
-
PatternToken::ManyWildcard,
197
-
PatternToken::Exact(".".into()),
198
-
PatternToken::AnyChar,
199
-
PatternToken::Exact("Bus".into()),
200
-
] }
0
0
0
201
);
202
assert_eq!(
203
Pattern::new("org.free*top", Some('.')),
204
-
Pattern { separator: Some('.'), tokens: vec![
205
-
PatternToken::Exact("org.free".into()),
206
-
PatternToken::OneWildcard,
207
-
PatternToken::Exact("top".into()),
208
-
] }
0
0
0
209
);
210
assert_eq!(
211
Pattern::new("org.free**top", Some('.')),
212
-
Pattern { separator: Some('.'), tokens: vec![
213
-
PatternToken::Exact("org.free".into()),
214
-
PatternToken::ManyWildcard,
215
-
PatternToken::Exact("top".into()),
216
-
] }
0
0
0
217
);
218
assert_eq!(
219
Pattern::new("org.**top", Some('.')),
220
-
Pattern { separator: Some('.'), tokens: vec![
221
-
PatternToken::Exact("org.".into()),
222
-
PatternToken::ManyWildcard,
223
-
PatternToken::Exact("top".into()),
224
-
] }
0
0
0
225
);
226
assert_eq!(
227
Pattern::new("**top", Some('.')),
228
-
Pattern { separator: Some('.'), tokens: vec![
229
-
PatternToken::ManyWildcard,
230
-
PatternToken::Exact("top".into()),
231
-
] }
0
0
0
232
);
233
assert_eq!(
234
Pattern::new("org.free**", Some('.')),
235
-
Pattern { separator: Some('.'), tokens: vec![
236
-
PatternToken::Exact("org.free".into()),
237
-
PatternToken::ManyWildcard,
238
-
] }
0
0
0
239
);
240
}
241
242
#[test]
243
fn test_pattern_is_match_empty() {
244
-
let pat = Pattern { separator: Some('.'), tokens: vec![] };
0
0
0
245
assert!(pat.is_match(""));
246
assert!(!pat.is_match("anystring"));
247
assert!(!pat.is_match("anystring.anyotherstring"));
···
249
250
#[test]
251
fn test_pattern_is_match_exact() {
252
-
let pat = Pattern { separator: Some('.'), tokens: vec![
253
-
PatternToken::Exact("specific".into()),
254
-
] };
0
255
assert!(pat.is_match("specific"));
256
assert!(!pat.is_match(""));
257
assert!(!pat.is_match("specifi"));
···
260
261
#[test]
262
fn test_pattern_is_match_one_wildcard() {
263
-
let pat = Pattern { separator: Some('.'), tokens: vec![
264
-
PatternToken::Exact("foo.".into()),
265
-
PatternToken::OneWildcard,
266
-
PatternToken::Exact(".baz".into()),
267
-
] };
0
0
0
268
assert!(pat.is_match("foo.bar.baz"));
269
assert!(pat.is_match("foo.grok.baz"));
270
assert!(pat.is_match("foo..baz"));
···
277
278
#[test]
279
fn test_pattern_is_match_one_wildcard_at_end() {
280
-
let pat = Pattern { separator: Some('.'), tokens: vec![
281
-
PatternToken::Exact("foo.".into()),
282
-
PatternToken::OneWildcard,
283
-
] };
0
0
0
284
assert!(pat.is_match("foo.bar"));
285
assert!(pat.is_match("foo.grok"));
286
assert!(pat.is_match("foo."));
···
292
293
#[test]
294
fn test_pattern_is_match_one_wildcard_at_start() {
295
-
let pat = Pattern { separator: Some('.'), tokens: vec![
296
-
PatternToken::OneWildcard,
297
-
PatternToken::Exact(".bar".into()),
298
-
] };
0
0
0
299
assert!(pat.is_match("foo.bar"));
300
assert!(pat.is_match("grok.bar"));
301
assert!(pat.is_match(".bar"));
···
307
308
#[test]
309
fn test_pattern_is_match_one_wildcard_no_separator() {
310
-
let pat = Pattern { separator: None, tokens: vec![
311
-
PatternToken::Exact("foo.".into()),
312
-
PatternToken::OneWildcard,
313
-
PatternToken::Exact(".baz".into()),
314
-
] };
0
0
0
315
assert!(pat.is_match("foo.bar.baz"));
316
assert!(pat.is_match("foo.grok.baz"));
317
assert!(pat.is_match("foo..baz"));
···
325
326
#[test]
327
fn test_pattern_is_match_many_wildcard() {
328
-
let pat = Pattern { separator: Some('.'), tokens: vec![
329
-
PatternToken::Exact("foo.".into()),
330
-
PatternToken::ManyWildcard,
331
-
PatternToken::Exact(".baz".into()),
332
-
] };
0
0
0
333
assert!(pat.is_match("foo.bar.baz"));
334
assert!(pat.is_match("foo.grok.baz"));
335
assert!(pat.is_match("foo..baz"));
···
343
344
#[test]
345
fn test_pattern_is_match_many_wildcard_at_end() {
346
-
let pat = Pattern { separator: Some('.'), tokens: vec![
347
-
PatternToken::Exact("foo.".into()),
348
-
PatternToken::ManyWildcard,
349
-
] };
0
0
0
350
assert!(pat.is_match("foo.bar"));
351
assert!(pat.is_match("foo.grok"));
352
assert!(pat.is_match("foo."));
···
358
359
#[test]
360
fn test_pattern_is_match_many_wildcard_at_start() {
361
-
let pat = Pattern { separator: Some('.'), tokens: vec![
362
-
PatternToken::ManyWildcard,
363
-
PatternToken::Exact(".bar".into()),
364
-
] };
0
0
0
365
assert!(pat.is_match("foo.bar"));
366
assert!(pat.is_match("grok.bar"));
367
assert!(pat.is_match("should.match.bar"));
···
373
374
#[test]
375
fn test_pattern_is_match_any_char() {
376
-
let pat = Pattern { separator: Some('.'), tokens: vec![
377
-
PatternToken::Exact("fo".into()),
378
-
PatternToken::AnyChar,
379
-
PatternToken::Exact(".baz".into()),
380
-
] };
0
0
0
381
assert!(pat.is_match("foo.baz"));
382
assert!(pat.is_match("foe.baz"));
383
assert!(pat.is_match("foi.baz"));
···
17
let mut tokens = vec![];
18
for ch in pattern.chars() {
19
match ch {
20
+
'*' => {
21
if tokens.last() == Some(&PatternToken::OneWildcard) {
22
*tokens.last_mut().unwrap() = PatternToken::ManyWildcard;
23
} else {
24
tokens.push(PatternToken::OneWildcard);
25
+
}
26
+
}
27
+
'?' => tokens.push(PatternToken::AnyChar),
28
+
_ => match tokens.last_mut() {
29
+
Some(PatternToken::Exact(ref mut s)) => s.push(ch),
30
+
_ => tokens.push(PatternToken::Exact(ch.into())),
31
+
},
0
32
}
33
}
34
Pattern { separator, tokens }
···
56
MatchState::Precise => {
57
// Can't possibly match
58
return false;
59
+
}
60
MatchState::ScanAhead { stop_at_separator } => {
61
if search_str.is_empty() {
62
// End of input, can't match
63
return false;
64
}
65
+
if stop_at_separator
66
+
&& self
67
+
.separator
68
+
.is_some_and(|sep| search_str.starts_with(sep))
69
+
{
70
// Found the separator. Consume a char and revert to precise
71
// mode
72
search_str = &search_str[1..];
···
78
}
79
}
80
}
81
+
}
82
PatternToken::OneWildcard => {
83
// Set the mode to ScanAhead, stopping at separator
84
+
state = MatchState::ScanAhead {
85
+
stop_at_separator: true,
86
+
};
87
tokens = &tokens[1..];
88
+
}
89
PatternToken::ManyWildcard => {
90
// Set the mode to ScanAhead, ignoring separator
91
+
state = MatchState::ScanAhead {
92
+
stop_at_separator: false,
93
+
};
94
tokens = &tokens[1..];
95
+
}
96
PatternToken::AnyChar => {
97
if !search_str.is_empty() {
98
// Take a char from the search str and continue
···
102
// End of input
103
return false;
104
}
105
+
}
106
}
107
}
108
+
#[cfg(test)]
109
+
{
110
+
println!(
111
+
"end, state={:?}, search_str={:?}, tokens={:?}",
112
+
state, search_str, tokens
113
+
);
114
}
115
if !search_str.is_empty() {
116
// If the search str is not empty at the end
···
118
// We didn't end with a wildcard, so this is a fail
119
MatchState::Precise => false,
120
// This could be a match as long as the separator isn't contained in the remainder
121
+
MatchState::ScanAhead {
122
+
stop_at_separator: true,
123
+
} => {
124
if let Some(separator) = self.separator {
125
!search_str.contains(separator)
126
} else {
127
// No separator specified, so this is a success
128
true
129
+
}
130
+
}
131
// Always a success, no matter what remains
132
+
MatchState::ScanAhead {
133
+
stop_at_separator: false,
134
+
} => true,
135
}
136
} else {
137
// The match has succeeded - there is nothing more to match
···
144
fn test_pattern_new() {
145
assert_eq!(
146
Pattern::new("", Some('/')),
147
+
Pattern {
148
+
separator: Some('/'),
149
+
tokens: vec![]
150
+
}
151
);
152
assert_eq!(
153
Pattern::new("", None),
154
+
Pattern {
155
+
separator: None,
156
+
tokens: vec![]
157
+
}
158
);
159
assert_eq!(
160
Pattern::new("org.freedesktop.DBus", Some('.')),
161
+
Pattern {
162
+
separator: Some('.'),
163
+
tokens: vec![PatternToken::Exact("org.freedesktop.DBus".into()),]
164
+
}
165
);
166
assert_eq!(
167
Pattern::new("*", Some('.')),
168
+
Pattern {
169
+
separator: Some('.'),
170
+
tokens: vec![PatternToken::OneWildcard,]
171
+
}
172
);
173
assert_eq!(
174
Pattern::new("**", Some('.')),
175
+
Pattern {
176
+
separator: Some('.'),
177
+
tokens: vec![PatternToken::ManyWildcard,]
178
+
}
179
);
180
assert_eq!(
181
Pattern::new("?", Some('.')),
182
+
Pattern {
183
+
separator: Some('.'),
184
+
tokens: vec![PatternToken::AnyChar,]
185
+
}
186
);
187
assert_eq!(
188
Pattern::new("org.freedesktop.*", Some('.')),
189
+
Pattern {
190
+
separator: Some('.'),
191
+
tokens: vec![
192
+
PatternToken::Exact("org.freedesktop.".into()),
193
+
PatternToken::OneWildcard,
194
+
]
195
+
}
196
);
197
assert_eq!(
198
Pattern::new("org.freedesktop.**", Some('.')),
199
+
Pattern {
200
+
separator: Some('.'),
201
+
tokens: vec![
202
+
PatternToken::Exact("org.freedesktop.".into()),
203
+
PatternToken::ManyWildcard,
204
+
]
205
+
}
206
);
207
assert_eq!(
208
Pattern::new("org.*.DBus", Some('.')),
209
+
Pattern {
210
+
separator: Some('.'),
211
+
tokens: vec![
212
+
PatternToken::Exact("org.".into()),
213
+
PatternToken::OneWildcard,
214
+
PatternToken::Exact(".DBus".into()),
215
+
]
216
+
}
217
);
218
assert_eq!(
219
Pattern::new("org.**.DBus", Some('.')),
220
+
Pattern {
221
+
separator: Some('.'),
222
+
tokens: vec![
223
+
PatternToken::Exact("org.".into()),
224
+
PatternToken::ManyWildcard,
225
+
PatternToken::Exact(".DBus".into()),
226
+
]
227
+
}
228
);
229
assert_eq!(
230
Pattern::new("org.**.?Bus", Some('.')),
231
+
Pattern {
232
+
separator: Some('.'),
233
+
tokens: vec![
234
+
PatternToken::Exact("org.".into()),
235
+
PatternToken::ManyWildcard,
236
+
PatternToken::Exact(".".into()),
237
+
PatternToken::AnyChar,
238
+
PatternToken::Exact("Bus".into()),
239
+
]
240
+
}
241
);
242
assert_eq!(
243
Pattern::new("org.free*top", Some('.')),
244
+
Pattern {
245
+
separator: Some('.'),
246
+
tokens: vec![
247
+
PatternToken::Exact("org.free".into()),
248
+
PatternToken::OneWildcard,
249
+
PatternToken::Exact("top".into()),
250
+
]
251
+
}
252
);
253
assert_eq!(
254
Pattern::new("org.free**top", Some('.')),
255
+
Pattern {
256
+
separator: Some('.'),
257
+
tokens: vec![
258
+
PatternToken::Exact("org.free".into()),
259
+
PatternToken::ManyWildcard,
260
+
PatternToken::Exact("top".into()),
261
+
]
262
+
}
263
);
264
assert_eq!(
265
Pattern::new("org.**top", Some('.')),
266
+
Pattern {
267
+
separator: Some('.'),
268
+
tokens: vec![
269
+
PatternToken::Exact("org.".into()),
270
+
PatternToken::ManyWildcard,
271
+
PatternToken::Exact("top".into()),
272
+
]
273
+
}
274
);
275
assert_eq!(
276
Pattern::new("**top", Some('.')),
277
+
Pattern {
278
+
separator: Some('.'),
279
+
tokens: vec![
280
+
PatternToken::ManyWildcard,
281
+
PatternToken::Exact("top".into()),
282
+
]
283
+
}
284
);
285
assert_eq!(
286
Pattern::new("org.free**", Some('.')),
287
+
Pattern {
288
+
separator: Some('.'),
289
+
tokens: vec![
290
+
PatternToken::Exact("org.free".into()),
291
+
PatternToken::ManyWildcard,
292
+
]
293
+
}
294
);
295
}
296
297
#[test]
298
fn test_pattern_is_match_empty() {
299
+
let pat = Pattern {
300
+
separator: Some('.'),
301
+
tokens: vec![],
302
+
};
303
assert!(pat.is_match(""));
304
assert!(!pat.is_match("anystring"));
305
assert!(!pat.is_match("anystring.anyotherstring"));
···
307
308
#[test]
309
fn test_pattern_is_match_exact() {
310
+
let pat = Pattern {
311
+
separator: Some('.'),
312
+
tokens: vec![PatternToken::Exact("specific".into())],
313
+
};
314
assert!(pat.is_match("specific"));
315
assert!(!pat.is_match(""));
316
assert!(!pat.is_match("specifi"));
···
319
320
#[test]
321
fn test_pattern_is_match_one_wildcard() {
322
+
let pat = Pattern {
323
+
separator: Some('.'),
324
+
tokens: vec![
325
+
PatternToken::Exact("foo.".into()),
326
+
PatternToken::OneWildcard,
327
+
PatternToken::Exact(".baz".into()),
328
+
],
329
+
};
330
assert!(pat.is_match("foo.bar.baz"));
331
assert!(pat.is_match("foo.grok.baz"));
332
assert!(pat.is_match("foo..baz"));
···
339
340
#[test]
341
fn test_pattern_is_match_one_wildcard_at_end() {
342
+
let pat = Pattern {
343
+
separator: Some('.'),
344
+
tokens: vec![
345
+
PatternToken::Exact("foo.".into()),
346
+
PatternToken::OneWildcard,
347
+
],
348
+
};
349
assert!(pat.is_match("foo.bar"));
350
assert!(pat.is_match("foo.grok"));
351
assert!(pat.is_match("foo."));
···
357
358
#[test]
359
fn test_pattern_is_match_one_wildcard_at_start() {
360
+
let pat = Pattern {
361
+
separator: Some('.'),
362
+
tokens: vec![
363
+
PatternToken::OneWildcard,
364
+
PatternToken::Exact(".bar".into()),
365
+
],
366
+
};
367
assert!(pat.is_match("foo.bar"));
368
assert!(pat.is_match("grok.bar"));
369
assert!(pat.is_match(".bar"));
···
375
376
#[test]
377
fn test_pattern_is_match_one_wildcard_no_separator() {
378
+
let pat = Pattern {
379
+
separator: None,
380
+
tokens: vec![
381
+
PatternToken::Exact("foo.".into()),
382
+
PatternToken::OneWildcard,
383
+
PatternToken::Exact(".baz".into()),
384
+
],
385
+
};
386
assert!(pat.is_match("foo.bar.baz"));
387
assert!(pat.is_match("foo.grok.baz"));
388
assert!(pat.is_match("foo..baz"));
···
396
397
#[test]
398
fn test_pattern_is_match_many_wildcard() {
399
+
let pat = Pattern {
400
+
separator: Some('.'),
401
+
tokens: vec![
402
+
PatternToken::Exact("foo.".into()),
403
+
PatternToken::ManyWildcard,
404
+
PatternToken::Exact(".baz".into()),
405
+
],
406
+
};
407
assert!(pat.is_match("foo.bar.baz"));
408
assert!(pat.is_match("foo.grok.baz"));
409
assert!(pat.is_match("foo..baz"));
···
417
418
#[test]
419
fn test_pattern_is_match_many_wildcard_at_end() {
420
+
let pat = Pattern {
421
+
separator: Some('.'),
422
+
tokens: vec![
423
+
PatternToken::Exact("foo.".into()),
424
+
PatternToken::ManyWildcard,
425
+
],
426
+
};
427
assert!(pat.is_match("foo.bar"));
428
assert!(pat.is_match("foo.grok"));
429
assert!(pat.is_match("foo."));
···
435
436
#[test]
437
fn test_pattern_is_match_many_wildcard_at_start() {
438
+
let pat = Pattern {
439
+
separator: Some('.'),
440
+
tokens: vec![
441
+
PatternToken::ManyWildcard,
442
+
PatternToken::Exact(".bar".into()),
443
+
],
444
+
};
445
assert!(pat.is_match("foo.bar"));
446
assert!(pat.is_match("grok.bar"));
447
assert!(pat.is_match("should.match.bar"));
···
453
454
#[test]
455
fn test_pattern_is_match_any_char() {
456
+
let pat = Pattern {
457
+
separator: Some('.'),
458
+
tokens: vec![
459
+
PatternToken::Exact("fo".into()),
460
+
PatternToken::AnyChar,
461
+
PatternToken::Exact(".baz".into()),
462
+
],
463
+
};
464
assert!(pat.is_match("foo.baz"));
465
assert!(pat.is_match("foe.baz"));
466
assert!(pat.is_match("foi.baz"));