a better coloring crate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//! smart crate for terminal coloring.
//!
//! uses macros instead of methods.
//!
//! ## usage
//!
//! heres how it works:
//! ```rust
//! # use comat::cprintln;
//! # use std::time::{Duration, Instant};
//! cprintln!("the traffic light is {bold_red}red.{reset}");
//! cprintln!("the traffic light will be {green}green{reset} at {:?}.", Instant::now() + Duration::from_secs(40));
//! ```
//!
//! ## why you should use comat instead of {`yansi`, `owo_colors`, `colored`, ..}
//!
//! - no method pollution, your intellisense remains fine
//! - compact: shorter than even raw ansi. see:
//!   ```
//!   # use comat::cprint;
//!   # let thing = 0;
//!   cprint!("{thing:red}.");
//!   ```
//!   vs
//!   ```
//!   # let thing = 0;
//!   print!("\x1b[0;34;31m{thing}\x1b[0m.");
//!   ```
//!   vs
//!   ```
//!   # #[cfg(doc)]
//!   print!("{}.", thing.red());
//!   ```
//! - intuitive: you dont have to
//!   ```
//!   # #[cfg(doc)]
//!   println!("{} {} {}", thing1.red().on_blue(), thing2.red().on_blue(), thing3.italic().yellow());
//!   ```
//!   instead, simply
//!   ```
//!   # use comat::cprintln;
//!   # let thing1 = 0; let thing2 = 5; let thing3 = 4;
//!   cprintln!("{red}{on_blue}{thing1} {thing2} {thing3:italic,yellow}");
//!   ```
//!
//! ## syntax
//!
//! `{{` gives you a `{`, to get a `{{` use `{{{{`.
//!
//! `{color}` adds that effect/color to the string. it does not reset afterwards.
//!
//! if the color inside a `{}` is not found, it doesnt touch the block, for convenience.
//!
//! `{thing:color}` will reset everything before the block, color it, and reset that color. similar to `thing.color()` with other libs.
//! it can also contain more than one color: `{thing:yelow,italic,on_red}`
//!
//! ## colors
//!
//! `black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `default` `bold_black` `bold_red` `bold_green` `bold_yellow` `bold_blue` `bold_magenta` `bold_cyan` `bold_white`
//! `bold_default` `on_black_bold` `on_red_bold` `on_green_bold` `on_yellow_bold` `on_blue_bold` `on_magenta_bold` `on_cyan_bold` `on_white_bold` `on_default_bold` `on_black` `on_red`
//! `on_green` `on_yellow` `on_blue` `on_magenta` `on_cyan` `on_white` `on_default` `reset` `dim` `italic` `underline` `blinking` `hide` `strike` `bold`
#![forbid(unsafe_code)]
#![warn(clippy::pedantic, clippy::dbg_macro, missing_docs)]
use proc_macro::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse::Parse, parse_macro_input, punctuated::Punctuated, Expr, Result, Token};

mod cfstr;
use cfstr::CFStr;

#[proc_macro]
/// Macro that simply modifies the format string to have colors.
/// Mostly for testing. Use [`cformat_args!`] instead where possible.
pub fn comat(input: TokenStream) -> TokenStream {
    let str = parse_macro_input!(input as CFStr);
    str.to_token_stream().into()
}

struct One {
    cfstr: CFStr,
    args: Punctuated<Expr, Token![,]>,
}

impl Parse for One {
    fn parse(input: syn::parse::ParseStream) -> Result<Self> {
        let cfstr = input.parse::<CFStr>()?;
        let _ = input.parse::<Token![,]>();
        Ok(Self {
            cfstr,
            args: Punctuated::<Expr, Token![,]>::parse_terminated(input)?,
        })
    }
}

impl ToTokens for One {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.cfstr.to_tokens(tokens);
        tokens.append(proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone));
        self.args.to_tokens(tokens);
    }
}

// NOTE: many of these can be made as decl macros, but decl macros can't be exported from proc macro crates yet.

#[proc_macro]
/// Print text, colorfully, to stdout, with a newline.
///
/// See also [`println`].
/// ```
/// # use comat::*;
/// let magic = 4;
/// cprintln!("{red}look its red{reset}! {bold_blue}{magic}{reset} is the magic number!");
/// ```
pub fn cprintln(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as One);
    quote! { println!(#f) }.into()
}

#[proc_macro]
/// Print text, colorfully, to stdout, without a newline.
///
/// See also [`print`].
/// ```
/// # use comat::*;
/// cprint!("{yellow}i am a warning. {reset}why do you dislike me?");
/// ```
pub fn cprint(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as One);
    quote! { print!(#f) }.into()
}

#[proc_macro]
/// Format text, colorfully.
///
/// See also [`format`].
/// ```
/// # use comat::*;
/// let favorite_thing = "teddy bears";
/// let message = cformat!("the {red}bogeymen{reset} will get your {favorite_thing:underline}");
/// # assert_eq!(message, "the \x1b[0;34;31mbogeymen\x1b[0m will get your \x1b[0m\x1b[24mteddy bears\x1b[0m");
/// ```
pub fn cformat(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as One);
    quote! { format!(#f) }.into()
}

#[proc_macro]
/// Produce [`fmt::Arguments`](std::fmt::Arguments). Sometimes functions take these.
///
/// See also [`format_args`].
/// ```
/// # use comat::*;
/// let args = cformat_args!("{bold_red}fatal error. {reset}killing {blue}everything{reset}");
/// // NOTE: do not do this. instead use cprintln.
/// println!("{}", args);
pub fn cformat_args(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as One);
    quote! { format_args!(#f) }.into()
}
/// Colorfully panic.
///
/// See also [`panic`].
/// ```should_panic
/// # use comat::cpanic;
/// cpanic!("why is the bound {red}bad");
/// ```
#[proc_macro]
pub fn cpanic(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as One);
    quote! { panic!(#f) }.into()
}

struct Two {
    a: Expr,
    cfstr: CFStr,
    args: Punctuated<Expr, Token![,]>,
}

impl Parse for Two {
    fn parse(input: syn::parse::ParseStream) -> Result<Self> {
        let a = input.parse::<Expr>()?;
        input.parse::<Token![,]>()?;
        let cfstr = input.parse::<CFStr>()?;
        let _ = input.parse::<Token![,]>();
        Ok(Self {
            a,
            cfstr,
            args: Punctuated::<Expr, Token![,]>::parse_terminated(input)?,
        })
    }
}

impl ToTokens for Two {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.a.to_tokens(tokens);
        tokens.append(proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone));
        self.cfstr.to_tokens(tokens);
        tokens.append(proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone));
        self.args.to_tokens(tokens);
    }
}

#[proc_macro]
/// Write to a buffer colorfully, with no newline.
///
/// See also [`write`]
/// ```
/// # use comat::cwrite;
/// use std::io::Write;
/// let mut buf = vec![];
/// cwrite!(buf, "{green}omg there's going to be ansi sequences in a {black}Vec<u8>{reset}!");
/// # assert_eq!(buf, [27, 91, 48, 59, 51, 52, 59, 51, 50, 109, 111, 109, 103, 32, 116, 104, 101, 114, 101, 39, 115, 32, 103, 111, 105, 110, 103, 32, 116, 111, 32, 98, 101, 32, 97, 110, 115, 105, 32, 115, 101, 113, 117, 101, 110, 99, 101, 115, 32, 105, 110, 32, 97, 32, 27, 91, 48, 59, 51, 52, 59, 51, 48, 109, 86, 101, 99, 60, 117, 56, 62, 27, 91, 48, 109, 33]);
/// ```
pub fn cwrite(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as Two);
    quote! { write!(#f) }.into()
}

#[proc_macro]
/// Write to a buffer colorfully, with newline.
///
/// See also [`writeln`]
/// ```
/// # use comat::cwriteln;
/// use std::io::Write;
/// let mut buf = vec![];
/// cwriteln!(buf, "hey look: {strike}strike'd text{reset}!");
/// # assert_eq!(buf, [104, 101, 121, 32, 108, 111, 111, 107, 58, 32, 27, 91, 57, 109, 115, 116, 114, 105, 107, 101, 39, 100, 32, 116, 101, 120, 116, 27, 91, 48, 109, 33, 10]);
/// ```
pub fn cwriteln(input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as Two);
    quote! { writeln!(#f) }.into()
}