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 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 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}