1use std::fs::File;
2use std::io::prelude::*;
3use std::io::BufReader;
4use std::path::PathBuf;
5
6use crate::types::{BlockType, DocItem};
7
8#[derive(PartialEq)]
9enum Directive {
10 Continue,
11 Stop,
12}
13
14pub fn parse_hcl(filename: PathBuf) -> std::io::Result<Vec<DocItem>> {
15 let file = File::open(filename)?;
16 let buf_reader = BufReader::new(file);
17 let mut result = vec![DocItem::new()];
18 for line in buf_reader.lines() {
19 let state = parse_line(line?, result.pop().unwrap());
20 result.push(state.0);
21 if state.1 == Directive::Stop {
22 result.push(DocItem::new());
23 }
24 }
25 result.pop(); result = result
27 .into_iter()
28 .filter(|i| {
29 if i.category == BlockType::Comment {
30 if let Some(line) = i.description.first() {
31 return line.starts_with("Title: ");
32 }
33 }
34 true
35 })
36 .collect();
37 Ok(result)
38}
39
40fn parse_line(line: String, mut result: DocItem) -> (DocItem, Directive) {
41 match get_line_variant(&line) {
42 BlockType::Resource => parse_regular(line, result, BlockType::Resource, &parse_resource),
43 BlockType::Output => parse_regular(line, result, BlockType::Output, &parse_interface),
44 BlockType::Variable => parse_regular(line, result, BlockType::Variable, &parse_interface),
45 BlockType::Comment => (parse_comment(line, result), Directive::Continue),
46 BlockType::None => {
47 if (line.starts_with('}') && result.category != BlockType::None)
49 || (line.trim().len() == 0 && result.category == BlockType::Comment)
50 {
51 return (result, Directive::Stop);
52 }
53 if (result.category == BlockType::Variable || result.category == BlockType::Output)
55 && line.trim().starts_with("description")
56 {
57 if let Some(description) = parse_description(&line) {
58 result.description.push(String::from(description));
59 }
60 }
61 (result, Directive::Continue)
62 }
63 }
64}
65
66fn get_line_variant(line: &str) -> BlockType {
67 let variants = vec![
68 ("resource ", BlockType::Resource),
69 ("variable ", BlockType::Variable),
70 ("output ", BlockType::Output),
71 ("#", BlockType::Comment),
72 ];
73 for variant in variants {
74 if line.starts_with(variant.0) {
75 return variant.1;
76 }
77 }
78 BlockType::None
79}
80
81fn parse_regular(
82 line: String,
83 mut result: DocItem,
84 category: BlockType,
85 parser_function: &Fn(&str) -> String,
86) -> (DocItem, Directive) {
87 result.category = category;
88 result.name = parser_function(&line);
89 match line.trim().ends_with('}') {
90 true => (result, Directive::Stop),
91 false => (result, Directive::Continue),
92 }
93}
94
95fn parse_resource(line: &str) -> String {
96 line.split_whitespace()
97 .skip(1)
98 .take(2)
99 .map(|s| s.trim_matches('"'))
100 .collect::<Vec<&str>>()
101 .join(".")
102}
103
104fn parse_interface(line: &str) -> String {
105 let result = line
107 .split_whitespace()
108 .skip(1)
109 .take(1)
110 .map(|s| s.trim_matches('"'))
111 .collect::<Vec<&str>>()[0];
112 String::from(result)
113}
114
115fn parse_description(line: &str) -> Option<&str> {
116 let start = line.find('"')?;
117 let substring = line.get(start..)?;
118 Some(substring.trim_matches('"'))
119}
120
121fn parse_comment(line: String, mut result: DocItem) -> DocItem {
122 let parsed = line.trim_start_matches('#').trim();
123 if parsed.len() > 0 {
124 result.category = BlockType::Comment;
125 result.description.push(String::from(parsed));
126 }
127 result
128}
129
130#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn get_line_variant_resource() {
140 let line = r#"resource "foo" "bar" {"#;
141 match get_line_variant(line) {
142 BlockType::Resource => {}
143 _ => panic!("Type error!"),
144 }
145 }
146
147 #[test]
148 fn get_line_variant_output() {
149 let line = r#"output "foo" {"#;
150 match get_line_variant(line) {
151 BlockType::Output => {}
152 _ => panic!("Type error!"),
153 }
154 }
155
156 #[test]
157 fn get_line_variant_variable() {
158 let line = r#"variable "foo" {"#;
159 match get_line_variant(line) {
160 BlockType::Variable => {}
161 _ => panic!("Type error!"),
162 }
163 }
164
165 #[test]
166 fn get_line_variant_comment() {
167 let line = r#"# foo"#;
168 match get_line_variant(line) {
169 BlockType::Comment => {}
170 _ => panic!("Type error!"),
171 }
172 }
173
174 #[test]
175 fn get_line_variant_comment2() {
176 let line = r#"#foo"#;
177 match get_line_variant(line) {
178 BlockType::Comment => {}
179 _ => panic!("Type error!"),
180 }
181 }
182
183 #[test]
184 fn get_line_variant_none() {
185 let line = r#" foo"#;
186 match get_line_variant(line) {
187 BlockType::None => {}
188 _ => panic!("Type error!"),
189 }
190 }
191
192 #[test]
193 fn test_parse_resource() {
194 let line = r#"resource "foo" "bar" {"#;
195 assert_eq!(parse_resource(line), "foo.bar".to_string());
196 }
197
198 #[test]
199 fn test_parse_output() {
200 let line = r#"output "foo" {"#;
201 assert_eq!(parse_interface(line), "foo");
202 }
203
204 #[test]
205 fn test_parse_variable() {
206 let line = r#"variable "foo" {"#;
207 assert_eq!(parse_interface(line), "foo");
208 }
209
210 #[test]
211 fn test_parse_description() {
212 let line = r#" description = "foo bar""#;
213 assert_eq!(parse_description(line), Some("foo bar"));
214 }
215
216 #[test]
217 fn test_parse_comment() {
218 let line = String::from(r#"# foo bar"#);
219 let result = DocItem::new();
220 assert_eq!(parse_comment(line, result).description[0], "foo bar");
221 }
222
223 #[test]
224 fn test_parse_comment2() {
225 let line = String::from(r#"#foo bar"#);
226 let result = DocItem::new();
227 assert_eq!(parse_comment(line, result).description[0], "foo bar");
228 }
229}