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
/// TextSlide represents a standard, non-image slide.
/// It contains a ``text`` field with the text of the slide.
pub struct TextSlide {
    text: String
}

impl TextSlide {
    fn new(text: &str) -> TextSlide {
        TextSlide { text: String::from(text) }
    }
}


/// ImageSlide contains an ``image_path`` property that represents the location of the image on disk.
pub struct ImageSlide {
    image_path: String
}

impl ImageSlide {
    fn new(text: & str) -> ImageSlide {
        let path = &text[1..];

        ImageSlide { image_path: String::from(path) }
    }
}

/// There are three types of slides. ``TextSlide``, ``ImageSlide``, and ``EmptySlide``.
/// Empty slides are meant to be blank, with no text or images.
pub enum Slide {
    TextSlide(TextSlide),
    ImageSlide(ImageSlide),
    EmptySlide
}

/// This function accepts a string and returns a ``Vec`` containing ``Slide`` instances.
/// ``generate_slides`` is very un-opinionated, meaning you can use it in a lot more ways than
/// the classic Suckless sent program. Currently, programs using this crate must implement
/// display functionality for ``TextSlide``, ``ImageSlide``, and ``EmptySlide``.
pub fn generate_slides(full_text: String) -> Vec<Slide> {
    // Lines starting with # are comments, so the parser ignores them
    let text_minus_comments: String = full_text
        .lines()
        .filter(|&line| line.len() == 0 || line.chars().nth(0).unwrap() != '#')
        .map(|line | format!("{}\n", line))
        .collect();

    // Get the text for each slide, with newlines stripped
    let slides_text: Vec<&str> = text_minus_comments
        .split("\n\n")
        .map(|s| s.trim())
        .collect();

    // Put the slides together in the slide struct
    let mut slides: Vec<Slide> = Vec::new();
    for s in slides_text {
        match s.chars().nth(0).unwrap() {
            // Starting with @ denotes an image slide with a path (e.g. @nyan.png)
            '@' => slides.push(Slide::ImageSlide(ImageSlide::new(s))),
            // Lines beginning with / are empty slides, used e.g. for pacing or section divisions
            '/' => slides.push(Slide::EmptySlide),
            // Everything else is just regular text
            _ => slides.push(Slide::TextSlide(TextSlide::new(s)))
        }
    };

    slides
}

#[cfg(test)]
mod tests {
    use crate::generate_slides;

    #[test]
    fn buncha_slides() {
        let presentation = String::from("
* test
* test
* test

This is another slide.

Another one!

@image.png
");
        let slides = generate_slides(presentation);
        assert_eq!(slides.len(), 4)
    }

    #[test]
    fn comments_work() {
        let presentation = String::from("
* test
* test
* test

# This is a comment.

Another one!

@image.png
");
    let slides = generate_slides(presentation);
    assert_eq!(slides.len(), 3)
    }
}