dbus_codegen/
generate.rs

1
2use std::{io, error};
3use std::collections::{HashSet, HashMap};
4use xml;
5
6fn find_attr<'a>(a: &'a Vec<xml::attribute::OwnedAttribute>, n: &str) -> Result<&'a str, Box<dyn error::Error>> {
7    a.into_iter()
8        .find(|q| q.name.prefix.is_none() && q.name.local_name == n)
9        .map(|f| &*f.value)
10        .ok_or_else(|| format!("attribute not found: {:?}", n).into())
11}
12
13/// Server access code generation option
14#[derive(Copy, Clone, Eq, PartialEq, Debug)]
15pub enum ServerAccess {
16    /// Supply a closure from ref to ref
17    RefClosure,
18    /// Supply a closure from ref to owned object which asrefs
19    AsRefClosure,
20    /// The interface is implemented for MethodInfo
21    MethodInfo
22}
23
24
25#[derive(Copy, Clone, Eq, PartialEq, Debug)]
26pub enum ConnectionType {
27    Ffidisp,
28    Blocking,
29    Nonblock,
30}
31
32/// Code generation options
33#[derive(Clone, Debug)]
34pub struct GenOpts {
35    /// Name of dbus crate (used for import)
36    pub dbuscrate: String,
37    /// MethodType for dbus-tree impl, set to none for client impl only
38    pub methodtype: Option<String>,
39    /// Generate dbus-crossroads server implementation
40    pub crossroads: bool,
41    /// Removes a prefix from interface names
42    pub skipprefix: Option<String>,
43    /// Type of server access (tree)
44    pub serveraccess: ServerAccess,
45    /// Tries to make variants generic instead of Variant<Box<Refarg>>
46    pub genericvariant: bool,
47    /// Type of connection, for client only
48    pub connectiontype: ConnectionType,
49    /// Generates a struct wrapping PropMap to get properties from it with their expected types.
50    pub propnewtype: bool,
51    /// interface filter. Only matching interface are generated, if non-empty.
52    pub interfaces: Option<HashSet<String>>,
53    /// The command line argument string. This will be inserted into generated source files.
54    pub command_line: String,
55}
56
57impl ::std::default::Default for GenOpts {
58    fn default() -> Self { GenOpts {
59        dbuscrate: "dbus".into(), methodtype: Some("MTFn".into()), skipprefix: None,
60        serveraccess: ServerAccess::RefClosure, genericvariant: false,
61        connectiontype: ConnectionType::Blocking, propnewtype: false,
62        interfaces: None, crossroads: false,
63        command_line: String::new()
64    }}
65}
66
67mod types;
68
69mod write;
70
71use types::*;
72
73
74/// Generates Rust structs and traits from D-Bus XML introspection data.
75pub fn generate(xmldata: &str, opts: &GenOpts) -> Result<String, Box<dyn error::Error>> {
76    use xml::EventReader;
77    use xml::reader::XmlEvent;
78
79    let mut s = String::new();
80    write::module_header(&mut s, opts);
81    let mut curintf = None;
82    let mut curm = None;
83    let mut cursig = None;
84    let mut curprop = None;
85    let mut curarg = None;
86    let parser = EventReader::new(io::Cursor::new(xmldata));
87    for e in parser {
88        match e? {
89            XmlEvent::StartElement { ref name, .. } if name.prefix.is_some() => (),
90            XmlEvent::EndElement { ref name, .. } if name.prefix.is_some() => (),
91            XmlEvent::StartElement { ref name, ref attributes, .. } if &name.local_name == "interface" => {
92                if curm.is_some() { Err("Start of Interface inside method")? };
93                if curintf.is_some() { Err("Start of Interface inside interface")? };
94                let n = find_attr(attributes, "name")?;
95                let mut n2 = n;
96                if let &Some(ref p) = &opts.skipprefix {
97                    if n.len() > p.len() && n.starts_with(p) { n2 = &n[p.len()..]; }
98                }
99                curintf = Some(Intf { origname: n.into(), shortname: n2.into(),
100                    methods: Vec::new(), signals: Vec::new(), props: Vec::new(), annotations: HashMap::new() });
101            }
102            XmlEvent::EndElement { ref name } if &name.local_name == "interface" => {
103                if curm.is_some() { Err("End of Interface inside method")? };
104                if curintf.is_none() { Err("End of Interface outside interface")? };
105                let intf = curintf.take().unwrap();
106                // If filters are set and no filter matches -> Just print a message and continue parsing
107                if let Some(filter) = &opts.interfaces {
108                    if !filter.contains(&intf.shortname) && !filter.contains(&intf.origname) {
109                        eprintln!("Skip filtered interface '{}'", &intf.shortname);
110                        continue;
111                    }
112                }
113                write::intf(&mut s, &intf, opts)?;
114                write::signals(&mut s, &intf)?;
115                if opts.propnewtype {
116                    write::intf_name(&mut s, &intf)?;
117                    write::prop_struct(&mut s, &intf)?;
118                }
119                if opts.crossroads {
120                    write::intf_cr(&mut s, &intf)?;
121                }
122                if let Some(ref mt) = opts.methodtype {
123                    write::intf_tree(&mut s, &intf, &mt, opts.serveraccess, opts.genericvariant)?;
124                } else if !opts.crossroads {
125                    write::intf_client(&mut s, &intf, opts)?;
126                }
127            }
128
129            XmlEvent::StartElement { ref name, ref attributes, .. } if &name.local_name == "method" => {
130                if curm.is_some() { Err("Start of method inside method")? };
131                if curintf.is_none() { Err("Start of method outside interface")? };
132                let name = find_attr(attributes, "name")?;
133                curm = Some(Method { name: name.into(), fn_name: make_fn_name(curintf.as_ref().unwrap(), name),
134                    iargs: Vec::new(), oargs: Vec::new(), annotations: HashMap::new() });
135            }
136            XmlEvent::EndElement { ref name } if &name.local_name == "method" => {
137                if curm.is_none() { Err("End of method outside method")? };
138                if curintf.is_none() { Err("End of method outside interface")? };
139                curintf.as_mut().unwrap().methods.push(curm.take().unwrap());
140            }
141
142            XmlEvent::StartElement { ref name, ref attributes, .. } if &name.local_name == "signal" => {
143                if cursig.is_some() { Err("Start of signal inside signal")? };
144                if curintf.is_none() { Err("Start of signal outside interface")? };
145                cursig = Some(Signal { name: find_attr(attributes, "name")?.into(), args: Vec::new(), annotations: HashMap::new() });
146            }
147            XmlEvent::EndElement { ref name } if &name.local_name == "signal" => {
148                if cursig.is_none() { Err("End of signal outside signal")? };
149                if curintf.is_none() { Err("End of signal outside interface")? };
150                curintf.as_mut().unwrap().signals.push(cursig.take().unwrap());
151            }
152
153            XmlEvent::StartElement { ref name, ref attributes, .. } if &name.local_name == "property" => {
154                if curprop.is_some() { Err("Start of property inside property")? };
155                if curintf.is_none() { Err("Start of property outside interface")? };
156                let name = find_attr(attributes, "name")?;
157                let get_fn_name = make_fn_name(curintf.as_ref().unwrap(), name);
158                let set_fn_name = make_fn_name(curintf.as_ref().unwrap(), &format!("Set{}", name));
159                curprop = Some(Prop {
160                    name: name.into(),
161                    typ: find_attr(attributes, "type")?.into(),
162                    access: find_attr(attributes, "access")?.into(),
163                    get_fn_name: get_fn_name,
164                    set_fn_name: set_fn_name,
165                    annotations: HashMap::new(),
166                });
167            }
168            XmlEvent::EndElement { ref name } if &name.local_name == "property" => {
169                if curprop.is_none() { Err("End of property outside property")? };
170                if curintf.is_none() { Err("End of property outside interface")? };
171                curintf.as_mut().unwrap().props.push(curprop.take().unwrap());
172            }
173
174
175            XmlEvent::StartElement { ref name, ref attributes, .. } if &name.local_name == "arg" => {
176                if curm.is_none() && cursig.is_none() { Err("Start of arg outside method and signal")? };
177                if curintf.is_none() { Err("Start of arg outside interface")? };
178                let typ = find_attr(attributes, "type")?.into();
179                let is_out = match find_attr(attributes, "direction") {
180                    Err(_) => false,
181                    Ok("in") => false,
182                    Ok("out") => true,
183                    _ => { Err("Invalid direction")?; unreachable!() }
184                };
185                let no_refs = is_out || cursig.is_some() || opts.crossroads;
186                curarg = Some(Arg { name: find_attr(attributes, "name").unwrap_or("").into(),
187                    typ: typ, no_refs, idx: 0, is_out, annotations: HashMap::new() });
188            }
189
190            XmlEvent::EndElement { ref name } if &name.local_name == "arg" => {
191                if curarg.is_none() { Err("End of arg outside arg")? };
192                let arg = curarg.as_mut().unwrap();
193                let arr = if let Some(ref mut sig) = cursig { &mut sig.args }
194                else if arg.is_out { &mut curm.as_mut().unwrap().oargs } else { &mut curm.as_mut().unwrap().iargs };
195                arg.idx = arr.len() as i32;
196                arr.push(curarg.take().unwrap());
197            }
198
199            XmlEvent::StartElement { ref name, ref attributes, ..} if &name.local_name == "annotation" => {
200                if let Ok(key) = find_attr(attributes, "name") {
201                    if let Ok(value) = find_attr(attributes, "value") {
202                        if let Some(ref mut arg) = curarg { arg.annotations.insert(key.into(), value.into()); }
203                        else if let Some(ref mut sig) = cursig { sig.annotations.insert(key.into(), value.into()); }
204                        else if let Some(ref mut prop) = curprop { prop.annotations.insert(key.into(), value.into()); }
205                        else if let Some(ref mut met) = curm { met.annotations.insert(key.into(), value.into()); }
206                        else if let Some(ref mut intf) = curintf { intf.annotations.insert(key.into(), value.into()); }
207                    }
208                }
209            }
210            _ => (),
211        }
212    }
213    if curintf.is_some() { Err("Unterminated interface")? }
214    Ok(s)
215}
216
217#[cfg(test)]
218mod tests {
219
220use super::{generate, GenOpts};
221
222static FROM_DBUS: &'static str = r#"
223<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
224"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
225<node>
226  <interface name="org.freedesktop.DBus">
227    <method name="Hello">
228      <arg direction="out" type="s"/>
229    </method>
230    <method name="RequestName">
231      <arg direction="in" type="s"/>
232      <arg direction="in" type="u"/>
233      <arg direction="out" type="u"/>
234    </method>
235    <method name="ReleaseName">
236      <arg direction="in" type="s"/>
237      <arg direction="out" type="u"/>
238    </method>
239    <method name="StartServiceByName">
240      <arg direction="in" type="s"/>
241      <arg direction="in" type="u"/>
242      <arg direction="out" type="u"/>
243    </method>
244    <method name="UpdateActivationEnvironment">
245      <arg direction="in" type="a{ss}"/>
246    </method>
247    <method name="NameHasOwner">
248      <arg direction="in" type="s"/>
249      <arg direction="out" type="b"/>
250    </method>
251    <method name="ListNames">
252      <arg direction="out" type="as"/>
253    </method>
254    <method name="ListActivatableNames">
255      <arg direction="out" type="as"/>
256    </method>
257    <method name="AddMatch">
258      <arg direction="in" type="s"/>
259    </method>
260    <method name="RemoveMatch">
261      <arg direction="in" type="s"/>
262    </method>
263    <method name="GetNameOwner">
264      <arg direction="in" type="s"/>
265      <arg direction="out" type="s"/>
266    </method>
267    <method name="ListQueuedOwners">
268      <arg direction="in" type="s"/>
269      <arg direction="out" type="as"/>
270    </method>
271    <method name="GetConnectionUnixUser">
272      <arg direction="in" type="s"/>
273      <arg direction="out" type="u"/>
274    </method>
275    <method name="GetConnectionUnixProcessID">
276      <arg direction="in" type="s"/>
277      <arg direction="out" type="u"/>
278    </method>
279    <method name="GetAdtAuditSessionData">
280      <arg direction="in" type="s"/>
281      <arg direction="out" type="ay"/>
282    </method>
283    <method name="GetConnectionSELinuxSecurityContext">
284      <arg direction="in" type="s"/>
285      <arg direction="out" type="ay"/>
286    </method>
287    <method name="GetConnectionAppArmorSecurityContext">
288      <arg direction="in" type="s"/>
289      <arg direction="out" type="s"/>
290    </method>
291    <method name="ReloadConfig">
292    </method>
293    <method name="GetId">
294      <arg direction="out" type="s"/>
295    </method>
296    <method name="GetConnectionCredentials">
297      <arg direction="in" type="s"/>
298      <arg direction="out" type="a{sv}"/>
299    </method>
300    <signal name="NameOwnerChanged">
301      <arg type="s"/>
302      <arg type="s"/>
303      <arg type="s"/>
304    </signal>
305    <signal name="NameLost">
306      <arg type="s"/>
307    </signal>
308    <signal name="NameAcquired">
309      <arg type="s"/>
310    </signal>
311  </interface>
312  <interface name="org.freedesktop.DBus.Introspectable">
313    <method name="Introspect">
314      <arg direction="out" type="s"/>
315    </method>
316  </interface>
317  <interface name="org.freedesktop.DBus.Monitoring">
318    <method name="BecomeMonitor">
319      <arg direction="in" type="as"/>
320      <arg direction="in" type="u"/>
321    </method>
322  </interface>
323  <interface name="org.freedesktop.DBus.Debug.Stats">
324    <method name="GetStats">
325      <arg direction="out" type="a{sv}"/>
326    </method>
327    <method name="GetConnectionStats">
328      <arg direction="in" type="s"/>
329      <arg direction="out" type="a{sv}"/>
330    </method>
331    <method name="GetAllMatchRules">
332      <arg direction="out" type="a{sas}"/>
333    </method>
334  </interface>
335</node>
336"#;
337
338    #[test]
339    fn from_dbus() {
340        let s = generate(FROM_DBUS, &GenOpts { methodtype: Some("MTSync".into()), ..Default::default() }).unwrap();
341        println!("{}", s);
342        //assert_eq!(s, "fdjsf");
343    }
344}