diff options
Diffstat (limited to 'vendor/indoc/src/lib.rs')
-rw-r--r-- | vendor/indoc/src/lib.rs | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/vendor/indoc/src/lib.rs b/vendor/indoc/src/lib.rs new file mode 100644 index 000000000..9c200adc7 --- /dev/null +++ b/vendor/indoc/src/lib.rs @@ -0,0 +1,403 @@ +//! [![github]](https://github.com/dtolnay/indoc) [![crates-io]](https://crates.io/crates/indoc) [![docs-rs]](https://docs.rs/indoc) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [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 +//! +//! <br> +//! +//! This crate provides a procedural macro for indented string literals. The +//! `indoc!()` macro takes a multiline string literal and un-indents it at +//! compile time so the leftmost non-space character is in the first column. +//! +//! ```toml +//! [dependencies] +//! indoc = "1.0" +//! ``` +//! +//! <br> +//! +//! # Using indoc +//! +//! ``` +//! use indoc::indoc; +//! +//! fn main() { +//! let testing = indoc! {" +//! def hello(): +//! print('Hello, world!') +//! +//! hello() +//! "}; +//! let expected = "def hello():\n print('Hello, world!')\n\nhello()\n"; +//! assert_eq!(testing, expected); +//! } +//! ``` +//! +//! Indoc also works with raw string literals: +//! +//! ``` +//! use indoc::indoc; +//! +//! fn main() { +//! let testing = indoc! {r#" +//! def hello(): +//! print("Hello, world!") +//! +//! hello() +//! "#}; +//! let expected = "def hello():\n print(\"Hello, world!\")\n\nhello()\n"; +//! assert_eq!(testing, expected); +//! } +//! ``` +//! +//! And byte string literals: +//! +//! ``` +//! use indoc::indoc; +//! +//! fn main() { +//! let testing = indoc! {b" +//! def hello(): +//! print('Hello, world!') +//! +//! hello() +//! "}; +//! let expected = b"def hello():\n print('Hello, world!')\n\nhello()\n"; +//! assert_eq!(testing[..], expected[..]); +//! } +//! ``` +//! +//! <br><br> +//! +//! # Formatting macros +//! +//! The indoc crate exports four additional macros to substitute conveniently +//! for the standard library's formatting macros: +//! +//! - `formatdoc!($fmt, ...)` — equivalent to `format!(indoc!($fmt), ...)` +//! - `printdoc!($fmt, ...)` — equivalent to `print!(indoc!($fmt), ...)` +//! - `eprintdoc!($fmt, ...)` — equivalent to `eprint!(indoc!($fmt), ...)` +//! - `writedoc!($dest, $fmt, ...)` — equivalent to `write!($dest, indoc!($fmt), ...)` +//! +//! ``` +//! use indoc::printdoc; +//! +//! fn main() { +//! printdoc! {" +//! GET {url} +//! Accept: {mime} +//! ", +//! url = "http://localhost:8080", +//! mime = "application/json", +//! } +//! } +//! ``` +//! +//! <br><br> +//! +//! # Explanation +//! +//! The following rules characterize the behavior of the `indoc!()` macro: +//! +//! 1. Count the leading spaces of each line, ignoring the first line and any +//! lines that are empty or contain spaces only. +//! 2. Take the minimum. +//! 3. If the first line is empty i.e. the string begins with a newline, remove +//! the first line. +//! 4. Remove the computed number of spaces from the beginning of each line. + +#![allow( + clippy::module_name_repetitions, + clippy::needless_doctest_main, + clippy::needless_pass_by_value, + clippy::trivially_copy_pass_by_ref, + clippy::type_complexity +)] + +mod error; +mod expr; +mod unindent; + +use crate::error::{Error, Result}; +use crate::expr::Expr; +use crate::unindent::unindent; +use proc_macro::token_stream::IntoIter as TokenIter; +use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; +use std::iter::{self, FromIterator}; +use std::str::FromStr; + +#[derive(Copy, Clone, PartialEq)] +enum Macro { + Indoc, + Format, + Print, + Eprint, + Write, +} + +/// Unindent and produce `&'static str`. +/// +/// # Example +/// +/// ``` +/// # use indoc::indoc; +/// # +/// // The type of `program` is &'static str +/// let program = indoc! {" +/// def hello(): +/// print('Hello, world!') +/// +/// hello() +/// "}; +/// print!("{}", program); +/// ``` +/// +/// ```text +/// def hello(): +/// print('Hello, world!') +/// +/// hello() +/// ``` +#[proc_macro] +pub fn indoc(input: TokenStream) -> TokenStream { + expand(input, Macro::Indoc) +} + +/// Unindent and call `format!`. +/// +/// Argument syntax is the same as for [`std::format!`]. +/// +/// # Example +/// +/// ``` +/// # use indoc::formatdoc; +/// # +/// let request = formatdoc! {" +/// GET {url} +/// Accept: {mime} +/// ", +/// url = "http://localhost:8080", +/// mime = "application/json", +/// }; +/// println!("{}", request); +/// ``` +/// +/// ```text +/// GET http://localhost:8080 +/// Accept: application/json +/// ``` +#[proc_macro] +pub fn formatdoc(input: TokenStream) -> TokenStream { + expand(input, Macro::Format) +} + +/// Unindent and call `print!`. +/// +/// Argument syntax is the same as for [`std::print!`]. +/// +/// # Example +/// +/// ``` +/// # use indoc::printdoc; +/// # +/// printdoc! {" +/// GET {url} +/// Accept: {mime} +/// ", +/// url = "http://localhost:8080", +/// mime = "application/json", +/// } +/// ``` +/// +/// ```text +/// GET http://localhost:8080 +/// Accept: application/json +/// ``` +#[proc_macro] +pub fn printdoc(input: TokenStream) -> TokenStream { + expand(input, Macro::Print) +} + +/// Unindent and call `eprint!`. +/// +/// Argument syntax is the same as for [`std::eprint!`]. +/// +/// # Example +/// +/// ``` +/// # use indoc::eprintdoc; +/// # +/// eprintdoc! {" +/// GET {url} +/// Accept: {mime} +/// ", +/// url = "http://localhost:8080", +/// mime = "application/json", +/// } +/// ``` +/// +/// ```text +/// GET http://localhost:8080 +/// Accept: application/json +/// ``` +#[proc_macro] +pub fn eprintdoc(input: TokenStream) -> TokenStream { + expand(input, Macro::Eprint) +} + +/// Unindent and call `write!`. +/// +/// Argument syntax is the same as for [`std::write!`]. +/// +/// # Example +/// +/// ``` +/// # use indoc::writedoc; +/// # use std::io::Write; +/// # +/// let _ = writedoc!( +/// std::io::stdout(), +/// " +/// GET {url} +/// Accept: {mime} +/// ", +/// url = "http://localhost:8080", +/// mime = "application/json", +/// ); +/// ``` +/// +/// ```text +/// GET http://localhost:8080 +/// Accept: application/json +/// ``` +#[proc_macro] +pub fn writedoc(input: TokenStream) -> TokenStream { + expand(input, Macro::Write) +} + +fn expand(input: TokenStream, mode: Macro) -> TokenStream { + match try_expand(input, mode) { + Ok(tokens) => tokens, + Err(err) => err.to_compile_error(), + } +} + +fn try_expand(input: TokenStream, mode: Macro) -> Result<TokenStream> { + let mut input = input.into_iter(); + + let prefix = if mode == Macro::Write { + Some(expr::parse(&mut input)?) + } else { + None + }; + + let first = input.next().ok_or_else(|| { + Error::new( + Span::call_site(), + "unexpected end of macro invocation, expected format string", + ) + })?; + + let unindented_lit = lit_indoc(first, mode)?; + + let macro_name = match mode { + Macro::Indoc => { + require_empty_or_trailing_comma(&mut input)?; + return Ok(TokenStream::from(TokenTree::Literal(unindented_lit))); + } + Macro::Format => "format", + Macro::Print => "print", + Macro::Eprint => "eprint", + Macro::Write => "write", + }; + + // #macro_name! { #unindented_lit #args } + Ok(TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new(macro_name, Span::call_site())), + TokenTree::Punct(Punct::new('!', Spacing::Alone)), + TokenTree::Group(Group::new( + Delimiter::Brace, + prefix + .map_or_else(TokenStream::new, Expr::into_tokens) + .into_iter() + .chain(iter::once(TokenTree::Literal(unindented_lit))) + .chain(input) + .collect(), + )), + ])) +} + +fn lit_indoc(token: TokenTree, mode: Macro) -> Result<Literal> { + let span = token.span(); + let mut single_token = Some(token); + + while let Some(TokenTree::Group(group)) = single_token { + single_token = if group.delimiter() == Delimiter::None { + let mut token_iter = group.stream().into_iter(); + token_iter.next().xor(token_iter.next()) + } else { + None + }; + } + + let single_token = + single_token.ok_or_else(|| Error::new(span, "argument must be a single string literal"))?; + + let repr = single_token.to_string(); + let is_string = repr.starts_with('"') || repr.starts_with('r'); + let is_byte_string = repr.starts_with("b\"") || repr.starts_with("br"); + + if !is_string && !is_byte_string { + return Err(Error::new(span, "argument must be a single string literal")); + } + + if is_byte_string && mode != Macro::Indoc { + return Err(Error::new( + span, + "byte strings are not supported in formatting macros", + )); + } + + let begin = repr.find('"').unwrap() + 1; + let end = repr.rfind('"').unwrap(); + let repr = format!( + "{open}{content}{close}", + open = &repr[..begin], + content = unindent(&repr[begin..end]), + close = &repr[end..], + ); + + match TokenStream::from_str(&repr) + .unwrap() + .into_iter() + .next() + .unwrap() + { + TokenTree::Literal(mut lit) => { + lit.set_span(span); + Ok(lit) + } + _ => unreachable!(), + } +} + +fn require_empty_or_trailing_comma(input: &mut TokenIter) -> Result<()> { + let first = match input.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => match input.next() { + Some(second) => second, + None => return Ok(()), + }, + Some(first) => first, + None => return Ok(()), + }; + let last = input.last(); + + let begin_span = first.span(); + let end_span = last.as_ref().map_or(begin_span, TokenTree::span); + let msg = format!( + "unexpected {token} in macro invocation; indoc argument must be a single string literal", + token = if last.is_some() { "tokens" } else { "token" } + ); + Err(Error::new2(begin_span, end_span, &msg)) +} |