//! OpenGL command. use crate::xml::element::{Elements, FromElements}; /// A command. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Command { prototype: Prototype, parameters: Vec, } impl Command { /// Returns a new `Command`. pub fn new( prototype: Prototype, parameters: impl IntoIterator, ) -> Self { Self { prototype, parameters: parameters.into_iter().collect(), } } /// Returns the command prototype. #[must_use] pub fn prototype(&self) -> &Prototype { &self.prototype } /// Returns the command parameters. #[must_use] pub fn parameters(&self) -> &[Parameter] { &self.parameters } } impl FromElements for Command { type Error = Error; fn from_elements( elements: &crate::xml::element::Elements, ) -> Result { let proto_element = elements .get_first_tagged_element("proto") .ok_or(Self::Error::MissingPrototype)?; let prototype = Prototype::from_elements(proto_element.child_elements())?; let parameters = elements .get_all_tagged_elements_with_name("param") .into_iter() .map(|param_element| Parameter::from_elements(param_element.child_elements())) .collect::, _>>()?; Ok(Self { prototype, parameters, }) } } /// [`Command`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// No 'proto' element was found. #[error("No 'proto' element was found")] MissingPrototype, /// Invalid prototype. #[error("Invalid prototype")] InvalidPrototype(#[from] PrototypeError), /// Invalid parameter. #[error("Invalid parameter")] InvalidParameter(#[from] ParameterError), } /// A command prototype. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Prototype { name: String, return_type: String, } impl Prototype { /// Returns a new `Prototype`. pub fn new(name: impl Into, return_type: impl Into) -> Self { Self { name: name.into(), return_type: return_type.into(), } } /// Returns the command prototype name. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the command prototype return type. #[must_use] pub fn return_type(&self) -> &str { &self.return_type } } impl FromElements for Prototype { type Error = PrototypeError; fn from_elements( elements: &crate::xml::element::Elements, ) -> Result { let name = elements .get_first_tagged_element("name") .ok_or(Self::Error::MissingName)? .child_elements() .get_first_text_element() .cloned() .unwrap_or_default(); let return_type = find_type(elements); Ok(Self { name, return_type }) } } /// [`Prototype`] error. #[derive(Debug, thiserror::Error)] pub enum PrototypeError { /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, } /// A command parameter. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Parameter { name: String, ty: String, } impl Parameter { /// Returns a new `Parameter`. pub fn new(name: impl Into, ty: impl Into) -> Self { Self { name: name.into(), ty: ty.into(), } } /// Returns the name of the command parameter. #[must_use] pub fn name(&self) -> &str { &self.name } /// Returns the type of the command parameter. #[must_use] pub fn get_type(&self) -> &str { &self.ty } } impl FromElements for Parameter { type Error = ParameterError; fn from_elements(elements: &Elements) -> Result { let name = elements .get_first_tagged_element("name") .ok_or(Self::Error::MissingName)? .child_elements() .get_first_text_element() .cloned() .unwrap_or_default(); let ty = find_type(elements); Ok(Self { name, ty }) } } /// [`Parameter`] error. #[derive(Debug, thiserror::Error)] pub enum ParameterError { /// No 'name' element was found. #[error("No 'name' element was found")] MissingName, } fn find_type(elements: &Elements) -> String { let text_type_parts = elements .get_all_text_elements() .into_iter() .map(|text_type_part| text_type_part.trim()) .filter(|text_type_part| !text_type_part.is_empty()) .collect::>(); let opt_ptype_text = get_ptype_text(elements); opt_ptype_text.map_or_else( || join_space_strs(text_type_parts.iter()), |ptype_text| { let Some(first_part) = text_type_parts.first() else { return ptype_text.clone(); }; let before = if *first_part == "const" { "const " } else { "" }; let after_start_index = usize::from(*first_part == "const"); format!( "{before}{ptype_text} {}", text_type_parts .get(after_start_index..) .map(|parts| join_space_strs(parts.iter())) .unwrap_or_default() ) }, ) } fn get_ptype_text(elements: &Elements) -> Option<&String> { let ptype_element = elements.get_first_tagged_element("ptype")?; ptype_element.child_elements().get_first_text_element() } fn join_space_strs(strings: Strings) -> String where Strings: Iterator, StrItem: ToString, { strings .into_iter() .map(|string| string.to_string()) .collect::>() .join(" ") }