//! Reference entry description. use crate::informal_table::{Error as InformalTableError, InformalTable}; use crate::itemized_list::{Error as ItemizedListError, ItemizedList}; use crate::variable_list::{Error as VariableListError, VariableList}; use crate::xml::element::{Element, Elements, FromElements, Tagged}; /// Reference entry description. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Description { for_function: Option, parts: Vec, } impl Description { /// Returns a new empty `Description`. #[must_use] pub fn new() -> Self { Self { for_function: None, parts: Vec::new(), } } /// Returns what function this description is specific for. #[must_use] pub fn for_function(&self) -> &Option { &self.for_function } /// Returns the description's parts. #[must_use] pub fn parts(&self) -> &[Part] { &self.parts } } impl Default for Description { fn default() -> Self { Self::new() } } impl FromElements for Description { type Error = Error; fn from_elements(elements: &Elements) -> Result { let for_function = elements .get_first_tagged_with_name("title") .and_then(|title_element| { let title_text = title_element.child_elements().get_first_text_element()?; if title_text != "Description for " { return None; } let function_element = title_element .child_elements() .get_first_tagged_with_name("function")?; function_element .child_elements() .get_first_text_element() .cloned() }); let parts = elements .get_all_tagged_elements() .into_iter() .filter_map(|part_elem| match part_elem.name() { "para" => Some( Paragraph::from_elements(part_elem.child_elements()) .map(Part::Paragraph) .map_err(Self::Error::InvalidParagraph), ), "variablelist" => Some( VariableList::from_elements(part_elem.child_elements()) .map(Part::VariableList) .map_err(Self::Error::InvalidVariableList), ), "programlisting" => Some(Ok(Part::ProgramListing( part_elem .child_elements() .get_first_text_element() .cloned() .unwrap_or_default(), ))), "informaltable" => Some( InformalTable::from_elements(part_elem.child_elements()) .map(Part::InformalTable) .map_err(Self::Error::InvalidInformalTable), ), "itemizedlist" => Some( ItemizedList::from_elements(part_elem.child_elements()) .map(Part::ItemizedList) .map_err(Self::Error::InvalidItemizedList), ), "title" => None, name => Some(Err(Self::Error::UnknownPartFound(name.to_string()))), }) .collect::, Self::Error>>()?; Ok(Description { for_function, parts, }) } } /// [`Description`] error. #[derive(Debug, thiserror::Error)] pub enum Error { /// Unknown part element found. #[error("Unknown part element with name '{0}' found")] UnknownPartFound(String), /// Invalid paragraph. #[error("Invalid paragraph")] InvalidParagraph(#[source] ParagraphError), /// Invalid variable list. #[error("Invalid variable list")] InvalidVariableList(#[source] VariableListError), /// Invalid informal table. #[error("Invalid informal table")] InvalidInformalTable(#[source] InformalTableError), /// Invalid itemized list. #[error("Invalid itemized list")] InvalidItemizedList(#[source] ItemizedListError), } /// Description part. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Part { /// Paragraph. Paragraph(Paragraph), /// Variable list. VariableList(VariableList), /// Program listing. ProgramListing(String), /// Informal table. InformalTable(InformalTable), /// Itemized list. ItemizedList(ItemizedList), } /// Reference entry description paragraph. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Paragraph { parts: Vec, } impl Paragraph { /// Returns a new `Paragraph`. pub fn new(parts: impl IntoIterator) -> Self { Self { parts: parts.into_iter().collect(), } } /// Returns the parts of the paragraph. #[must_use] pub fn parts(&self) -> &[ParagraphPart] { &self.parts } } impl FromElements for Paragraph { type Error = ParagraphError; fn from_elements(elements: &Elements) -> Result { let parts = elements .into_iter() .filter_map(|element| { if matches!(element, Element::Comment(_)) { return None; } Some(ParagraphPart::from_elements(&Elements::from([ element.clone() ]))) }) .collect::, _>>()?; Ok(Self { parts }) } } /// [`Paragraph`] error. #[derive(Debug, thiserror::Error)] pub enum ParagraphError { /// Invalid reference description part. #[error("Invalid part")] InvalidPart(#[from] ParagraphPartError), } /// Reference entry description paragraph part. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParagraphPart { /// Text part. Text(String), /// .. part. Constant(String), /// .. part. Function(String), /// .. part. Parameter(String), /// Emphasis part. Emphasis(String), /// Code part. Code(String), /// Inline equation part. InlineEquation(String), /// Program listing part. ProgramListing(String), /// Reference entry citation part. Entry(String), /// Variable list part. VariableList(VariableList), /// Itemized list part. ItemizedList(ItemizedList), /// Informal table part. InformalTable(InformalTable), /// Paragraph part. Paragraph(Paragraph), /// Footnote part. Footnote(Paragraph), } impl FromElements for ParagraphPart { type Error = ParagraphPartError; fn from_elements(elements: &Elements) -> Result { if let Some(tagged_element) = elements.get_first_tagged() { return Self::from_tagged_element(tagged_element); } let text = elements .get_first_text_element() .ok_or(Self::Error::ExpectedTextElement)?; Ok(Self::Text(text.clone())) } } impl ParagraphPart { fn from_tagged_element( tagged_element: &Tagged, ) -> Result::Error> { let create: fn(String) -> Self = match tagged_element.name() { "constant" => Self::Constant, "function" => Self::Function, "parameter" => Self::Parameter, "emphasis" => Self::Emphasis, "code" => Self::Code, "inlineequation" => Self::InlineEquation, "programlisting" => Self::ProgramListing, "citerefentry" => Self::Entry, "variablelist" | "itemizedlist" | "informaltable" | "para" | "footnote" => { |_| { unreachable!(); } } _ => { return Err(::Error::UnknownPart( tagged_element.name().to_string(), )); } }; if tagged_element.name() == "citerefentry" { let title_element = tagged_element .child_elements() .get_first_tagged_with_name("refentrytitle") .ok_or(::Error::NoEntryTitleFound)?; let title = title_element .child_elements() .get_first_text_element() .ok_or(::Error::NoTextInTagged)?; return Ok(Self::Entry(title.clone())); } if tagged_element.name() == "variablelist" { let variable_list = VariableList::from_elements(tagged_element.child_elements())?; return Ok(Self::VariableList(variable_list)); } if tagged_element.name() == "itemizedlist" { let itemized_list = ItemizedList::from_elements(tagged_element.child_elements())?; return Ok(Self::ItemizedList(itemized_list)); } if tagged_element.name() == "informaltable" { let informal_table = InformalTable::from_elements( tagged_element.child_elements(), ) .map_err(|err| ParagraphPartError::InvalidInformalTable(Box::new(err)))?; return Ok(Self::InformalTable(informal_table)); } if tagged_element.name() == "inlineequation" { return Ok(Self::InlineEquation( tagged_element .child_elements() .into_iter() .map(ToString::to_string) .collect::(), )); } if tagged_element.name() == "para" { return Ok(Self::Paragraph( Paragraph::from_elements(tagged_element.child_elements()) .map_err(|err| ParagraphPartError::InvalidParagraph(Box::new(err)))?, )); } if tagged_element.name() == "footnote" { return Ok(Self::Footnote( Paragraph::from_elements(tagged_element.child_elements()) .map_err(|err| ParagraphPartError::InvalidFootnote(Box::new(err)))?, )); } let text_element = tagged_element .child_elements() .get_first_text_element() .ok_or(::Error::NoTextInTagged)?; Ok(create(text_element.clone())) } } /// [`ParagraphPart`] error. #[derive(Debug, thiserror::Error)] pub enum ParagraphPartError { /// Expected a text element. #[error("Expected a text element")] ExpectedTextElement, /// A input element is a unknown description part. #[error("Input element with name '{0}' is a unknown description part")] UnknownPart(String), /// No text was found in tagged input element. #[error("No text was found in tagged input element")] NoTextInTagged, /// No entry title found. #[error("No entry title found")] NoEntryTitleFound, /// Invalid variable list. #[error("Invalid variable list")] InvalidVariableList(#[from] VariableListError), /// Invalid itemized list. #[error("Invalid itemized list")] InvalidItemizedList(#[from] ItemizedListError), /// Invalid informal table. #[error("Invalid informal table")] InvalidInformalTable(#[source] Box), /// Invalid paragraph. #[error("Invalid paragraph")] InvalidParagraph(#[source] Box), /// Invalid footnote. #[error("Invalid footnote")] InvalidFootnote(#[source] Box), }