Skip to main content

clml_proc_macro/
lib.rs

1//! This internal library provides the procedural macros needed by the crate [`clml`].
2//!
3//! [`clml`]: https://crates.io/crates/clml
4
5extern crate proc_macro;
6
7#[macro_use]
8mod util;
9mod ansi;
10mod ansi_constants;
11mod color_context;
12mod error;
13mod format_args;
14mod parse;
15mod untagged;
16
17use proc_macro::TokenStream;
18use proc_macro2::TokenStream as TokenStream2;
19use quote::{ToTokens, quote};
20use syn::parse::{Parse, ParseStream};
21use syn::token::Comma;
22use syn::{Expr, parse_macro_input};
23
24/// The same as `format!()`, but parses color tags.
25///
26/// #### Example
27///
28/// ```
29/// # use clml_proc_macro::cformat;
30/// let s: String = cformat!("A <g>green</> word, {}", "placeholders are allowed");
31/// assert_eq!(s, "A \u{1b}[32mgreen\u{1b}[39m word, placeholders are allowed");
32/// ```
33#[proc_macro]
34pub fn cformat(input: TokenStream) -> TokenStream {
35    get_macro("format", input, false)
36}
37
38/// The same as `write!()`, but parses color tags.
39#[proc_macro]
40pub fn cwrite(input: TokenStream) -> TokenStream {
41    get_macro("write", input, true)
42}
43
44/// The same as `writeln!()`, but parses color tags.
45#[proc_macro]
46pub fn cwriteln(input: TokenStream) -> TokenStream {
47    get_macro("writeln", input, true)
48}
49
50/// The same as `cformat!()`, but also dedents the format string like `indoc::indoc!()`.
51#[cfg(feature = "doc")]
52#[proc_macro]
53pub fn cformatdoc(input: TokenStream) -> TokenStream {
54    get_macro_doc("format", input, false)
55}
56
57/// The same as `cwrite!()`, but also dedents the format string like `indoc::indoc!()`.
58#[cfg(feature = "doc")]
59#[proc_macro]
60pub fn cwritedoc(input: TokenStream) -> TokenStream {
61    get_macro_doc("write", input, true)
62}
63
64/// The same as `cwriteln!()`, but also dedents the format string like `indoc::indoc!()`.
65#[cfg(feature = "doc")]
66#[proc_macro]
67pub fn cwritelndoc(input: TokenStream) -> TokenStream {
68    get_macro_doc("writeln", input, true)
69}
70
71/// Colorizes a string literal, without formatting the `format!`-like placeholders.
72///
73/// Accepts only one argument.
74///
75/// #### Example
76///
77/// ```
78/// # use clml_proc_macro::cstr;
79/// let s: &str = cstr!("A <g>green</> word");
80/// assert_eq!(s, "A \u{1b}[32mgreen\u{1b}[39m word");
81/// ```
82#[proc_macro]
83pub fn cstr(input: TokenStream) -> TokenStream {
84    crate::ansi::get_cstr(input)
85        .unwrap_or_else(|err| err.to_token_stream())
86        .into()
87}
88
89/// Removes all the color tags from the given string literal.
90///
91/// Accepts only one argument.
92///
93/// #### Example
94///
95/// ```
96/// # use clml_proc_macro::untagged;
97/// let s: &str = untagged!("A <g>normal</> word");
98/// assert_eq!(s, "A normal word");
99/// ```
100#[proc_macro]
101pub fn untagged(input: TokenStream) -> TokenStream {
102    crate::untagged::get_untagged(input)
103        .unwrap_or_else(|err| err.to_token_stream())
104        .into()
105}
106
107struct WriteInput {
108    dst: Expr,
109    rest: TokenStream,
110}
111
112impl Parse for WriteInput {
113    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
114        let dst: Expr = input.parse()?;
115        let _: Comma = input.parse()?;
116        // Forward the tail as raw tokens: it is re-parsed as `format!`-like arguments downstream,
117        // so parsing it as expressions here would only round-trip it for no benefit.
118        let rest = input.parse::<TokenStream2>()?.into();
119        Ok(Self { dst, rest })
120    }
121}
122
123/// Renders a whole processed macro.
124fn get_macro(macro_name: &str, input: TokenStream, is_write_macro: bool) -> TokenStream {
125    get_macro_impl(
126        macro_name,
127        input,
128        is_write_macro,
129        crate::ansi::get_format_args,
130    )
131}
132
133/// Same as [`get_macro`], but the format string is dedented like `indoc::indoc!()`.
134#[cfg(feature = "doc")]
135fn get_macro_doc(macro_name: &str, input: TokenStream, is_write_macro: bool) -> TokenStream {
136    get_macro_impl(
137        macro_name,
138        input,
139        is_write_macro,
140        crate::ansi::get_format_args_doc,
141    )
142}
143
144fn get_macro_impl(
145    macro_name: &str,
146    input: TokenStream,
147    is_write_macro: bool,
148    get_format_args: fn(TokenStream) -> Result<TokenStream2, crate::error::SpanError>,
149) -> TokenStream {
150    let macro_name = util::ident(macro_name);
151    let fmt_args =
152        |input_tail| get_format_args(input_tail).unwrap_or_else(|err| err.to_token_stream());
153
154    if is_write_macro {
155        let WriteInput { dst, rest } = parse_macro_input!(input);
156        let format_args = fmt_args(rest);
157        (quote! { #macro_name!(#dst, #format_args) }).into()
158    } else {
159        let format_args = fmt_args(input);
160        (quote! { #macro_name!(#format_args) }).into()
161    }
162}