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/// Colorizes a string literal, without formatting the `format!`-like placeholders.
51///
52/// Accepts only one argument.
53///
54/// #### Example
55///
56/// ```
57/// # use clml_proc_macro::cstr;
58/// let s: &str = cstr!("A <g>green</> word");
59/// assert_eq!(s, "A \u{1b}[32mgreen\u{1b}[39m word");
60/// ```
61#[proc_macro]
62pub fn cstr(input: TokenStream) -> TokenStream {
63    crate::ansi::get_cstr(input)
64        .unwrap_or_else(|err| err.to_token_stream())
65        .into()
66}
67
68/// Removes all the color tags from the given string literal.
69///
70/// Accepts only one argument.
71///
72/// #### Example
73///
74/// ```
75/// # use clml_proc_macro::untagged;
76/// let s: &str = untagged!("A <g>normal</> word");
77/// assert_eq!(s, "A normal word");
78/// ```
79#[proc_macro]
80pub fn untagged(input: TokenStream) -> TokenStream {
81    crate::untagged::get_untagged(input)
82        .unwrap_or_else(|err| err.to_token_stream())
83        .into()
84}
85
86struct WriteInput {
87    dst: Expr,
88    rest: TokenStream,
89}
90
91impl Parse for WriteInput {
92    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
93        let dst: Expr = input.parse()?;
94        let _: Comma = input.parse()?;
95        // Forward the tail as raw tokens: it is re-parsed as `format!`-like arguments downstream,
96        // so parsing it as expressions here would only round-trip it for no benefit.
97        let rest = input.parse::<TokenStream2>()?.into();
98        Ok(Self { dst, rest })
99    }
100}
101
102/// Renders a whole processed macro.
103fn get_macro(macro_name: &str, input: TokenStream, is_write_macro: bool) -> TokenStream {
104    let macro_name = util::ident(macro_name);
105    let fmt_args = |input_tail| {
106        crate::ansi::get_format_args(input_tail).unwrap_or_else(|err| err.to_token_stream())
107    };
108
109    if is_write_macro {
110        let WriteInput { dst, rest } = parse_macro_input!(input);
111        let format_args = fmt_args(rest);
112        (quote! { #macro_name!(#dst, #format_args) }).into()
113    } else {
114        let format_args = fmt_args(input);
115        (quote! { #macro_name!(#format_args) }).into()
116    }
117}