From 5ab5fafd8f3cab7f82c11e7ad89f8fefd66e911c Mon Sep 17 00:00:00 2001
From: HampusM <hampus@hampusmat.com>
Date: Sun, 26 Feb 2023 14:45:20 +0100
Subject: feat: add multiple description support

---
 src/description.rs | 41 ++++++++++++++++++++++++++++++++++++++---
 src/lib.rs         | 46 ++++++++++++++++++++++++++--------------------
 src/xml/element.rs | 40 ++++++++++++++++++++--------------------
 3 files changed, 84 insertions(+), 43 deletions(-)

diff --git a/src/description.rs b/src/description.rs
index f536b4f..ccd95b2 100644
--- a/src/description.rs
+++ b/src/description.rs
@@ -7,16 +7,27 @@ use crate::xml::element::{Elements, FromElements, Tagged};
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Description
 {
+    for_function: Option<String>,
     parts: Vec<Part>,
 }
 
 impl Description
 {
-    /// Returns a new `ReferenceDescription`.
+    /// Returns a new empty `Description`.
     #[must_use]
     pub fn new() -> Self
     {
-        Self { parts: Vec::new() }
+        Self {
+            for_function: None,
+            parts: Vec::new(),
+        }
+    }
+
+    /// Returns what function this description is specific for.
+    #[must_use]
+    pub fn for_function(&self) -> &Option<String>
+    {
+        &self.for_function
     }
 
     /// Returns the description's parts.
@@ -41,6 +52,27 @@ impl FromElements for Description
 
     fn from_elements(elements: &Elements) -> Result<Self, Self::Error>
     {
+        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()
@@ -61,7 +93,10 @@ impl FromElements for Description
             })
             .collect::<Result<Vec<_>, Self::Error>>()?;
 
-        Ok(Description { parts })
+        Ok(Description {
+            for_function,
+            parts,
+        })
     }
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index c0a300b..7a715f1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,7 +10,7 @@ use std::os::unix::prelude::OsStrExt;
 use include_dir::{include_dir, Dir};
 
 use crate::description::{Description, Error as DescriptionError};
-use crate::xml::element::{Attribute, Elements, FromElements};
+use crate::xml::element::{Elements, FromElements};
 use crate::xml::parser::{Error as ParserError, Parser};
 
 pub mod description;
@@ -26,17 +26,20 @@ static GL4_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/OpenGL-Refpages/gl4");
 pub struct ReferenceEntry
 {
     purpose: String,
-    description: Description,
+    descriptions: Vec<Description>,
 }
 
 impl ReferenceEntry
 {
     /// Returns a new `ReferenceEntry`.
-    pub fn new(purpose: &impl ToString, description: Description) -> Self
+    pub fn new(
+        purpose: &impl ToString,
+        descriptions: impl IntoIterator<Item = Description>,
+    ) -> Self
     {
         Self {
             purpose: purpose.to_string(),
-            description,
+            descriptions: descriptions.into_iter().collect(),
         }
     }
 
@@ -106,11 +109,11 @@ impl ReferenceEntry
         &self.purpose
     }
 
-    /// Returns the reference entry description.
+    /// Returns the reference entry descriptions.
     #[must_use]
-    pub fn description(&self) -> &Description
+    pub fn descriptions(&self) -> &[Description]
     {
-        &self.description
+        &self.descriptions
     }
 }
 
@@ -140,23 +143,26 @@ impl FromElements for ReferenceEntry
             .cloned()
             .unwrap_or_default();
 
-        let description_refsect = refentry_element
+        let description_elements = refentry_element
             .child_elements()
-            .get_first_tagged_with_name_and_attr(
-                "refsect1",
-                &Attribute {
-                    key: "xml:id".to_string(),
-                    value: b"description".to_vec(),
-                },
-            )
-            .ok_or(Self::Error::MissingDescriptionRefSect)?;
-
-        let description =
-            Description::from_elements(description_refsect.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::<Result<Vec<_>, _>>()?;
 
         Ok(ReferenceEntry {
             purpose,
-            description,
+            descriptions,
         })
     }
 }
diff --git a/src/xml/element.rs b/src/xml/element.rs
index b778dac..91e4130 100644
--- a/src/xml/element.rs
+++ b/src/xml/element.rs
@@ -24,26 +24,6 @@ impl Elements
         })
     }
 
-    pub fn get_first_tagged_with_name_and_attr(
-        &self,
-        tag_name: &str,
-        attribute: &Attribute,
-    ) -> Option<&Tagged>
-    {
-        self.elements.iter().find_map(|element| match element {
-            Element::Tagged(tagged_element)
-                if tagged_element.name == tag_name
-                    && tagged_element
-                        .attributes
-                        .iter()
-                        .any(|attr| attr == attribute) =>
-            {
-                Some(tagged_element)
-            }
-            _ => None,
-        })
-    }
-
     pub fn get_all_tagged_elements(&self) -> Vec<&Tagged>
     {
         self.elements
@@ -68,6 +48,26 @@ impl Elements
             .collect()
     }
 
+    pub fn get_all_tagged_with_name_and_attr(
+        &self,
+        tag_name: &str,
+        attr_is_match: fn(&Attribute) -> bool,
+    ) -> Vec<&Tagged>
+    {
+        self.elements
+            .iter()
+            .filter_map(|element| match element {
+                Element::Tagged(tagged_element)
+                    if tagged_element.name == tag_name
+                        && tagged_element.attributes.iter().any(attr_is_match) =>
+                {
+                    Some(tagged_element)
+                }
+                _ => None,
+            })
+            .collect()
+    }
+
     pub fn get_first_text_element(&self) -> Option<&String>
     {
         self.elements.iter().find_map(|element| match element {
-- 
cgit v1.2.3-18-g5258