1use crate::{util::ws, KconfigInput};
2use nom::bytes::complete::take_while;
3use nom::character::complete::space0;
4use nom::combinator::recognize;
5use nom::multi::fold_many0;
6use nom::sequence::terminated;
7use nom::Parser;
8use nom::{
9 branch::alt,
10 bytes::complete::{tag, take},
11 character::complete::{line_ending, newline, not_line_ending, space1},
12 combinator::{eof, map, opt, peek},
13 multi::many0,
14 sequence::delimited,
15 IResult,
16};
17
18pub fn weirdo_help(input: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
19 map(
22 delimited(
23 ws(opt(many0(tag("-")))),
24 ws(tag("help")),
25 opt(many0(alt((tag("-"), space1)))),
26 ),
27 |d| d,
28 )
29 .parse(input)
30}
31
32pub fn parse_help(input: KconfigInput) -> IResult<KconfigInput, String> {
44 let (input, _) = (
46 alt((
47 ws(tag("help")),
48 weirdo_help,
50 )),
51 many0(space1),
52 newline,
53 )
54 .parse(input)?;
55
56 let (input, text) = parse_help_text(input)?;
58 Ok((input, text))
59}
60
61fn parse_help_text(input: KconfigInput) -> IResult<KconfigInput, String> {
100 let (original, initial_indentation_len) = peek_initial_indentation(input)?;
101 if initial_indentation_len.chars == 0 {
102 return Ok((original, String::new()));
103 }
104
105 let (remaining, help_text) = fold_many0(
107 |i| {
108 let (orig, indent_len) = peek_indentation(i)?;
109 let peek_line = peek_til_newline(orig)?;
110
111 if peek_line.1.fragment().trim() == "" {
112 parse_newline_only(peek_line.0)
114 } else if indent_len < initial_indentation_len {
115 Err(nom::Err::Error(nom::error::Error::new(
117 peek_line.1,
118 nom::error::ErrorKind::Fail,
119 )))
120 } else {
121 let (remain, _) =
123 take(indent_len.chars.min(initial_indentation_len.chars))(peek_line.0)?;
124 parse_full_help_line(remain)
126 }
127 },
128 String::new,
144 |mut acc, line| {
145 acc.push_str(&line);
146 acc
147 },
148 )
149 .parse(original)?;
150
151 Ok((remaining, help_text.trim_end().to_string()))
152}
153
154fn parse_line_help(input: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
155 terminated(not_line_ending, opt(alt((line_ending, eof)))).parse(input)
157}
158
159fn parse_full_help_line(input: KconfigInput) -> IResult<KconfigInput, String> {
160 let (input, raw_text) = parse_line_help(input)?;
161 let mut parsed_line = raw_text.to_string();
162 parsed_line.push('\n');
163 Ok((input, parsed_line))
164}
165
166fn parse_newline_only(input: KconfigInput) -> IResult<KconfigInput, String> {
167 let (input, _newline) = recognize(terminated(space0, line_ending)).parse(input)?;
168 Ok((input, "\n".to_string()))
169}
170fn peek_til_newline(s: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
171 peek(parse_line_help).parse(s)
172}
173
174fn empty_line(s: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
176 recognize(terminated(space0, line_ending)).parse(s)
177}
178
179fn peek_first_non_empty_line(s: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
181 let (original, (_, non_empty_line)) = peek((many0(empty_line), not_line_ending)).parse(s)?;
182 Ok((original, non_empty_line))
183 }
186
187fn peek_initial_indentation(s: KconfigInput) -> IResult<KconfigInput, IndentationLevel> {
189 let (original, peeked) = peek_first_non_empty_line.parse(s)?;
190 let (_, len) = indentation_level(peeked)?;
191 Ok((original, len))
192}
193
194fn peek_indentation(s: KconfigInput) -> IResult<KconfigInput, IndentationLevel> {
195 let (original, peeked) = peek_til_newline(s)?;
196 let (_, len) = indentation_level(peeked)?;
197 Ok((original, len))
198}
199
200fn indentation_level(input: KconfigInput) -> IResult<KconfigInput, IndentationLevel> {
202 let (input, indent) = take_while(|c: char| c == ' ' || c == '\t')(input)?;
212
213 let mut computed = 0;
214 for c in indent.fragment().chars() {
215 match c {
216 '\t' => {
217 computed = (computed & !7) + 8;
218 }
219 ' ' => computed += 1,
220 _ => unreachable!(
221 "This should never happen because indentation only takes spaces and tabs"
222 ),
223 }
224 }
225
226 Ok((
227 input,
228 IndentationLevel {
229 chars: indent.fragment().len(),
230 computed,
231 },
232 ))
233}
234
235#[derive(PartialEq, Debug)]
236struct IndentationLevel {
237 chars: usize,
238 computed: usize,
239}
240
241impl PartialOrd for IndentationLevel {
243 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
244 self.computed.partial_cmp(&other.computed)
245 }
246}
247
248#[cfg(test)]
249use crate::assert_parsing_eq;
250#[test]
251fn test_peek_initial_indentation_first_empty_line() {
252 let input = r#"
253 This is a general notification"#;
254
255 assert_parsing_eq!(
256 peek_initial_indentation,
257 input,
258 Ok((
259 "\n\t This is a general notification",
260 IndentationLevel {
261 chars: 3,
262 computed: 10
263 }
264 ))
265 )
266}
267
268#[test]
269fn test_peek_initial_indentation() {
270 let input = r#" first word"#;
271 assert_parsing_eq!(
272 peek_initial_indentation,
273 input,
274 Ok((
275 " first word",
276 IndentationLevel {
277 chars: 4,
278 computed: 4
279 }
280 ))
281 )
282}
283
284#[test]
285fn test_indentation_level() {
286 assert_parsing_eq!(
287 indentation_level,
288 "\t",
289 Ok((
290 "",
291 IndentationLevel {
292 chars: 1,
293 computed: 8
294 }
295 ))
296 );
297 assert_parsing_eq!(
298 indentation_level,
299 " \t",
300 Ok((
301 "",
302 IndentationLevel {
303 chars: 2,
304 computed: 8
305 }
306 ))
307 );
308 assert_parsing_eq!(
309 indentation_level,
310 " \t",
311 Ok((
312 "",
313 IndentationLevel {
314 chars: 3,
315 computed: 8
316 }
317 ))
318 );
319 assert_parsing_eq!(
320 indentation_level,
321 " \t",
322 Ok((
323 "",
324 IndentationLevel {
325 chars: 9,
326 computed: 16
327 }
328 ))
329 );
330}
331
332#[test]
333fn test_peek_first_non_empty_line() {
334 let input = " \t\n\n hello";
335 let (remaining, line) = peek_first_non_empty_line
336 .parse(KconfigInput::from(input))
337 .unwrap();
338 assert_eq!(remaining.fragment(), &input);
339 assert_eq!(line.fragment(), &" hello");
340}
341
342#[test]
343fn test_parse_newline_only() {
344 assert_parsing_eq!(parse_newline_only, " \t\n", Ok(("", "\n".to_string())));
345}