Skip to main content

gir_parser/
lib.rs

1#![doc = include_str!("../README.md")]
2use xmlserde::xml_serde_enum;
3
4#[derive(Debug)]
5pub enum ParserError {
6    IO(std::io::Error),
7    Xml(String),
8}
9
10impl From<std::io::Error> for ParserError {
11    fn from(value: std::io::Error) -> Self {
12        Self::IO(value)
13    }
14}
15
16impl std::error::Error for ParserError {}
17impl std::fmt::Display for ParserError {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            Self::IO(e) => f.write_fmt(format_args!("I/O operation failed {e}")),
21            Self::Xml(e) => f.write_fmt(format_args!("Failed to parse xml file: {e}")),
22        }
23    }
24}
25
26xml_serde_enum! {
27    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
28    Stability {
29        Stable => "Stable",
30        Unstable => "Unstable",
31        Private => "Private",
32    }
33}
34
35xml_serde_enum! {
36    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
37    TransferOwnership {
38        None => "none",
39        Container => "container",
40        Full => "full",
41    }
42}
43
44impl TransferOwnership {
45    pub fn is_none(&self) -> bool {
46        matches!(self, Self::None)
47    }
48
49    pub fn is_full(&self) -> bool {
50        matches!(self, Self::Full)
51    }
52
53    pub fn is_container(&self) -> bool {
54        matches!(self, Self::Container)
55    }
56}
57
58xml_serde_enum! {
59    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
60    FunctionScope {
61        Call => "call",
62        Notified => "notified",
63        Async => "async",
64        Forever => "forever",
65    }
66}
67
68impl FunctionScope {
69    pub fn is_call(&self) -> bool {
70        matches!(self, Self::Call)
71    }
72
73    pub fn is_notified(&self) -> bool {
74        matches!(self, Self::Notified)
75    }
76
77    pub fn is_async(&self) -> bool {
78        matches!(self, Self::Async)
79    }
80
81    pub fn is_forever(&self) -> bool {
82        matches!(self, Self::Forever)
83    }
84}
85
86xml_serde_enum! {
87    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
88    SignalEmission {
89        First => "first",
90        Last => "last",
91        Cleanup => "cleanup",
92    }
93}
94
95#[macro_use]
96mod traits;
97pub mod prelude {
98    pub use xmlserde::XmlValue;
99
100    pub use super::traits::*;
101}
102
103mod alias;
104pub use alias::Alias;
105mod array;
106pub use array::Array;
107mod attribute;
108pub use attribute::Attribute;
109mod bitfield;
110pub use bitfield::BitField;
111mod boxed;
112pub use boxed::Boxed;
113mod callable;
114pub use callable::Callable;
115mod callback;
116pub use callback::Callback;
117mod class;
118pub use class::{Class, ClassField, Implements};
119mod constant;
120pub use constant::Constant;
121mod documentation;
122pub use documentation::{DocDeprecated, DocStability, DocVersion, Documentation, SourcePosition};
123mod enums;
124pub use enums::Enumeration;
125mod field;
126pub use field::{Field, FieldType};
127mod function;
128pub use function::{Function, FunctionInline};
129mod function_macro;
130pub use function_macro::FunctionMacro;
131mod interface;
132pub use interface::{Interface, InterfaceField, Prerequisite};
133mod member;
134pub use member::Member;
135mod method;
136pub use method::{Method, MethodInline};
137mod namespace;
138pub use namespace::Namespace;
139mod parameter;
140pub use parameter::{
141    AnyParameter, Direction, InstanceParameter, Parameter, ParameterType, Parameters,
142};
143mod property;
144pub use property::Property;
145mod record;
146pub use record::{Record, RecordField};
147mod repository;
148pub use repository::{DocFormat, HeaderInclude, NamespaceInclude, Package, Repository};
149mod return_value;
150pub use return_value::ReturnValue;
151mod signal;
152pub use signal::Signal;
153mod r#type;
154pub use r#type::{AnyType, Type};
155mod union;
156pub use union::{Union, UnionField};
157mod version;
158pub use version::Version;
159mod virtual_method;
160pub use virtual_method::VirtualMethod;
161
162#[cfg(test)]
163mod tests {
164    use std::{path::PathBuf, str::FromStr};
165
166    use super::prelude::*;
167
168    const GIR_FILES: [&str; 35] = [
169        "Atk-1.0",
170        "cairo-1.0",
171        "fontconfig-2.0",
172        "freetype2-2.0",
173        "Gdk-3.0",
174        "Gdk-4.0",
175        "GdkPixbuf-2.0",
176        "GdkPixdata-2.0",
177        "GdkWayland-4.0",
178        "GdkWin32-4.0",
179        "GdkX11-3.0",
180        "GdkX11-4.0",
181        "Gio-2.0",
182        "GL-1.0",
183        "GLib-2.0",
184        "GModule-2.0",
185        "GObject-2.0",
186        "Graphene-1.0",
187        "Gsk-4.0",
188        "Gtk-3.0",
189        "Gtk-4.0",
190        "HarfBuzz-0.0",
191        "libxml2-2.0",
192        "Pango-1.0",
193        "PangoCairo-1.0",
194        "PangoFc-1.0",
195        "PangoFT2-1.0",
196        "PangoOT-1.0",
197        "PangoXft-1.0",
198        "Vulkan-1.0",
199        "win32-1.0",
200        "xfixes-4.0",
201        "xft-2.0",
202        "xlib-2.0",
203        "xrandr-1.3",
204    ];
205
206    use super::{repository::Repository, version::Version};
207
208    fn parse_gir(gir_file: &str) -> Repository {
209        let path = PathBuf::from("./gir-files").join(format!("{}.gir", gir_file));
210        Repository::from_path(path).unwrap()
211    }
212    #[test]
213    fn xft_gir() {
214        let repo = parse_gir(GIR_FILES[32]);
215        assert_eq!(repo.version(), Version::from_str("1.2").ok().as_ref());
216        assert_eq!(repo.namespace_includes()[0].as_package(), "xlib-2.0");
217
218        let namespace = repo.namespace();
219        assert_eq!(namespace.version(), "2.0");
220        assert_eq!(namespace.name(), "xft");
221        assert_eq!(
222            namespace.c_identifier_prefixes().collect::<Vec<_>>(),
223            vec!["Xft"]
224        );
225        assert_eq!(
226            namespace.c_symbol_prefixes().collect::<Vec<_>>(),
227            vec!["Xft"]
228        );
229    }
230
231    #[test]
232    fn xlib_gir() {
233        let repo = parse_gir(GIR_FILES[33]);
234        assert_eq!(repo.version(), Version::from_str("1.2").ok().as_ref());
235        let namespace = repo.namespace();
236        assert_eq!(namespace.version(), "2.0");
237        assert_eq!(namespace.name(), "xlib");
238        assert!(namespace
239            .c_identifier_prefixes()
240            .collect::<Vec<_>>()
241            .is_empty());
242        assert_eq!(namespace.c_symbol_prefixes().collect::<Vec<_>>(), vec!["X"]);
243        let aliases = namespace.aliases();
244        assert_eq!(aliases[0].name(), "Atom");
245        assert_eq!(aliases[0].c_type(), "Atom");
246        assert_eq!(aliases[0].ty().as_type().name(), Some("gulong"));
247        assert_eq!(aliases[0].ty().as_type().c_type(), Some("gulong"));
248
249        let records = namespace.records();
250        assert_eq!(records[0].name(), Some("Display"));
251        assert_eq!(records[0].c_type(), Some("Display"));
252
253        let unions = namespace.unions();
254        assert_eq!(unions[0].name(), Some("XEvent"));
255        assert_eq!(unions[0].c_type(), Some("XEvent"));
256
257        let functions = namespace.functions();
258        assert_eq!(functions[0].name(), "open_display");
259        assert_eq!(functions[0].c_identifier(), Some("XOpenDisplay"));
260        assert!(functions[0].parameters().is_empty());
261
262        let return_value = functions[0].return_value();
263        assert!(return_value.transfer_ownership().unwrap().is_none());
264        assert_eq!(return_value.ty().as_type().name(), Some("none"));
265        assert_eq!(return_value.ty().as_type().c_type(), Some("void"));
266    }
267
268    #[test]
269    fn xrandr_gir() {
270        let repo = parse_gir(GIR_FILES[34]);
271        assert_eq!(repo.version(), Version::from_str("1.2").ok().as_ref());
272        let namespace = repo.namespace();
273        assert_eq!(namespace.version(), "1.3");
274        assert_eq!(namespace.name(), "xrandr");
275        assert_eq!(
276            namespace.c_identifier_prefixes().collect::<Vec<_>>(),
277            vec!["XRR"]
278        );
279        assert_eq!(
280            namespace.c_symbol_prefixes().collect::<Vec<_>>(),
281            vec!["XRR"]
282        );
283        let records = namespace.records();
284        assert_eq!(records[0].name(), Some("ScreenSize"));
285        assert_eq!(records[0].c_type(), Some("XRRScreenSize"));
286    }
287
288    #[test]
289    fn parse_all_gir_files() {
290        let paths = std::fs::read_dir("./gir-files").unwrap();
291
292        for path in paths {
293            let path = path.unwrap().path();
294            let gir_file = path
295                .file_name()
296                .unwrap()
297                .to_str()
298                .unwrap()
299                .trim_end_matches(".gir")
300                .to_owned();
301            println!("{:#?}", gir_file);
302            let repository = parse_gir(&gir_file);
303            assert!(gir_file.starts_with(repository.namespace().name()));
304        }
305    }
306
307    #[test]
308    fn parse_doc_format_is_missing() {
309        // doc:format is missing
310        let content = r#"
311<repository xmlns="http://www.gtk.org/introspection/core/1.0" xmlns:c="http://www.gtk.org/introspection/c/1.0" xmlns:doc="http://www.gtk.org/introspection/doc/1.0" xmlns:glib="http://www.gtk.org/introspection/glib/1.0" version="1.2">
312  <include name="GObject" version="2.0"/>
313  <package name="pango"/>
314  <c:include name="pango/pango.h"/>
315  <namespace name="Pango" version="1.0" shared-library="libpango-1.0.so.0" c:identifier-prefixes="Pango" c:symbol-prefixes="pango">
316  </namespace>
317</repository>"#;
318        let repo = Repository::from_str(content).unwrap();
319        assert_eq!(repo.doc_format(), crate::DocFormat::Unknown);
320    }
321
322    #[test]
323    fn parse_doc_format_gi_docgen_missing() {
324        // doc:format is missing
325        let content = r#"
326<repository xmlns="http://www.gtk.org/introspection/core/1.0" xmlns:c="http://www.gtk.org/introspection/c/1.0" xmlns:doc="http://www.gtk.org/introspection/doc/1.0" xmlns:glib="http://www.gtk.org/introspection/glib/1.0" version="1.2">
327  <include name="GObject" version="2.0"/>
328  <package name="pango"/>
329  <c:include name="pango/pango.h"/>
330  <doc:format name="gi-docgen"/>
331  <namespace name="Pango" version="1.0" shared-library="libpango-1.0.so.0" c:identifier-prefixes="Pango" c:symbol-prefixes="pango">
332  </namespace>
333</repository>"#;
334        let repo = Repository::from_str(content).unwrap();
335        assert_eq!(repo.doc_format(), crate::DocFormat::GiDocgen);
336    }
337}