1use std::{collections::HashMap, path::Path, str::FromStr};
2
3use xmlserde_derives::XmlDeserialize;
4
5use crate::{namespace::Namespace, version::Version, ParserError};
6
7#[derive(Clone, Debug, PartialEq, Eq, Hash, XmlDeserialize)]
8#[xmlserde(root = b"include")]
9#[xmlserde(deny_unknown_fields)]
10pub struct NamespaceInclude {
11 #[xmlserde(name = b"name", ty = "attr")]
12 name: String,
13 #[xmlserde(name = b"version", ty = "attr")]
14 version: Version,
15}
16
17impl NamespaceInclude {
18 pub fn as_package(&self) -> String {
19 format!("{}-{}", self.name, self.version)
20 }
21
22 pub fn as_package_file(&self) -> String {
23 format!("{}-{}.gir", self.name, self.version)
24 }
25
26 pub fn name(&self) -> &str {
27 &self.name
28 }
29
30 pub fn version(&self) -> &Version {
31 &self.version
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Hash, XmlDeserialize)]
36#[xmlserde(root = b"c:include")]
37#[xmlserde(deny_unknown_fields)]
38pub struct HeaderInclude {
39 #[xmlserde(name = b"name", ty = "attr")]
40 name: String,
41}
42
43impl HeaderInclude {
44 pub fn name(&self) -> &str {
45 &self.name
46 }
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Hash, XmlDeserialize)]
50#[xmlserde(root = b"package")]
51#[xmlserde(deny_unknown_fields)]
52pub struct Package {
53 #[xmlserde(name = b"name", ty = "attr")]
54 name: String,
55}
56
57impl Package {
58 pub fn name(&self) -> &str {
59 &self.name
60 }
61}
62
63#[derive(Clone, Debug, XmlDeserialize, Copy, Default)]
64#[xmlserde(root = b"doc:format")]
65#[xmlserde(deny_unknown_fields)]
66struct DocFormatChild {
67 #[xmlserde(name = b"name", ty = "attr")]
68 format: DocFormat,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq, Default, Copy)]
72pub enum DocFormat {
73 GtkDocMarkdown,
74 GtkDocDocbook,
75 GiDocgen,
76 Hotdoc,
77 #[default]
78 Unknown,
79}
80
81impl xmlserde::XmlValue for DocFormat {
82 fn serialize(&self) -> String {
83 match self {
84 Self::GtkDocMarkdown => "gtk-doc-markdown",
85 Self::GtkDocDocbook => "gtk-doc-docbook",
86 Self::GiDocgen => "gi-docgen",
87 Self::Hotdoc => "hotdoc",
88 Self::Unknown => "unknown",
89 }
90 .to_owned()
91 }
92
93 fn deserialize(s: &str) -> Result<Self, String> {
94 match s {
95 "gtk-doc-markdown" => Ok(Self::GtkDocMarkdown),
96 "gtk-doc-docbook" => Ok(Self::GtkDocDocbook),
97 "gi-docgen" => Ok(Self::GiDocgen),
98 "hotdoc" => Ok(Self::Hotdoc),
99 "unknown" => Ok(Self::Unknown),
100 e => Err(format!("Invalid doc:format {e}")),
101 }
102 }
103}
104
105#[derive(Clone, Debug, XmlDeserialize)]
106#[xmlserde(root = b"repository")]
107#[xmlserde(deny_unknown_fields)]
108pub struct Repository {
109 #[xmlserde(name = b"version", ty = "attr")]
110 version: Option<Version>,
111 #[xmlserde(name = b"c:identifier-prefixes", ty = "attr")]
112 c_identifier_prefixes: Option<String>,
113 #[xmlserde(name = b"c:symbol-prefixes", ty = "attr")]
114 c_symbol_prefixes: Option<String>,
115 #[xmlserde(name = b"xmlns", ty = "attr")]
116 _xmlns: Option<String>,
117 #[xmlserde(name = b"xmlns:c", ty = "attr")]
118 _xmlns_c: Option<String>,
119 #[xmlserde(name = b"xmlns:glib", ty = "attr")]
120 _xmlns_glib: Option<String>,
121 #[xmlserde(name = b"xmlns:doc", ty = "attr")]
122 _xmlns_doc: Option<String>,
123 #[xmlserde(name = b"include", ty = "child")]
124 includes: Vec<NamespaceInclude>,
125 #[xmlserde(name = b"c:include", ty = "child")]
126 c_includes: Vec<HeaderInclude>,
127 #[xmlserde(name = b"package", ty = "child")]
128 packages: Vec<Package>,
129 #[xmlserde(name = b"namespace", ty = "child")]
130 namespace: Namespace,
131 #[xmlserde(name = b"doc:format", ty = "child")]
132 doc_format_child: Option<DocFormatChild>,
133}
134
135impl Repository {
136 pub fn from_path_follow_namespaces_and_cache(
137 cache: &mut HashMap<String, Self>,
138 package_file: &str,
139 girs_dirs: impl AsRef<Path>,
140 ) -> Result<(), ParserError> {
141 let repo = Self::from_path(girs_dirs.as_ref().join(package_file))?;
142 if cache.contains_key(package_file) {
143 return Ok(());
144 }
145 for namespace in repo.namespace_includes() {
146 if !cache.contains_key(&namespace.as_package_file()) {
147 Self::from_path_follow_namespaces_and_cache(
148 cache,
149 &namespace.as_package_file(),
150 girs_dirs.as_ref(),
151 )?;
152 }
153 }
154 debug_assert_eq!(
155 package_file,
156 format!(
157 "{}-{}.gir",
158 repo.namespace().name(),
159 repo.namespace().version()
160 )
161 );
162 cache.insert(package_file.to_owned(), repo);
163 Ok(())
164 }
165
166 pub fn from_path_follow_namespaces(
167 package_file: &str,
168 girs_dirs: impl AsRef<Path>,
169 ) -> Result<HashMap<String, Self>, ParserError> {
170 let mut output = HashMap::new();
171 Self::from_path_follow_namespaces_and_cache(&mut output, package_file, girs_dirs)?;
172 Ok(output)
173 }
174
175 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, ParserError> {
176 let content = std::fs::read_to_string(path)?;
177 let repository = xmlserde::xml_deserialize_from_str(&content).map_err(ParserError::Xml)?;
178 Ok(repository)
179 }
180
181 pub fn version(&self) -> Option<&Version> {
182 self.version.as_ref()
183 }
184
185 pub fn c_identifier_prefixes(&self) -> impl Iterator<Item = &str> {
186 self.c_identifier_prefixes
187 .as_ref()
188 .filter(|ps| !ps.is_empty())
189 .map(|ps| ps.split(','))
190 .into_iter()
191 .flatten()
192 }
193
194 pub fn c_symbol_prefixes(&self) -> impl Iterator<Item = &str> {
195 self.c_symbol_prefixes
196 .as_ref()
197 .filter(|ps| !ps.is_empty())
198 .map(|ps| ps.split(','))
199 .into_iter()
200 .flatten()
201 }
202
203 pub fn namespace_includes(&self) -> &[NamespaceInclude] {
204 &self.includes
205 }
206
207 pub fn header_includes(&self) -> &[HeaderInclude] {
208 &self.c_includes
209 }
210
211 pub fn packages(&self) -> &[Package] {
212 &self.packages
213 }
214
215 pub fn namespace(&self) -> &Namespace {
216 &self.namespace
217 }
218
219 pub fn doc_format(&self) -> DocFormat {
220 self.doc_format_child.map(|c| c.format).unwrap_or_default()
221 }
222}
223
224impl FromStr for Repository {
225 type Err = ParserError;
226
227 fn from_str(s: &str) -> Result<Self, Self::Err> {
228 let repository = xmlserde::xml_deserialize_from_str(s).map_err(ParserError::Xml)?;
229 Ok(repository)
230 }
231}