extract_frontmatter/lib.rs
1#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)]
2
3//! A Rust library that allows a user to extract an arbitrary number of lines of "front-matter" from the start of any
4//! multiline string.
5//!
6//! Note that absolutely no parsing of extracted front-matter is performed; this is designed to output its results for
7//! another library to then parse.
8//!
9//! See [`Extractor`] for how to use this library.
10pub mod config;
11
12mod extraction;
13mod modification;
14
15use crate::config::{Modifier, Splitter};
16use crate::extraction::{
17 extract_by_delimiter_line, extract_by_enclosing_lines, extract_by_line_index, extract_by_line_prefix,
18};
19use crate::modification::{strip_first_line, strip_last_line, strip_prefix, trim_whitespace};
20use std::borrow::Cow;
21
22/// Holds configuration for how front-matter and data should be extracted from a string.
23///
24/// 1. Instantiate using [`Extractor::new()`] with the desired [`Splitter`]
25/// 2. (Optional) Call [`.with_modifier()`] with a desired [`Modifier`]; repeat as necessary
26/// 3. Call [`.extract()`] with a string to get back a tuple of front-matter and data
27///
28/// [`.with_modifier()`]: Self::with_modifier
29/// [`.extract()`]: Self::extract
30///
31/// # Usage
32///
33/// ## Example 1 (Markdown with TOML)
34///
35/// Input:
36///
37/// ```md
38/// [meta]
39/// field_one = 10
40/// field_two = [2, 4]
41/// +++
42///
43/// ## Markdown example
44///
45/// This is an example markdown document that contains the following TOML front-matter:
46///
47/// [meta]
48/// field_one = 10
49/// field_two = [2, 4]
50/// ```
51///
52/// Code:
53///
54/// ```rust
55/// # use extract_frontmatter::config::Splitter;
56/// # use extract_frontmatter::Extractor;
57/// # let input = include_str!("../resources/tests/example1/raw.md");
58/// let (front_matter, data) = Extractor::new(Splitter::DelimiterLine("+++")).extract(input);
59/// # assert_eq!(front_matter.trim(), include_str!("../resources/tests/example1/meta.toml").trim());
60/// # assert_eq!(data.trim(), include_str!("../resources/tests/example1/data.md").trim());
61/// ```
62///
63/// Front-matter output:
64///
65/// ```toml
66/// [meta]
67/// field_one = 10
68/// field_two = [2, 4]
69/// ```
70///
71/// Data output:
72///
73/// ```md
74/// ## Markdown example
75///
76/// This is an example markdown document that contains the following TOML front-matter:
77///
78/// [meta]
79/// field_one = 10
80/// field_two = [2, 4]
81/// ```
82///
83/// ## Example 2 (SQL with YAML)
84///
85/// Input:
86///
87/// ```sql
88/// -- meta:
89/// -- field_one: 10
90/// -- field_two:
91/// -- - 2
92/// -- - 4
93/// SELECT version();
94/// ```
95///
96/// Code:
97///
98/// ```rust
99/// # use extract_frontmatter::config::{Modifier, Splitter};
100/// # use extract_frontmatter::Extractor;
101/// # let input = include_str!("../resources/tests/example2/raw.sql");
102/// let (front_matter, data) = Extractor::new(Splitter::LinePrefix("-- "))
103/// .with_modifier(Modifier::StripPrefix("-- "))
104/// .extract(input);
105/// # assert_eq!(front_matter.trim(), include_str!("../resources/tests/example2/meta.yml").trim());
106/// # assert_eq!(data.trim(), include_str!("../resources/tests/example2/data.sql").trim());
107/// ```
108///
109/// Front-matter output:
110///
111/// ```yaml
112/// meta:
113/// field_one: 10
114/// field_two:
115/// - 2
116/// - 4
117/// ```
118///
119/// Data output:
120///
121/// ```sql
122/// SELECT version();
123/// ```
124#[derive(Debug)]
125pub struct Extractor<'opt> {
126 splitter: Splitter<'opt>,
127 modifiers: Vec<Modifier<'opt>>,
128}
129
130impl<'opt> Extractor<'opt> {
131 /// Instantiate a new [`Extractor`] config.
132 #[must_use]
133 pub const fn new(splitter: Splitter<'opt>) -> Self {
134 Self { splitter, modifiers: Vec::new() }
135 }
136
137 /// Add a modifier to the front-matter returned by [`.extract()`](Self::extract).
138 pub fn with_modifier(&mut self, modifier: Modifier<'opt>) -> &mut Self {
139 self.modifiers.push(modifier);
140
141 self
142 }
143
144 /// Split the given input string into a tuple of front-matter and data, applying any modifiers specified with
145 /// [`.with_modifier()`].
146 ///
147 /// [`.with_modifier()`]:Self::with_modifier
148 #[must_use]
149 pub fn extract<'input>(&self, input: &'input str) -> (Cow<'input, str>, &'input str) {
150 let (meta, data) = match self.splitter {
151 Splitter::LineIndex(index) => extract_by_line_index(input, index),
152 Splitter::DelimiterLine(delim) => extract_by_delimiter_line(input, delim),
153 Splitter::LinePrefix(prefix) => extract_by_line_prefix(input, prefix),
154 Splitter::EnclosingLines(delim) => extract_by_enclosing_lines(input, delim),
155 };
156
157 let mut meta = Cow::from(meta);
158
159 for modifier in &self.modifiers {
160 meta = Cow::from(match modifier {
161 Modifier::StripPrefix(prefix) => strip_prefix(&meta, prefix),
162 Modifier::TrimWhitespace => trim_whitespace(&meta),
163 Modifier::StripFirstLine => strip_first_line(&meta),
164 Modifier::StripLastLine => strip_last_line(&meta),
165 });
166 }
167
168 (meta, data)
169 }
170}