//! Rust API for the [OpenGL reference page sources]. //! //! [OpenGL reference page sources]: https://github.com/KhronosGroup/OpenGL-Refpages #![cfg_attr(doc_cfg, feature(doc_cfg))] #![deny(clippy::all, clippy::pedantic, missing_docs)] use std::ffi::OsStr; use std::os::unix::prelude::OsStrExt; use include_dir::{include_dir, Dir}; use crate::description::{Description, Error as DescriptionError}; use crate::xml::element::{Elements, FromElements}; use crate::xml::parser::{Error as ParserError, Parser}; pub mod description; pub mod informal_table; pub mod itemized_list; pub mod variable_list; mod xml; static GL4_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/OpenGL-Refpages/gl4"); /// Reference entry. #[derive(Debug)] pub struct ReferenceEntry { purpose: String, descriptions: Vec, } impl ReferenceEntry { /// Returns a new `ReferenceEntry`. pub fn new( purpose: &impl ToString, descriptions: impl IntoIterator, ) -> Self { Self { purpose: purpose.to_string(), descriptions: descriptions.into_iter().collect(), } } /// Returns a function reference entry. /// /// # Errors /// Returns `Err` if /// - No reference entry file was found. /// - Parsing the reference entry file data fails. /// - The reference entry file data is invalid. pub fn get_function(function_name: &str) -> Result { let xml_files = GL4_DIR .files() .filter(|file| { file.path() .extension() .map_or_else(|| false, |extension| extension.as_bytes() == b"xml") }) .collect::>(); let function_file = xml_files .iter() .find_map(|file| { if file.path().file_stem()?.to_str()? == function_name { Some(file) } else { None } }) .or_else(|| { xml_files.iter().find_map(|file| { let file_stem = file.path().file_stem()?.to_str()?; if function_name.starts_with(file_stem) { Some(file) } else { None } }) }) .ok_or_else(|| ReferenceEntryError::NoFileFound(function_name.to_string()))?; let function_ref_content = function_file.contents(); let mut parser = Parser::new(function_ref_content); let root_elements = parser .parse() .map_err(|err| ReferenceEntryError::ParsingFailed { source: err, entry_name: function_file .path() .file_stem() .unwrap_or_else(|| OsStr::new("(unknown)")) .to_string_lossy() .to_string(), })?; ReferenceEntry::from_elements(&root_elements).map_err(|err| { ReferenceEntryError::Invalid { source: err, entry_name: function_file .path() .file_stem() .unwrap_or_else(|| OsStr::new("(unknown)")) .to_string_lossy() .to_string(), } }) } /// Returns the reference entry purpose. #[must_use] pub fn purpose(&self) -> &str { &self.purpose } /// Returns the reference entry descriptions. #[must_use] pub fn descriptions(&self) -> &[Description] { &self.descriptions } } impl FromElements for ReferenceEntry { type Error = InvalidReferenceEntryError; fn from_elements(elements: &Elements) -> Result { let refentry_element = elements .get_first_tagged_with_name("refentry") .ok_or(Self::Error::MissingRefEntry)?; let refnamediv_element = refentry_element .child_elements() .get_first_tagged_with_name("refnamediv") .ok_or(Self::Error::MissingRefNameDiv)?; let refpurpose_element = refnamediv_element .child_elements() .get_first_tagged_with_name("refpurpose") .ok_or(Self::Error::MissingRefPurpose)?; let purpose = refpurpose_element .child_elements() .get_first_text_element() .cloned() .unwrap_or_default(); let description_elements = refentry_element .child_elements() .get_all_tagged_with_name_and_attr("refsect1", |attr| { attr.key == "xml:id" && attr.value.starts_with(b"description") }); if description_elements.is_empty() { return Err(Self::Error::MissingDescriptionRefSect); } let descriptions = description_elements .iter() .map(|description_element| { Description::from_elements(description_element.child_elements()) }) .collect::, _>>()?; Ok(ReferenceEntry { purpose, descriptions, }) } } /// [`ReferenceEntry`] error. #[derive(Debug, thiserror::Error)] pub enum ReferenceEntryError { /// No reference entry file was found. #[error("No reference entry file was found for '{0}'")] NoFileFound(String), /// The data of the reference entry is invalid. #[error("Invalid reference entry '{entry_name}'")] Invalid { /// Source error. #[source] source: InvalidReferenceEntryError, /// Name of the invalid reference entry. entry_name: String, }, /// Parsing failed. #[error("Failed to parse reference entry '{entry_name}'")] ParsingFailed { /// Source error. #[source] source: ParserError, /// Name of the reference entry that couldn't be parsed. entry_name: String, }, } /// Error for when a reference entry is invalid. #[derive(Debug, thiserror::Error)] pub enum InvalidReferenceEntryError { /// No 'refentry' element was found. #[error("No 'refentry' element was found")] MissingRefEntry, /// No 'refnamediv' element was found. #[error("No 'refnamediv' element was found")] MissingRefNameDiv, /// No 'refpurpose' element was found. #[error("No 'refpurpose' element was found")] MissingRefPurpose, /// No 'refsect1' element was found with id 'description''. #[error("No 'refsect1' element was found with id 'description'")] MissingDescriptionRefSect, /// Invalid description. #[error("Invalid description")] InvalidDescription(#[from] DescriptionError), }