opengl_registry/
command.rs

1//! OpenGL command.
2use crate::xml::element::{Elements, FromElements};
3
4/// A command.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Command
7{
8    prototype: Prototype,
9    parameters: Vec<Parameter>,
10}
11
12impl Command
13{
14    /// Returns a new `Command`.
15    pub fn new(
16        prototype: Prototype,
17        parameters: impl IntoIterator<Item = Parameter>,
18    ) -> Self
19    {
20        Self {
21            prototype,
22            parameters: parameters.into_iter().collect(),
23        }
24    }
25
26    /// Returns the command prototype.
27    #[must_use]
28    pub fn prototype(&self) -> &Prototype
29    {
30        &self.prototype
31    }
32
33    /// Returns the command parameters.
34    #[must_use]
35    pub fn parameters(&self) -> &[Parameter]
36    {
37        &self.parameters
38    }
39}
40
41impl FromElements for Command
42{
43    type Error = Error;
44
45    fn from_elements(
46        elements: &crate::xml::element::Elements,
47    ) -> Result<Self, Self::Error>
48    {
49        let proto_element = elements
50            .get_first_tagged_element("proto")
51            .ok_or(Self::Error::MissingPrototype)?;
52
53        let prototype = Prototype::from_elements(proto_element.child_elements())?;
54
55        let parameters = elements
56            .get_all_tagged_elements_with_name("param")
57            .into_iter()
58            .map(|param_element| Parameter::from_elements(param_element.child_elements()))
59            .collect::<Result<Vec<_>, _>>()?;
60
61        Ok(Self {
62            prototype,
63            parameters,
64        })
65    }
66}
67
68/// [`Command`] error.
69#[derive(Debug, thiserror::Error)]
70pub enum Error
71{
72    /// No 'proto' element was found.
73    #[error("No 'proto' element was found")]
74    MissingPrototype,
75
76    /// Invalid prototype.
77    #[error("Invalid prototype")]
78    InvalidPrototype(#[from] PrototypeError),
79
80    /// Invalid parameter.
81    #[error("Invalid parameter")]
82    InvalidParameter(#[from] ParameterError),
83}
84
85/// A command prototype.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Prototype
88{
89    name: String,
90    return_type: String,
91}
92
93impl Prototype
94{
95    /// Returns a new `Prototype`.
96    pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self
97    {
98        Self {
99            name: name.into(),
100            return_type: return_type.into(),
101        }
102    }
103
104    /// Returns the command prototype name.
105    #[must_use]
106    pub fn name(&self) -> &str
107    {
108        &self.name
109    }
110
111    /// Returns the command prototype return type.
112    #[must_use]
113    pub fn return_type(&self) -> &str
114    {
115        &self.return_type
116    }
117}
118
119impl FromElements for Prototype
120{
121    type Error = PrototypeError;
122
123    fn from_elements(
124        elements: &crate::xml::element::Elements,
125    ) -> Result<Self, Self::Error>
126    {
127        let name = elements
128            .get_first_tagged_element("name")
129            .ok_or(Self::Error::MissingName)?
130            .child_elements()
131            .get_first_text_element()
132            .cloned()
133            .unwrap_or_default();
134
135        let return_type = find_type(elements);
136
137        Ok(Self { name, return_type })
138    }
139}
140
141/// [`Prototype`] error.
142#[derive(Debug, thiserror::Error)]
143pub enum PrototypeError
144{
145    /// No 'name' element was found.
146    #[error("No 'name' element was found")]
147    MissingName,
148}
149
150/// A command parameter.
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct Parameter
153{
154    name: String,
155    ty: String,
156}
157
158impl Parameter
159{
160    /// Returns a new `Parameter`.
161    pub fn new(name: impl Into<String>, ty: impl Into<String>) -> Self
162    {
163        Self {
164            name: name.into(),
165            ty: ty.into(),
166        }
167    }
168
169    /// Returns the name of the command parameter.
170    #[must_use]
171    pub fn name(&self) -> &str
172    {
173        &self.name
174    }
175
176    /// Returns the type of the command parameter.
177    #[must_use]
178    pub fn get_type(&self) -> &str
179    {
180        &self.ty
181    }
182}
183
184impl FromElements for Parameter
185{
186    type Error = ParameterError;
187
188    fn from_elements(elements: &Elements) -> Result<Self, Self::Error>
189    {
190        let name = elements
191            .get_first_tagged_element("name")
192            .ok_or(Self::Error::MissingName)?
193            .child_elements()
194            .get_first_text_element()
195            .cloned()
196            .unwrap_or_default();
197
198        let ty = find_type(elements);
199
200        Ok(Self { name, ty })
201    }
202}
203
204/// [`Parameter`] error.
205#[derive(Debug, thiserror::Error)]
206pub enum ParameterError
207{
208    /// No 'name' element was found.
209    #[error("No 'name' element was found")]
210    MissingName,
211}
212
213fn find_type(elements: &Elements) -> String
214{
215    let text_type_parts = elements
216        .get_all_text_elements()
217        .into_iter()
218        .map(|text_type_part| text_type_part.trim())
219        .filter(|text_type_part| !text_type_part.is_empty())
220        .collect::<Vec<_>>();
221
222    let opt_ptype_text = get_ptype_text(elements);
223
224    opt_ptype_text.map_or_else(
225        || join_space_strs(text_type_parts.iter()),
226        |ptype_text| {
227            let Some(first_part) = text_type_parts.first() else {
228                return ptype_text.clone();
229            };
230
231            let before = if *first_part == "const" { "const " } else { "" };
232
233            let after_start_index = usize::from(*first_part == "const");
234
235            format!(
236                "{before}{ptype_text} {}",
237                text_type_parts
238                    .get(after_start_index..)
239                    .map(|parts| join_space_strs(parts.iter()))
240                    .unwrap_or_default()
241            )
242        },
243    )
244}
245
246fn get_ptype_text(elements: &Elements) -> Option<&String>
247{
248    let ptype_element = elements.get_first_tagged_element("ptype")?;
249
250    ptype_element.child_elements().get_first_text_element()
251}
252
253fn join_space_strs<Strings, StrItem>(strings: Strings) -> String
254where
255    Strings: Iterator<Item = StrItem>,
256    StrItem: ToString,
257{
258    strings
259        .into_iter()
260        .map(|string| string.to_string())
261        .collect::<Vec<_>>()
262        .join(" ")
263}