1//! Type and helper to collect the gramamr and rule documentation.
2
3use pest::iterators::Pairs;
4use pest_meta::parser::Rule;
5use std::collections::HashMap;
6
7/// Abstraction for the grammer and rule doc.
8#[derive(Debug)]
9pub struct DocComment {
10 /// The grammar documentation is defined at the beginning of a file with //!.
11 pub grammar_doc: String,
12
13 /// HashMap for store all doc_comments for rules.
14 /// key is rule name, value is doc_comment.
15 pub line_docs: HashMap<String, String>,
16}
17
18/// Consume pairs to matches `Rule::grammar_doc`, `Rule::line_doc` into `DocComment`
19///
20/// e.g.
21///
22/// a pest file:
23///
24/// ```ignore
25/// //! This is a grammar doc
26/// /// line doc 1
27/// /// line doc 2
28/// foo = {}
29///
30/// /// line doc 3
31/// bar = {}
32/// ```
33///
34/// Then will get:
35///
36/// ```ignore
37/// grammar_doc = "This is a grammar doc"
38/// line_docs = { "foo": "line doc 1\nline doc 2", "bar": "line doc 3" }
39/// ```
40pub fn consume(pairs: Pairs<'_, Rule>) -> DocComment {
41 let mut grammar_doc = String::new();
42
43 let mut line_docs: HashMap<String, String> = HashMap::new();
44 let mut line_doc = String::new();
45
46 for pair in pairs {
47 match pair.as_rule() {
48 Rule::grammar_doc => {
49 // grammar_doc > inner_doc
50 let inner_doc = pair.into_inner().next().unwrap();
51 grammar_doc.push_str(inner_doc.as_str());
52 grammar_doc.push('\n');
53 }
54 Rule::grammar_rule => {
55 if let Some(inner) = pair.into_inner().next() {
56 // grammar_rule > line_doc | identifier
57 match inner.as_rule() {
58 Rule::line_doc => {
59 if let Some(inner_doc) = inner.into_inner().next() {
60 line_doc.push_str(inner_doc.as_str());
61 line_doc.push('\n');
62 }
63 }
64 Rule::identifier => {
65 if !line_doc.is_empty() {
66 let rule_name = inner.as_str().to_owned();
67
68 // Remove last \n
69 line_doc.pop();
70 line_docs.insert(rule_name, line_doc.clone());
71 line_doc.clear();
72 }
73 }
74 _ => (),
75 }
76 }
77 }
78 _ => (),
79 }
80 }
81
82 if !grammar_doc.is_empty() {
83 // Remove last \n
84 grammar_doc.pop();
85 }
86
87 DocComment {
88 grammar_doc,
89 line_docs,
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use std::collections::HashMap;
96
97 use pest_meta::parser;
98 use pest_meta::parser::Rule;
99
100 #[test]
101 fn test_doc_comment() {
102 let pairs = match parser::parse(Rule::grammar_rules, include_str!("../tests/test.pest")) {
103 Ok(pairs) => pairs,
104 Err(_) => panic!("error parsing tests/test.pest"),
105 };
106
107 let doc_comment = super::consume(pairs);
108
109 let mut expected = HashMap::new();
110 expected.insert("foo".to_owned(), "Matches foo str, e.g.: `foo`".to_owned());
111 expected.insert(
112 "bar".to_owned(),
113 "Matches bar str\n\n Indent 2, e.g: `bar` or `foobar`".to_owned(),
114 );
115 expected.insert(
116 "dar".to_owned(),
117 "Matches dar\n\nMatch dar description\n".to_owned(),
118 );
119 assert_eq!(expected, doc_comment.line_docs);
120
121 assert_eq!(
122 "A parser for JSON file.\nAnd this is a example for JSON parser.\n\n indent-4-space\n",
123 doc_comment.grammar_doc
124 );
125 }
126
127 #[test]
128 fn test_empty_grammar_doc() {
129 assert!(parser::parse(Rule::grammar_rules, "//!").is_ok());
130 assert!(parser::parse(Rule::grammar_rules, "///").is_ok());
131 assert!(parser::parse(Rule::grammar_rules, "//").is_ok());
132 assert!(parser::parse(Rule::grammar_rules, "/// Line Doc").is_ok());
133 assert!(parser::parse(Rule::grammar_rules, "//! Grammar Doc").is_ok());
134 assert!(parser::parse(Rule::grammar_rules, "// Comment").is_ok());
135 }
136}
137