nom_kconfig/attribute/
help.rs

1use nom::bytes::complete::take_while;
2use nom::multi::fold_many0;
3use nom::Parser;
4use nom::{
5    branch::alt,
6    bytes::complete::{tag, take},
7    character::complete::{line_ending, newline, not_line_ending, space1},
8    combinator::{eof, map, opt, peek},
9    multi::many0,
10    sequence::{delimited, pair, preceded},
11    IResult,
12};
13
14use crate::{util::ws, KconfigInput};
15
16pub fn weirdo_help(input: KconfigInput) -> IResult<KconfigInput, KconfigInput> {
17    // TODO linux v-3.2, in file /drivers/net/ethernet/stmicro/stmmac/Kconfig
18    // TODO 3.4.110/drivers/net/ethernet/sfc/Kconfig
19    map(
20        delimited(
21            ws(opt(many0(tag("-")))),
22            ws(tag("help")),
23            opt(many0(alt((tag("-"), space1)))),
24        ),
25        |d| d,
26    )
27    .parse(input)
28}
29
30/// This parses a help text. The end of the help text is determined by the indentation level, this means it ends at the first line which has a smaller indentation than the first line of the help text.
31///
32/// # Example
33/// ```
34/// use nom_kconfig::{
35///     assert_parsing_eq,
36///     attribute::parse_help,
37/// };
38///
39/// assert_parsing_eq!(parse_help, "help\n   hello world", Ok(("", "hello world".to_string())))
40/// ```
41pub fn parse_help(input: KconfigInput) -> IResult<KconfigInput, String> {
42    // parse out help tag
43    let (input, _) = pair(
44        alt((
45            ws(tag("help")),
46            // TODO linux v-3.2, in file /drivers/net/ethernet/stmicro/stmmac/Kconfig
47            weirdo_help,
48        )),
49        preceded(many0(space1), newline),
50    )
51    .parse(input)?;
52
53    // parse the raw text
54    let (input, text) = parse_help_text(input)?;
55    Ok((input, text))
56}
57
58fn parse_help_text(input: KconfigInput) -> IResult<KconfigInput, String> {
59    let (original, initial_indentation_len) = peek_indentation(input)?;
60    if initial_indentation_len == 0 {
61        return Ok((original, String::new()));
62    }
63
64    // Remove initial indentation and parse first line
65    let (input, _) = take(initial_indentation_len)(original)?;
66    let (remaining, first_line) = parse_full_help_line(input)?;
67
68    // Parse subsequent lines while maintaining indentation
69    let (remaining, mut help_text) = fold_many0(
70        |i| {
71            let (orig, indent_len) = peek_indentation(i)?;
72
73            if (indent_len != 0) && (indent_len < initial_indentation_len) {
74                return Err(nom::Err::Error(nom::error::Error::new(
75                    orig,
76                    nom::error::ErrorKind::Fail, // Stop parsing when indentation decreases
77                )));
78            } else if indent_len == 0 {
79                // handle newlines between paragraph's
80                parse_newline_only(orig)
81            } else {
82                // Consume the same base indentation.
83                let (remain, _) = take(initial_indentation_len)(orig)?;
84                // Parse the raw help line text.
85                parse_full_help_line(remain)
86            }
87        },
88        String::new,
89        |mut acc, line| {
90            acc.push_str(&line);
91            acc
92        },
93    )
94    .parse(remaining)?;
95
96    help_text.insert_str(0, &first_line);
97
98    Ok((remaining, help_text.trim().to_string()))
99}
100
101fn parse_line_help(input: KconfigInput) -> IResult<KconfigInput, (KconfigInput, KconfigInput)> {
102    pair(not_line_ending, alt((line_ending, eof))).parse(input)
103}
104
105fn parse_full_help_line(input: KconfigInput) -> IResult<KconfigInput, String> {
106    let (input, (raw_text, line_end)) = parse_line_help(input)?;
107    let mut parsed_line = raw_text.to_string();
108    parsed_line.push_str(line_end.fragment());
109    Ok((input, parsed_line))
110}
111
112fn parse_newline_only(input: KconfigInput) -> IResult<KconfigInput, String> {
113    let (input, newline) = newline(input)?;
114    Ok((input, newline.to_string()))
115}
116fn peek_til_newline(s: KconfigInput) -> IResult<KconfigInput, (KconfigInput, KconfigInput)> {
117    peek(parse_line_help).parse(s)
118}
119
120fn peek_indentation(s: KconfigInput) -> IResult<KconfigInput, usize> {
121    let (original, peeked) = peek_til_newline(s)?;
122    let (_, len) = indentation_level(peeked.0)?;
123    Ok((original, len))
124}
125
126fn indentation_level(input: KconfigInput) -> IResult<KconfigInput, usize> {
127    // Assumes that tab and space usage is consistent across multi line text.
128    // E.g We don't know how much spaces a tab is.
129    //
130    // Example:
131    // help\n
132    //     Lorem Ipsum\n
133    // \t\tLorem Ipsum\n
134    //
135    // The above would consider the second text line to have less indentation( 4 white spaces vs 2 tabs as we only count chars) and thous only include the first line.
136    let (input, indent) = take_while(|c: char| c == ' ' || c == '\t')(input)?;
137    let len = indent.fragment().len();
138
139    Ok((input, (len)))
140}