bpaf_derive/
utils.rs

1use syn::{
2    parenthesized,
3    parse::{Parse, ParseStream},
4    token, Attribute, Expr, LitChar, LitStr, Result,
5};
6
7pub(crate) fn parse_arg<T: Parse>(input: ParseStream) -> Result<T> {
8    let content;
9    let _ = parenthesized!(content in input);
10    content.parse::<T>()
11}
12
13pub(crate) fn parse_opt_arg<T: Parse>(input: ParseStream) -> Result<Option<T>> {
14    if input.peek(token::Paren) {
15        let content;
16        let _ = parenthesized!(content in input);
17        Ok(Some(content.parse::<T>()?))
18    } else {
19        Ok(None)
20    }
21}
22
23pub(crate) fn parse_arg2<A: Parse, B: Parse>(input: ParseStream) -> Result<(A, B)> {
24    let content;
25    let _ = parenthesized!(content in input);
26    let a = content.parse::<A>()?;
27    let _ = content.parse::<token::Comma>()?;
28    let b = content.parse::<B>()?;
29    Ok((a, b))
30}
31
32#[inline(never)]
33pub(crate) fn parse_lit_char(input: ParseStream) -> Result<LitChar> {
34    parse_arg(input)
35}
36
37#[inline(never)]
38pub(crate) fn parse_lit_str(input: ParseStream) -> Result<LitStr> {
39    parse_arg(input)
40}
41
42#[inline(never)]
43pub(crate) fn parse_expr(input: ParseStream) -> Result<Box<Expr>> {
44    Ok(Box::new(parse_arg(input)?))
45}
46
47pub(crate) fn parse_opt_metavar(input: ParseStream) -> Result<Option<LitStr>> {
48    let content;
49    Ok(if input.peek(syn::token::Paren) {
50        let _ = parenthesized!(content in input);
51        Some(content.parse::<LitStr>()?)
52    } else {
53        None
54    })
55}
56
57pub(crate) fn doc_comment(attr: &Attribute) -> Option<String> {
58    match &attr.meta {
59        syn::Meta::NameValue(syn::MetaNameValue {
60            value:
61                syn::Expr::Lit(syn::ExprLit {
62                    lit: syn::Lit::Str(s),
63                    ..
64                }),
65            ..
66        }) => {
67            let mut s = s.value();
68            if s.starts_with(' ') {
69                s = s[1..].to_string();
70            }
71            Some(s)
72        }
73        _ => None,
74    }
75}
76
77pub(crate) fn to_snake_case(input: &str) -> String {
78    to_custom_case(input, '_')
79}
80
81pub(crate) fn to_kebab_case(input: &str) -> String {
82    to_custom_case(input, '-')
83}
84
85pub(crate) fn to_custom_case(input: &str, sep: char) -> String {
86    let mut res = String::with_capacity(input.len() * 2);
87    for c in input.strip_prefix("r#").unwrap_or(input).chars() {
88        if c.is_ascii_uppercase() {
89            if !res.is_empty() {
90                res.push(sep);
91            }
92            res.push(c.to_ascii_lowercase());
93        } else if c == '-' || c == '_' {
94            res.push(sep);
95        } else {
96            res.push(c);
97        }
98    }
99    res
100}
101
102#[test]
103fn check_to_snake_case() {
104    assert_eq!(to_snake_case("Foo"), "foo");
105    assert_eq!(to_snake_case("FooBar"), "foo_bar");
106    assert_eq!(to_snake_case("FOO"), "f_o_o");
107    assert_eq!(to_snake_case("r#in"), "in");
108}
109
110/// Contains a slice of strings that used to represent doc comment lines
111/// And perform following operations:
112///
113/// - adjacent non empty strings are combined: with a single line newline:
114///   ["foo", "bar"] => ["foo\nbar"]
115/// - single empty lines are stripped and used to represent logical blocks:
116///   ["foo", "bar", "", "baz"] => ["foo\nbar", "baz"]
117/// strip single empty lines,
118pub(crate) struct LineIter<'a> {
119    strings: std::str::Lines<'a>,
120    prev_empty: bool,
121    current: String,
122}
123
124impl<'a> LineIter<'a> {
125    fn take(&mut self) -> String {
126        let mut string = String::new();
127        self.current.truncate(self.current.trim_end().len());
128        std::mem::swap(&mut self.current, &mut string);
129        string
130    }
131
132    pub(crate) fn rest(&mut self) -> Option<String> {
133        let mut res = String::new();
134        for t in self {
135            if !res.is_empty() {
136                res.push('\n');
137            }
138            res.push_str(&t);
139        }
140        if res.is_empty() {
141            None
142        } else {
143            Some(res)
144        }
145    }
146}
147
148impl<'a> From<&'a str> for LineIter<'a> {
149    fn from(strings: &'a str) -> Self {
150        Self {
151            strings: strings.lines(),
152            prev_empty: false,
153            current: String::new(),
154        }
155    }
156}
157
158impl Iterator for LineIter<'_> {
159    type Item = String;
160
161    fn next(&mut self) -> Option<Self::Item> {
162        loop {
163            if let Some(line) = self.strings.next() {
164                if line.is_empty() {
165                    if self.prev_empty {
166                        self.prev_empty = false;
167                        return Some(self.take());
168                    }
169                    self.prev_empty = true;
170                } else {
171                    if self.prev_empty {
172                        self.current.push('\n');
173                    }
174                    self.current.push_str(line);
175                    self.current.push('\n');
176                    self.prev_empty = false;
177                }
178            } else {
179                if self.current.is_empty() {
180                    return None;
181                }
182                return Some(self.take());
183            }
184        }
185    }
186}
187
188#[cfg(test)]
189fn split(input: &str) -> LineIter {
190    LineIter::from(input)
191}
192
193#[test]
194fn splitter_preserves_line_breaks() {
195    let x = split("a\nb").collect::<Vec<_>>();
196    assert_eq!(x, ["a\nb"]);
197
198    let x = split("a\n\nb").collect::<Vec<_>>();
199    assert_eq!(x, ["a\n\nb"]);
200
201    let x = split("a\n\n\nb").collect::<Vec<_>>();
202    assert_eq!(x, ["a", "b"]);
203}
204
205#[test]
206fn splitter_with_code_blocks() {
207    let input = "Make a tree\n\n\n\n\nExamples:\n\n```sh\ncargo 1\ncargo 2\n```";
208    let out = split(input).collect::<Vec<_>>();
209    assert_eq!(
210        out,
211        [
212            "Make a tree",
213            "",
214            "Examples:\n\n```sh\ncargo 1\ncargo 2\n```"
215        ]
216    );
217}