extract-frontmatter 4.1.1

A library that allows a user to extract an arbitrary number of lines of 'front-matter' from the start of any string
Documentation
#![warn(clippy::cargo, clippy::nursery, clippy::pedantic)]

//! A Rust library that allows a user to extract an arbitrary number of lines of "front-matter" from the start of any
//! multiline string.
//!
//! Note that absolutely no parsing of extracted front-matter is performed; this is designed to output its results for
//! another library to then parse.
//!
//! See [`Extractor`] for how to use this library.
pub mod config;

mod extraction;
mod modification;

use crate::config::{Modifier, Splitter};
use crate::extraction::{
    extract_by_delimiter_line, extract_by_enclosing_lines, extract_by_line_index, extract_by_line_prefix,
};
use crate::modification::{strip_first_line, strip_last_line, strip_prefix, trim_whitespace};
use std::borrow::Cow;

/// Holds configuration for how front-matter and data should be extracted from a string.
///
/// 1. Instantiate using [`Extractor::new()`] with the desired [`Splitter`]
/// 2. (Optional) Call [`.with_modifier()`] with a desired [`Modifier`]; repeat as necessary
/// 3. Call [`.extract()`] with a string to get back a tuple of front-matter and data
///
/// [`.with_modifier()`]: Self::with_modifier
/// [`.extract()`]: Self::extract
///
/// # Usage
///
/// ## Example 1 (Markdown with TOML)
///
/// Input:
///
/// ```md
/// [meta]
/// field_one = 10
/// field_two = [2, 4]
/// +++
///
/// ## Markdown example
///
/// This is an example markdown document that contains the following TOML front-matter:
///
///     [meta]
///     field_one = 10
///     field_two = [2, 4]
/// ```
///
/// Code:
///
/// ```rust
/// # use extract_frontmatter::config::Splitter;
/// # use extract_frontmatter::Extractor;
/// # let input = include_str!("../resources/tests/example1/raw.md");
/// let (front_matter, data) = Extractor::new(Splitter::DelimiterLine("+++")).extract(input);
/// # assert_eq!(front_matter.trim(), include_str!("../resources/tests/example1/meta.toml").trim());
/// # assert_eq!(data.trim(), include_str!("../resources/tests/example1/data.md").trim());
/// ```
///
/// Front-matter output:
///
/// ```toml
/// [meta]
/// field_one = 10
/// field_two = [2, 4]
/// ```
///
/// Data output:
///
/// ```md
/// ## Markdown example
///
/// This is an example markdown document that contains the following TOML front-matter:
///
///     [meta]
///     field_one = 10
///     field_two = [2, 4]
/// ```
///
/// ## Example 2 (SQL with YAML)
///
/// Input:
///
/// ```sql
/// -- meta:
/// --   field_one: 10
/// --   field_two:
/// --     - 2
/// --     - 4
/// SELECT version();
/// ```
///
/// Code:
///
/// ```rust
/// # use extract_frontmatter::config::{Modifier, Splitter};
/// # use extract_frontmatter::Extractor;
/// # let input = include_str!("../resources/tests/example2/raw.sql");
/// let (front_matter, data) = Extractor::new(Splitter::LinePrefix("-- "))
///     .with_modifier(Modifier::StripPrefix("-- "))
///     .extract(input);
/// # assert_eq!(front_matter.trim(), include_str!("../resources/tests/example2/meta.yml").trim());
/// # assert_eq!(data.trim(), include_str!("../resources/tests/example2/data.sql").trim());
/// ```
///
/// Front-matter output:
///
/// ```yaml
/// meta:
///   field_one: 10
///   field_two:
///     - 2
///     - 4
/// ```
///
/// Data output:
///
/// ```sql
/// SELECT version();
/// ```
#[derive(Debug)]
pub struct Extractor<'opt> {
    splitter: Splitter<'opt>,
    modifiers: Vec<Modifier<'opt>>,
}

impl<'opt> Extractor<'opt> {
    /// Instantiate a new [`Extractor`] config.
    #[must_use]
    pub const fn new(splitter: Splitter<'opt>) -> Self {
        Self { splitter, modifiers: Vec::new() }
    }

    /// Add a modifier to the front-matter returned by [`.extract()`](Self::extract).
    pub fn with_modifier(&mut self, modifier: Modifier<'opt>) -> &mut Self {
        self.modifiers.push(modifier);

        self
    }

    /// Split the given input string into a tuple of front-matter and data, applying any modifiers specified with
    /// [`.with_modifier()`].
    ///
    /// [`.with_modifier()`]:Self::with_modifier
    #[must_use]
    pub fn extract<'input>(&self, input: &'input str) -> (Cow<'input, str>, &'input str) {
        let (meta, data) = match self.splitter {
            Splitter::LineIndex(index) => extract_by_line_index(input, index),
            Splitter::DelimiterLine(delim) => extract_by_delimiter_line(input, delim),
            Splitter::LinePrefix(prefix) => extract_by_line_prefix(input, prefix),
            Splitter::EnclosingLines(delim) => extract_by_enclosing_lines(input, delim),
        };

        let mut meta = Cow::from(meta);

        for modifier in &self.modifiers {
            meta = Cow::from(match modifier {
                Modifier::StripPrefix(prefix) => strip_prefix(&meta, prefix),
                Modifier::TrimWhitespace => trim_whitespace(&meta),
                Modifier::StripFirstLine => strip_first_line(&meta),
                Modifier::StripLastLine => strip_last_line(&meta),
            });
        }

        (meta, data)
    }
}