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
110pub(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}