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
// Copyright 2015 Google Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//! Main public pull parse interface, running two passes over input.

use parse::{RawParser, Event, Tag, Options, OPTION_FIRST_PASS};
use std::collections::HashSet;

pub struct Parser<'a> {
    inner: RawParser<'a>,
    loose_lists: HashSet<usize>,
    loose_stack: Vec<bool>,
}

impl<'a> Parser<'a> {
    pub fn new(text: &'a str) -> Parser<'a> {
        Parser::new_ext(text, Options::empty())
    }
    pub fn new_ext(text: &'a str, mut opts: Options) -> Parser<'a> {
        opts.remove(OPTION_FIRST_PASS);
        // first pass, collecting info
        let first_opts = opts | OPTION_FIRST_PASS;
        let mut first = RawParser::new(text, first_opts);
        while first.next().is_some() { }
        let info = first.get_info();

        // second pass
        let loose_lists = info.loose_lists;
        let second = RawParser::new_with_links(text, opts, info.links);

        //println!("loose lists: {:?}", info.loose_lists);
        Parser {
            inner: second,
            loose_lists: loose_lists,
            loose_stack: Vec::new(),
        }
    }

    pub fn get_offset(&self) -> usize {
        self.inner.get_offset()
    }
}

impl<'a> Iterator for Parser<'a> {
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Event<'a>> {
        loop {
            match self.inner.next() {
                Some(event) => {
                    match event {
                        Event::Start(Tag::List(_)) => {
                            let offset = self.inner.get_offset();
                            let is_loose = self.loose_lists.contains(&offset);
                            self.loose_stack.push(is_loose);
                        }
                        Event::Start(Tag::BlockQuote) => {
                            self.loose_stack.push(true);
                        }
                        Event::Start(Tag::Paragraph) | Event::End(Tag::Paragraph) => {
                            if let Some(&false) = self.loose_stack.last() {
                                continue;
                            }
                        }
                        Event::End(Tag::List(_)) | Event::End(Tag::BlockQuote) => {
                            let _ = self.loose_stack.pop();
                        }
                        _ => ()
                    }
                    return Some(event)
                }
                None => return None
            }
        }
    }
}

// Note: there are currently no tests here, because so far there's been adequate coverage
// using the test cases from the CommonMark spec. Those can be run using:
//
//     cargo run -- -s spec.text
//
// where spec.txt is from <https://github.com/jgm/cmark/blob/master/test/spec.txt>.
//
// There are more cases that should be tested beyond that spec, and those should be added here.