metadata_gen/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(rustdoc::broken_intra_doc_links)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![doc = include_str!("../README.md")]
7#![doc(
8 html_favicon_url = "https://cloudcdn.pro/metadata-gen/v1/favicon.ico",
9 html_logo_url = "https://cloudcdn.pro/metadata-gen/v1/logos/metadata-gen.svg",
10 html_root_url = "https://docs.rs/metadata-gen"
11)]
12#![crate_name = "metadata_gen"]
13#![crate_type = "lib"]
14
15extern crate alloc;
16
17use alloc::string::{String, ToString};
18use alloc::vec::Vec;
19
20/// The `error` module contains error types for metadata processing.
21pub mod error;
22/// Synchronous readers and files (`std`).
23#[cfg(feature = "std")]
24#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
25pub mod io;
26/// The `metadata` module contains functions for extracting and processing metadata.
27pub mod metadata;
28/// The `metatags` module contains functions for generating meta tags.
29pub mod metatags;
30/// Async file helpers on the Tokio runtime (feature `tokio`).
31#[cfg(all(feature = "tokio", not(loom)))]
32#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
33pub mod tokio;
34/// The `utils` module contains utility functions for metadata processing.
35pub mod utils;
36
37pub use error::MetadataError;
38pub use metadata::{
39 detect_front_matter, extract_metadata, extract_metadata_with_body,
40 extract_metadata_with_limits, extract_typed,
41 extract_typed_borrowed, process_metadata, process_metadata_with,
42 DateOrder, FrontMatterFormat, Metadata, ParseLimits,
43 ProcessOptions,
44};
45pub use metatags::{generate_metatags, MetaTag, MetaTagGroups};
46#[cfg(all(feature = "tokio", not(loom)))]
47pub use utils::async_extract_metadata_from_file;
48pub use utils::{escape_attribute, escape_html};
49
50/// Type alias for a map of metadata key-value pairs.
51///
52/// With the `std` feature (the default) this is `std::collections::HashMap`;
53/// in a `no_std + alloc` build it is `alloc::collections::BTreeMap`, which
54/// has the same `get`, `insert`, `iter` and `contains_key` surface.
55///
56/// # Example
57///
58/// ```
59/// use metadata_gen::MetadataMap;
60///
61/// let mut map = MetadataMap::new();
62/// map.insert("title".to_string(), "My Page".to_string());
63/// assert_eq!(map.get("title"), Some(&"My Page".to_string()));
64/// ```
65#[cfg(feature = "std")]
66pub type MetadataMap = std::collections::HashMap<String, String>;
67/// Type alias for a map of metadata key-value pairs (`no_std` build).
68#[cfg(not(feature = "std"))]
69pub type MetadataMap = alloc::collections::BTreeMap<String, String>;
70
71/// Type alias for a list of keywords.
72///
73/// # Example
74///
75/// ```
76/// use metadata_gen::Keywords;
77///
78/// let keywords: Keywords = vec!["rust".to_string(), "metadata".to_string()];
79/// assert_eq!(keywords.len(), 2);
80/// ```
81pub type Keywords = Vec<String>;
82
83/// Type alias for the result of metadata extraction and processing.
84///
85/// # Example
86///
87/// ```
88/// use metadata_gen::{MetadataResult, extract_and_prepare_metadata};
89///
90/// let result: MetadataResult = extract_and_prepare_metadata("---\ntitle: Test\n---\n");
91/// assert!(result.is_ok());
92/// ```
93pub type MetadataResult =
94 Result<(MetadataMap, Keywords, MetaTagGroups), MetadataError>;
95
96/// Extracts metadata from the content, generates keywords based on the metadata,
97/// and prepares meta tag groups.
98///
99/// This function performs three key tasks:
100/// 1. It extracts metadata from the front matter of the content.
101/// 2. It generates keywords based on this metadata.
102/// 3. It generates various meta tags required for the page.
103///
104/// # Arguments
105///
106/// * `content` - A string slice representing the content from which to extract metadata.
107///
108/// # Returns
109///
110/// Returns a Result containing a tuple with:
111/// * [`MetadataMap`]: Extracted metadata
112/// * `Vec<String>`: A list of keywords
113/// * `MetaTagGroups`: A structure containing various meta tags
114///
115/// # Errors
116///
117/// This function will return a `MetadataError` if metadata extraction or processing fails.
118///
119/// # Example
120///
121/// ```
122/// use metadata_gen::extract_and_prepare_metadata;
123///
124/// let content = r#"---
125/// title: My Page
126/// description: A sample page
127/// ---
128/// # Content goes here
129/// "#;
130///
131/// let result = extract_and_prepare_metadata(content);
132/// assert!(result.is_ok());
133/// ```
134pub fn extract_and_prepare_metadata(content: &str) -> MetadataResult {
135 // Ensure the front matter format contains key-value separators
136 if !content.contains(':') && !content.contains('=') {
137 return Err(MetadataError::ExtractionError {
138 message: "No valid front matter found".to_string(),
139 });
140 }
141
142 let metadata = extract_metadata(content)?;
143 let metadata_map = metadata.into_inner();
144 let keywords = extract_keywords(&metadata_map);
145 let all_meta_tags = generate_metatags(&metadata_map);
146
147 Ok((metadata_map, keywords, all_meta_tags))
148}
149
150/// Extracts keywords from the metadata.
151///
152/// This function looks for a "keywords" key in the metadata and splits its value into a vector of strings.
153///
154/// # Arguments
155///
156/// * `metadata` - A reference to the [`MetadataMap`] holding the metadata.
157///
158/// # Returns
159///
160/// A vector of strings representing the keywords. Returns an empty vector if no keywords are found.
161///
162/// # Example
163///
164/// ```
165/// use metadata_gen::{extract_keywords, MetadataMap};
166///
167/// let mut metadata = MetadataMap::new();
168/// metadata.insert("keywords".to_string(), "rust, metadata, parsing".to_string());
169///
170/// let keywords = extract_keywords(&metadata);
171/// assert_eq!(keywords, vec!["rust", "metadata", "parsing"]);
172/// ```
173pub fn extract_keywords(metadata: &MetadataMap) -> Vec<String> {
174 metadata
175 .get("keywords")
176 .map(|k| k.split(',').map(|s| s.trim().to_string()).collect())
177 .unwrap_or_default()
178}
179
180#[cfg(all(test, feature = "std", feature = "yaml"))]
181mod tests {
182 use super::*;
183 use alloc::string::ToString;
184 use alloc::vec;
185
186 #[test]
187 fn test_extract_and_prepare_metadata() {
188 let content = r#"---
189title: Test Page
190description: A test page for metadata extraction
191keywords: test, metadata, extraction
192---
193# Test Content
194This is a test file for metadata extraction."#;
195
196 let result = extract_and_prepare_metadata(content);
197 assert!(result.is_ok());
198
199 let (metadata, keywords, meta_tags) = result.unwrap();
200 assert_eq!(
201 metadata.get("title"),
202 Some(&"Test Page".to_string())
203 );
204 assert_eq!(
205 metadata.get("description"),
206 Some(&"A test page for metadata extraction".to_string())
207 );
208 assert_eq!(keywords, vec!["test", "metadata", "extraction"]);
209 assert!(!meta_tags.primary.is_empty());
210 }
211
212 #[test]
213 fn test_extract_keywords() {
214 let mut metadata = MetadataMap::new();
215 metadata.insert(
216 "keywords".to_string(),
217 "rust, programming, metadata".to_string(),
218 );
219
220 let keywords = extract_keywords(&metadata);
221 assert_eq!(keywords, vec!["rust", "programming", "metadata"]);
222 }
223
224 #[test]
225 fn test_extract_keywords_empty() {
226 let metadata = MetadataMap::new();
227 let keywords = extract_keywords(&metadata);
228 assert!(keywords.is_empty());
229 }
230}