1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::selector::Selector;
use std::collections::BTreeSet;
use crate::modifier::Modifier;

/// Extracts front-matter depending on how it is configured.
///
/// Once instantiated, call one of the available `select_by_*()` methods, optionally call any of the other modifier methods, and then finally call `extract()` to get a copy of the front-matter.
///
/// Calling multiple `select_by_*()` methods will result in only the latest call taking effect.
///
/// Each modifier can only be set once and are applied in a specific order (though setting them can be done in any order):
/// - `discard_first_line`
/// - `discard_last_line`
/// - `strip_prefix`
/// - `strip_whitespace`
///
/// Attempting to set the same modifier multiple times will result in only one call taking effect (with which one being arbitrary).
#[derive(Debug)]
pub struct Extractor<'a, 'b, 'c> {
    input: &'a str,
    selector: Selector<'b>,
    modifiers: BTreeSet<Modifier<'c>>,
}

impl<'a, 'b, 'c> Extractor<'a, 'b, 'c> {
    /// Instantiates a new front-matter `Extractor` for the given `&str`
    pub fn new(input: &'a str) -> Self {
        Extractor {
            input,
            selector: Selector::All,
            modifiers: BTreeSet::new(),
        }
    }

    /// Using the configured selectors and modifiers, return a copy of `input`'s front-matter as a `String`
    pub fn extract(&self) -> String {
        self.collect().join("\n")
    }

    /// Using the configured selectors and modifiers, collect lines of front-matter into a `Vec<&str>`
    pub fn collect(&self) -> Vec<&str> {
        let mut lines: Vec<&str> = match self.selector {
            Selector::All =>
                self.input.lines().collect(),
            Selector::Terminator(terminator) =>
                self.input.lines().take_while(|l| l != &terminator).collect(),
            Selector::Prefix(prefix) =>
                self.input.lines().take_while(|l| l.starts_with(prefix)).collect(),
            Selector::LineCount(count) =>
                self.input.lines().take(count).collect()
        };

        for modifier in &self.modifiers {
            match modifier {
                Modifier::DiscardFirstLine => {
                    lines.remove(0);
                }
                Modifier::DiscardLastLine => {
                    lines.pop();
                }
                Modifier::StripPrefix(prefix) => {
                    lines = lines.iter().map(|l| l.trim_start_matches(prefix)).collect();
                }
                Modifier::StripWhitespace => {
                    lines = lines.iter().map(|l| l.trim()).collect();
                }
            }
        }

        lines
    }

    /// Updates the selector used by the `Extractor` to select lines until the first equal to `terminator` is found
    pub fn select_by_terminator(&mut self, terminator: &'b str) -> &mut Self {
        self.selector = Selector::Terminator(terminator);
        self
    }

    /// Updates the selector used by the `Extractor` to select consecutive lines starting with `prefix`
    pub fn select_by_prefix(&mut self, prefix: &'b str) -> &mut Self {
        self.selector = Selector::Prefix(prefix);
        self
    }

    /// Updates the selector used by the `Extractor` to select `count` lines
    pub fn select_by_line_count(&mut self, count: usize) -> &mut Self {
        self.selector = Selector::LineCount(count);
        self
    }

    /// Adds a modifier to discard the first line that would've been returned
    pub fn discard_first_line(&mut self) -> &mut Self {
        self.modifiers.insert(Modifier::DiscardFirstLine);
        self
    }

    /// Adds a modifier to discard the last line that would've been returned
    pub fn discard_last_line(&mut self) -> &mut Self {
        self.modifiers.insert(Modifier::DiscardLastLine);
        self
    }

    /// Adds a modifier to strip the the given `prefix` from all returned lines
    pub fn strip_prefix(&mut self, prefix: &'c str) -> &mut Self {
        self.modifiers.insert(Modifier::StripPrefix(prefix));
        self
    }

    /// Adds a modifier to strip leading and trailing whitespace from all returned lines
    pub fn strip_whitespace(&mut self) -> &mut Self {
        self.modifiers.insert(Modifier::StripWhitespace);
        self
    }
}