]> git.proxmox.com Git - rustc.git/blob - vendor/indoc/src/lib.rs
New upstream version 1.58.1+dfsg1
[rustc.git] / vendor / indoc / src / lib.rs
1 //! [![github]](https://github.com/dtolnay/indoc) [![crates-io]](https://crates.io/crates/indoc) [![docs-rs]](https://docs.rs/indoc)
2 //!
3 //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
6 //!
7 //! <br>
8 //!
9 //! This crate provides a procedural macro for indented string literals. The
10 //! `indoc!()` macro takes a multiline string literal and un-indents it at
11 //! compile time so the leftmost non-space character is in the first column.
12 //!
13 //! ```toml
14 //! [dependencies]
15 //! indoc = "1.0"
16 //! ```
17 //!
18 //! <br>
19 //!
20 //! # Using indoc
21 //!
22 //! ```
23 //! use indoc::indoc;
24 //!
25 //! fn main() {
26 //! let testing = indoc! {"
27 //! def hello():
28 //! print('Hello, world!')
29 //!
30 //! hello()
31 //! "};
32 //! let expected = "def hello():\n print('Hello, world!')\n\nhello()\n";
33 //! assert_eq!(testing, expected);
34 //! }
35 //! ```
36 //!
37 //! Indoc also works with raw string literals:
38 //!
39 //! ```
40 //! use indoc::indoc;
41 //!
42 //! fn main() {
43 //! let testing = indoc! {r#"
44 //! def hello():
45 //! print("Hello, world!")
46 //!
47 //! hello()
48 //! "#};
49 //! let expected = "def hello():\n print(\"Hello, world!\")\n\nhello()\n";
50 //! assert_eq!(testing, expected);
51 //! }
52 //! ```
53 //!
54 //! And byte string literals:
55 //!
56 //! ```
57 //! use indoc::indoc;
58 //!
59 //! fn main() {
60 //! let testing = indoc! {b"
61 //! def hello():
62 //! print('Hello, world!')
63 //!
64 //! hello()
65 //! "};
66 //! let expected = b"def hello():\n print('Hello, world!')\n\nhello()\n";
67 //! assert_eq!(testing[..], expected[..]);
68 //! }
69 //! ```
70 //!
71 //! <br><br>
72 //!
73 //! # Formatting macros
74 //!
75 //! The indoc crate exports four additional macros to substitute conveniently
76 //! for the standard library's formatting macros:
77 //!
78 //! - `formatdoc!($fmt, ...)`&ensp;&mdash;&ensp;equivalent to `format!(indoc!($fmt), ...)`
79 //! - `printdoc!($fmt, ...)`&ensp;&mdash;&ensp;equivalent to `print!(indoc!($fmt), ...)`
80 //! - `eprintdoc!($fmt, ...)`&ensp;&mdash;&ensp;equivalent to `eprint!(indoc!($fmt), ...)`
81 //! - `writedoc!($dest, $fmt, ...)`&ensp;&mdash;&ensp;equivalent to `write!($dest, indoc!($fmt), ...)`
82 //!
83 //! ```
84 //! use indoc::printdoc;
85 //!
86 //! fn main() {
87 //! printdoc! {"
88 //! GET {url}
89 //! Accept: {mime}
90 //! ",
91 //! url = "http://localhost:8080",
92 //! mime = "application/json",
93 //! }
94 //! }
95 //! ```
96 //!
97 //! <br><br>
98 //!
99 //! # Explanation
100 //!
101 //! The following rules characterize the behavior of the `indoc!()` macro:
102 //!
103 //! 1. Count the leading spaces of each line, ignoring the first line and any
104 //! lines that are empty or contain spaces only.
105 //! 2. Take the minimum.
106 //! 3. If the first line is empty i.e. the string begins with a newline, remove
107 //! the first line.
108 //! 4. Remove the computed number of spaces from the beginning of each line.
109
110 #![allow(clippy::needless_doctest_main)]
111
112 mod error;
113 mod expr;
114
115 use crate::error::{Error, Result};
116 use crate::expr::Expr;
117 use proc_macro::token_stream::IntoIter as TokenIter;
118 use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
119 use std::iter::{self, FromIterator};
120 use std::str::FromStr;
121 use unindent::unindent;
122
123 #[derive(Copy, Clone, PartialEq)]
124 enum Macro {
125 Indoc,
126 Format,
127 Print,
128 Eprint,
129 Write,
130 }
131
132 /// Unindent and produce `&'static str`.
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// # use indoc::indoc;
138 /// #
139 /// // The type of `program` is &'static str
140 /// let program = indoc! {"
141 /// def hello():
142 /// print('Hello, world!')
143 ///
144 /// hello()
145 /// "};
146 /// print!("{}", program);
147 /// ```
148 ///
149 /// ```text
150 /// def hello():
151 /// print('Hello, world!')
152 ///
153 /// hello()
154 /// ```
155 #[proc_macro]
156 pub fn indoc(input: TokenStream) -> TokenStream {
157 expand(input, Macro::Indoc)
158 }
159
160 /// Unindent and call `format!`.
161 ///
162 /// Argument syntax is the same as for [`std::format!`].
163 ///
164 /// # Example
165 ///
166 /// ```
167 /// # use indoc::formatdoc;
168 /// #
169 /// let request = formatdoc! {"
170 /// GET {url}
171 /// Accept: {mime}
172 /// ",
173 /// url = "http://localhost:8080",
174 /// mime = "application/json",
175 /// };
176 /// println!("{}", request);
177 /// ```
178 ///
179 /// ```text
180 /// GET http://localhost:8080
181 /// Accept: application/json
182 /// ```
183 #[proc_macro]
184 pub fn formatdoc(input: TokenStream) -> TokenStream {
185 expand(input, Macro::Format)
186 }
187
188 /// Unindent and call `print!`.
189 ///
190 /// Argument syntax is the same as for [`std::print!`].
191 ///
192 /// # Example
193 ///
194 /// ```
195 /// # use indoc::printdoc;
196 /// #
197 /// printdoc! {"
198 /// GET {url}
199 /// Accept: {mime}
200 /// ",
201 /// url = "http://localhost:8080",
202 /// mime = "application/json",
203 /// }
204 /// ```
205 ///
206 /// ```text
207 /// GET http://localhost:8080
208 /// Accept: application/json
209 /// ```
210 #[proc_macro]
211 pub fn printdoc(input: TokenStream) -> TokenStream {
212 expand(input, Macro::Print)
213 }
214
215 /// Unindent and call `eprint!`.
216 ///
217 /// Argument syntax is the same as for [`std::eprint!`].
218 ///
219 /// # Example
220 ///
221 /// ```
222 /// # use indoc::eprintdoc;
223 /// #
224 /// eprintdoc! {"
225 /// GET {url}
226 /// Accept: {mime}
227 /// ",
228 /// url = "http://localhost:8080",
229 /// mime = "application/json",
230 /// }
231 /// ```
232 ///
233 /// ```text
234 /// GET http://localhost:8080
235 /// Accept: application/json
236 /// ```
237 #[proc_macro]
238 pub fn eprintdoc(input: TokenStream) -> TokenStream {
239 expand(input, Macro::Eprint)
240 }
241
242 /// Unindent and call `write!`.
243 ///
244 /// Argument syntax is the same as for [`std::write!`].
245 ///
246 /// # Example
247 ///
248 /// ```
249 /// # use indoc::writedoc;
250 /// # use std::io::Write;
251 /// #
252 /// let _ = writedoc!(
253 /// std::io::stdout(),
254 /// "
255 /// GET {url}
256 /// Accept: {mime}
257 /// ",
258 /// url = "http://localhost:8080",
259 /// mime = "application/json",
260 /// );
261 /// ```
262 ///
263 /// ```text
264 /// GET http://localhost:8080
265 /// Accept: application/json
266 /// ```
267 #[proc_macro]
268 pub fn writedoc(input: TokenStream) -> TokenStream {
269 expand(input, Macro::Write)
270 }
271
272 fn expand(input: TokenStream, mode: Macro) -> TokenStream {
273 match try_expand(input, mode) {
274 Ok(tokens) => tokens,
275 Err(err) => err.to_compile_error(),
276 }
277 }
278
279 fn try_expand(input: TokenStream, mode: Macro) -> Result<TokenStream> {
280 let mut input = input.into_iter();
281
282 let prefix = if mode == Macro::Write {
283 Some(expr::parse(&mut input)?)
284 } else {
285 None
286 };
287
288 let first = input.next().ok_or_else(|| {
289 Error::new(
290 Span::call_site(),
291 "unexpected end of macro invocation, expected format string",
292 )
293 })?;
294
295 let unindented_lit = lit_indoc(first, mode)?;
296
297 let macro_name = match mode {
298 Macro::Indoc => {
299 require_empty_or_trailing_comma(&mut input)?;
300 return Ok(TokenStream::from(TokenTree::Literal(unindented_lit)));
301 }
302 Macro::Format => "format",
303 Macro::Print => "print",
304 Macro::Eprint => "eprint",
305 Macro::Write => "write",
306 };
307
308 // #macro_name! { #unindented_lit #args }
309 Ok(TokenStream::from_iter(vec![
310 TokenTree::Ident(Ident::new(macro_name, Span::call_site())),
311 TokenTree::Punct(Punct::new('!', Spacing::Alone)),
312 TokenTree::Group(Group::new(
313 Delimiter::Brace,
314 prefix
315 .map_or_else(TokenStream::new, Expr::into_tokens)
316 .into_iter()
317 .chain(iter::once(TokenTree::Literal(unindented_lit)))
318 .chain(input)
319 .collect(),
320 )),
321 ]))
322 }
323
324 fn lit_indoc(token: TokenTree, mode: Macro) -> Result<Literal> {
325 let repr = token.to_string();
326 let repr = repr.trim();
327 let is_string = repr.starts_with('"') || repr.starts_with('r');
328 let is_byte_string = repr.starts_with("b\"") || repr.starts_with("br");
329
330 if !is_string && !is_byte_string {
331 return Err(Error::new(
332 token.span(),
333 "argument must be a single string literal",
334 ));
335 }
336
337 if is_byte_string && mode != Macro::Indoc {
338 return Err(Error::new(
339 token.span(),
340 "byte strings are not supported in formatting macros",
341 ));
342 }
343
344 let begin = repr.find('"').unwrap() + 1;
345 let end = repr.rfind('"').unwrap();
346 let repr = format!(
347 "{open}{content}{close}",
348 open = &repr[..begin],
349 content = unindent(&repr[begin..end]),
350 close = &repr[end..],
351 );
352
353 match TokenStream::from_str(&repr)
354 .unwrap()
355 .into_iter()
356 .next()
357 .unwrap()
358 {
359 TokenTree::Literal(mut lit) => {
360 lit.set_span(token.span());
361 Ok(lit)
362 }
363 _ => unreachable!(),
364 }
365 }
366
367 fn require_empty_or_trailing_comma(input: &mut TokenIter) -> Result<()> {
368 let first = match input.next() {
369 Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => match input.next() {
370 Some(second) => second,
371 None => return Ok(()),
372 },
373 Some(first) => first,
374 None => return Ok(()),
375 };
376 let last = input.last();
377
378 let begin_span = first.span();
379 let end_span = last.as_ref().map_or(begin_span, TokenTree::span);
380 let msg = format!(
381 "unexpected {token} in macro invocation; indoc argument must be a single string literal",
382 token = if last.is_some() { "tokens" } else { "token" }
383 );
384 Err(Error::new2(begin_span, end_span, &msg))
385 }