gir_parser/
repository.rs

1use std::{collections::HashMap, path::Path};
2
3use xmlserde_derives::XmlDeserialize;
4
5use crate::{namespace::Namespace, version::Version, ParserError};
6
7#[derive(Clone, Debug, 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, 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, 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)]
64pub enum DocFormat {
65    GtkDocMarkdown,
66    GtkDocDocbook,
67    GiDocgen,
68    HotDoc,
69    Unknown,
70}
71
72impl xmlserde::XmlValue for DocFormat {
73    fn serialize(&self) -> String {
74        match self {
75            Self::GtkDocMarkdown => "gtk-doc-markdown",
76            Self::GtkDocDocbook => "gtk-doc-docbook",
77            Self::GiDocgen => "gi-docgen",
78            Self::HotDoc => "hotdoc",
79            Self::Unknown => "unknown",
80        }
81        .to_owned()
82    }
83
84    fn deserialize(s: &str) -> Result<Self, String> {
85        match s {
86            "gtk-doc-markdown" => Ok(Self::GtkDocMarkdown),
87            "gtk-doc-docbook" => Ok(Self::GtkDocDocbook),
88            "gi-docgen" => Ok(Self::GiDocgen),
89            "hotdoc" => Ok(Self::HotDoc),
90            "unknown" => Ok(Self::Unknown),
91            e => Err(format!("Invalid doc:format {e}")),
92        }
93    }
94}
95
96#[derive(Clone, Debug, XmlDeserialize)]
97#[xmlserde(root = b"repository")]
98#[xmlserde(deny_unknown_fields)]
99pub struct Repository {
100    #[xmlserde(name = b"version", ty = "attr")]
101    version: Option<Version>,
102    #[xmlserde(name = b"c:identifier-prefixes", ty = "attr")]
103    c_identifier_prefixes: Option<String>,
104    #[xmlserde(name = b"c:symbol-prefixes", ty = "attr")]
105    c_symbol_prefixes: Option<String>,
106    #[xmlserde(name = b"xmlns", ty = "attr")]
107    _xmlns: Option<String>,
108    #[xmlserde(name = b"xmlns:c", ty = "attr")]
109    _xmlns_c: Option<String>,
110    #[xmlserde(name = b"xmlns:glib", ty = "attr")]
111    _xmlns_glib: Option<String>,
112    #[xmlserde(name = b"xmlns:doc", ty = "attr")]
113    _xmlns_doc: Option<String>,
114    #[xmlserde(name = b"include", ty = "child")]
115    includes: Vec<NamespaceInclude>,
116    #[xmlserde(name = b"c:include", ty = "child")]
117    c_includes: Vec<HeaderInclude>,
118    #[xmlserde(name = b"package", ty = "child")]
119    packages: Vec<Package>,
120    #[xmlserde(name = b"namespace", ty = "child")]
121    namespace: Namespace,
122    #[xmlserde(name = b"doc:format", ty = "attr")]
123    doc_format: Option<DocFormat>,
124}
125
126impl Repository {
127    pub fn from_path_follow_namespaces_and_cache(
128        cache: &mut HashMap<String, Self>,
129        package_file: &str,
130        girs_dirs: impl AsRef<Path>,
131    ) -> Result<(), ParserError> {
132        let repo = Self::from_path(girs_dirs.as_ref().join(package_file))?;
133        if cache.contains_key(package_file) {
134            return Ok(());
135        }
136        for namespace in repo.namespace_includes() {
137            if !cache.contains_key(&namespace.as_package_file()) {
138                Self::from_path_follow_namespaces_and_cache(
139                    cache,
140                    &namespace.as_package_file(),
141                    girs_dirs.as_ref(),
142                )?;
143            }
144        }
145        debug_assert_eq!(
146            package_file,
147            format!(
148                "{}-{}.gir",
149                repo.namespace().name(),
150                repo.namespace().version()
151            )
152        );
153        cache.insert(package_file.to_owned(), repo);
154        Ok(())
155    }
156
157    pub fn from_path_follow_namespaces(
158        package_file: &str,
159        girs_dirs: impl AsRef<Path>,
160    ) -> Result<HashMap<String, Self>, ParserError> {
161        let mut output = HashMap::new();
162        Self::from_path_follow_namespaces_and_cache(&mut output, package_file, girs_dirs)?;
163        Ok(output)
164    }
165
166    pub fn from_path(path: impl AsRef<Path>) -> Result<Self, ParserError> {
167        let content = std::fs::read_to_string(path)?;
168        let repository = xmlserde::xml_deserialize_from_str(&content).map_err(ParserError::Xml)?;
169        Ok(repository)
170    }
171
172    pub fn version(&self) -> Option<&Version> {
173        self.version.as_ref()
174    }
175
176    // TODO: split this by ","
177    pub fn c_identifier_prefixes(&self) -> Option<&str> {
178        self.c_identifier_prefixes.as_deref()
179    }
180
181    // TODO: split this by ","
182    pub fn c_symbol_prefixes(&self) -> Option<&str> {
183        self.c_symbol_prefixes.as_deref()
184    }
185
186    pub fn namespace_includes(&self) -> &[NamespaceInclude] {
187        &self.includes
188    }
189
190    pub fn header_includes(&self) -> &[HeaderInclude] {
191        &self.c_includes
192    }
193
194    pub fn packages(&self) -> &[Package] {
195        &self.packages
196    }
197
198    pub fn namespace(&self) -> &Namespace {
199        &self.namespace
200    }
201
202    pub fn doc_format(&self) -> Option<&DocFormat> {
203        self.doc_format.as_ref()
204    }
205}