1 //! [![github]](https://github.com/dtolnay/indoc) [![crates-io]](https://crates.io/crates/indoc) [![docs-rs]](https://docs.rs/indoc)
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
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.
26 //! let testing = indoc! {"
28 //! print('Hello, world!')
32 //! let expected = "def hello():\n print('Hello, world!')\n\nhello()\n";
33 //! assert_eq!(testing, expected);
37 //! Indoc also works with raw string literals:
43 //! let testing = indoc! {r#"
45 //! print("Hello, world!")
49 //! let expected = "def hello():\n print(\"Hello, world!\")\n\nhello()\n";
50 //! assert_eq!(testing, expected);
54 //! And byte string literals:
60 //! let testing = indoc! {b"
62 //! print('Hello, world!')
66 //! let expected = b"def hello():\n print('Hello, world!')\n\nhello()\n";
67 //! assert_eq!(testing[..], expected[..]);
73 //! # Formatting macros
75 //! The indoc crate exports four additional macros to substitute conveniently
76 //! for the standard library's formatting macros:
78 //! - `formatdoc!($fmt, ...)` — equivalent to `format!(indoc!($fmt), ...)`
79 //! - `printdoc!($fmt, ...)` — equivalent to `print!(indoc!($fmt), ...)`
80 //! - `eprintdoc!($fmt, ...)` — equivalent to `eprint!(indoc!($fmt), ...)`
81 //! - `writedoc!($dest, $fmt, ...)` — equivalent to `write!($dest, indoc!($fmt), ...)`
84 //! use indoc::printdoc;
91 //! url = "http://localhost:8080",
92 //! mime = "application/json",
101 //! The following rules characterize the behavior of the `indoc!()` macro:
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
108 //! 4. Remove the computed number of spaces from the beginning of each line.
110 #![allow(clippy::needless_doctest_main)]
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
;
123 #[derive(Copy, Clone, PartialEq)]
132 /// Unindent and produce `&'static str`.
137 /// # use indoc::indoc;
139 /// // The type of `program` is &'static str
140 /// let program = indoc! {"
142 /// print('Hello, world!')
146 /// print!("{}", program);
151 /// print('Hello, world!')
156 pub fn indoc(input
: TokenStream
) -> TokenStream
{
157 expand(input
, Macro
::Indoc
)
160 /// Unindent and call `format!`.
162 /// Argument syntax is the same as for [`std::format!`].
167 /// # use indoc::formatdoc;
169 /// let request = formatdoc! {"
173 /// url = "http://localhost:8080",
174 /// mime = "application/json",
176 /// println!("{}", request);
180 /// GET http://localhost:8080
181 /// Accept: application/json
184 pub fn formatdoc(input
: TokenStream
) -> TokenStream
{
185 expand(input
, Macro
::Format
)
188 /// Unindent and call `print!`.
190 /// Argument syntax is the same as for [`std::print!`].
195 /// # use indoc::printdoc;
201 /// url = "http://localhost:8080",
202 /// mime = "application/json",
207 /// GET http://localhost:8080
208 /// Accept: application/json
211 pub fn printdoc(input
: TokenStream
) -> TokenStream
{
212 expand(input
, Macro
::Print
)
215 /// Unindent and call `eprint!`.
217 /// Argument syntax is the same as for [`std::eprint!`].
222 /// # use indoc::eprintdoc;
228 /// url = "http://localhost:8080",
229 /// mime = "application/json",
234 /// GET http://localhost:8080
235 /// Accept: application/json
238 pub fn eprintdoc(input
: TokenStream
) -> TokenStream
{
239 expand(input
, Macro
::Eprint
)
242 /// Unindent and call `write!`.
244 /// Argument syntax is the same as for [`std::write!`].
249 /// # use indoc::writedoc;
250 /// # use std::io::Write;
252 /// let _ = writedoc!(
253 /// std::io::stdout(),
258 /// url = "http://localhost:8080",
259 /// mime = "application/json",
264 /// GET http://localhost:8080
265 /// Accept: application/json
268 pub fn writedoc(input
: TokenStream
) -> TokenStream
{
269 expand(input
, Macro
::Write
)
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(),
279 fn try_expand(input
: TokenStream
, mode
: Macro
) -> Result
<TokenStream
> {
280 let mut input
= input
.into_iter();
282 let prefix
= if mode
== Macro
::Write
{
283 Some(expr
::parse(&mut input
)?
)
288 let first
= input
.next().ok_or_else(|| {
291 "unexpected end of macro invocation, expected format string",
295 let unindented_lit
= lit_indoc(first
, mode
)?
;
297 let macro_name
= match mode
{
299 require_empty_or_trailing_comma(&mut input
)?
;
300 return Ok(TokenStream
::from(TokenTree
::Literal(unindented_lit
)));
302 Macro
::Format
=> "format",
303 Macro
::Print
=> "print",
304 Macro
::Eprint
=> "eprint",
305 Macro
::Write
=> "write",
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(
315 .map_or_else(TokenStream
::new
, Expr
::into_tokens
)
317 .chain(iter
::once(TokenTree
::Literal(unindented_lit
)))
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
");
330 if !is_string && !is_byte_string {
331 return Err(Error::new(
333 "argument must be a single string literal
",
337 if is_byte_string && mode != Macro::Indoc {
338 return Err(Error::new(
340 "byte strings are not supported
in formatting macros
",
344 let begin = repr.find('"'
).unwrap() + 1;
345 let end
= repr
.rfind('
"').unwrap();
347 "{open}{content}{close}
",
348 open = &repr[..begin],
349 content = unindent(&repr[begin..end]),
350 close = &repr[end..],
353 match TokenStream::from_str(&repr)
359 TokenTree::Literal(mut lit) => {
360 lit.set_span(token.span());
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(()),
373 Some(first) => first,
374 None => return Ok(()),
376 let last = input.last();
378 let begin_span = first.span();
379 let end_span = last.as_ref().map_or(begin_span, TokenTree::span);
381 "unexpected {token}
in macro invocation
; indoc argument must be a single string literal
",
382 token = if last.is_some() { "tokens" } else { "token" }
384 Err(Error::new2(begin_span, end_span, &msg))