Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple fmt strings #17

Merged
merged 3 commits into from
Oct 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions examples/wrapper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Sometimes, you might want to wrap bunt's macros into your own
//! project-specific macro. Often, you want to print a prefix or something like
//! that.


macro_rules! log {
($fmt:literal $(, $arg:expr)* $(,)?) => {
bunt::println!(
// Bunt macros allow to pass an "array" of format strings which are
// then concatenated by bunt.
[
// Our prefix
"[{[magenta] log_module_path}] ",
// What the user passed
$fmt,
// Our postfix
" {$cyan}({log_file}:{log_line}){/$}",
],
$($arg ,)*
log_module_path = std::module_path!(),
log_file = std::file!(),
log_line = std::line!(),
)

// This solution is not optimal though. For one, it would be nice to
// `stringify!(std::module_path!())` instead of passing it as runtime
// string argument. That's not possible because macro expansions are
// lazy.
//
// Futhermore, we pass the arguments with names like `log_module_path`.
// In theory, the user could also use a named argument with that name.
// This `log!` macro is very much an internal helper macro and not
// something you would want to put into your public API.
};
}


fn main() {
log!("Hello {}", "peter");
banana::do_something();
}

mod banana {
pub fn do_something() {
log!("I'm doing something with {[yellow]:?}", vec![1, 2, 4]);
}
}
43 changes: 40 additions & 3 deletions macros/src/parse/fmt.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro2::{
Span,
token_stream::IntoIter as TokenIterator,
token_stream::IntoIter as TokenIterator, TokenTree, Delimiter,
};
use unicode_xid::UnicodeXID;
use std::str::Chars;
Expand All @@ -15,6 +15,7 @@ use super::{parse, expect_helper_group, lit::expect_str_literal};


impl FormatStr {
/// Parses `["foo"]`, `["foo" "bar"]`.
pub(crate) fn parse(it: &mut TokenIterator) -> Result<Self, Error> {
/// Searches for the next closing `}`. Returns a pair of strings, the
/// first starting like `s` and ending at the closing brace, the second
Expand All @@ -29,8 +30,44 @@ impl FormatStr {
Ok((&s[..end], &s[end + 1..]))
}

let (inner, _) = expect_helper_group(it.next())?;
let (raw, span) = parse(inner, expect_str_literal)?;
// We expect a []-delimited group
let (inner, span) = match it.next() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
(g.stream(), g.span())
}
Some(TokenTree::Group(g)) => {
return Err(err!(
g.span(),
"expected `[]` delimited group, but delimiter is {:?} (note: do not use \
the macros from `bunt-macros` directly, but only through `bunt`)",
g.delimiter(),
));
}
Some(tt) => {
return Err(err!(
tt.span(),
"expected `[]` delimited group, but found different token tree (note: do \
not use the macros from `bunt-macros` directly, but only through `bunt`)",
))
}
None => return Err(err!("expected `[]` delimited group, found EOF")),
};

if inner.is_empty() {
return Err(err!(
span,
"at least one format string has to be provided, but `[]` was passed (note: do not \
use the macros from `bunt-macros` directly, but only through `bunt`)"
));
}

// Concat all string literals
let mut raw = String::new();
for tt in inner {
let (literal, _) = expect_helper_group(Some(tt))?;
let (string_data, _) = parse(literal, expect_str_literal)?;
raw += &string_data;
}

// Scan the whole string
let mut fragments = Vec::new();
Expand Down
53 changes: 47 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,29 @@
//! want to give your users the choice of color usage, e.g. via a `--color` CLI
//! argument.
//!
//!
//! # Passing multiple format strings (`concat!` replacement)
//!
//! In many cases, users wish to call `concat!` and pass the result as format
//! string to bunt's macros, e.g. `bunt::println!(concat!("foo", "bar"))`. This
//! is mainly used if you want to write your own macro to wrap bunt's macros.
//! Unfortunately, this is not easily possible as macros are expaned lazily. See
//! [issue #15](https://github.com/LukasKalbertodt/bunt/issues/15) for more
//! information.
//!
//! As a workaround for this fairly common use case, bunt allows passing an
//! "array of format strings", like so:
//!
//! ```
//! bunt::println!(["foo ", "{[red]} bar"], 27);
//! ```
//!
//! All given strings will be concatenated by `bunt`. So the above code is
//! equivalent to `bunt::println!("foo {[red]} bar", 27)`.
//!
//! For most users this feature is irrelevant. If possible, pass the format
//! string as single string literal.
//!

#![deny(intra_doc_link_resolution_failure)]

Expand Down Expand Up @@ -152,8 +175,11 @@ pub extern crate bunt_macros;
#[macro_export]
macro_rules! write {
($target:expr, $format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::write!($target, [$format_str] $(, $arg )*)
};
($target:expr, [$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::write!(
$target $format_str $( $arg )*
$target [$($format_str)+] $( $arg )*
)
};
}
Expand All @@ -175,8 +201,11 @@ macro_rules! write {
#[macro_export]
macro_rules! writeln {
($target:expr, $format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::writeln!($target, [$format_str] $(, $arg )*)
};
($target:expr, [$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::writeln!(
$target $format_str $( $arg )*
$target [$($format_str)+] $( $arg )*
)
};
}
Expand All @@ -196,9 +225,12 @@ macro_rules! writeln {
#[macro_export]
macro_rules! print {
($format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::print!([$format_str] $(, $arg )*)
};
([$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::write!(
($crate::termcolor::StandardStream::stdout($crate::termcolor::ColorChoice::Auto))
$format_str $( $arg )*
[$($format_str)+] $( $arg )*
).expect("failed to write to stdout in `bunt::print`")
};
}
Expand All @@ -215,9 +247,12 @@ macro_rules! print {
#[macro_export]
macro_rules! println {
($format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::println!([$format_str] $(, $arg )*)
};
([$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::writeln!(
($crate::termcolor::StandardStream::stdout($crate::termcolor::ColorChoice::Auto))
$format_str $( $arg )*
[$($format_str)+] $( $arg )*
).expect("failed to write to stdout in `bunt::println`")
};
}
Expand All @@ -237,9 +272,12 @@ macro_rules! println {
#[macro_export]
macro_rules! eprint {
($format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::eprint!([$format_str] $(, $arg )*)
};
([$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::write!(
($crate::termcolor::StandardStream::stderr($crate::termcolor::ColorChoice::Auto))
$format_str $( $arg )*
[$($format_str)+] $( $arg )*
).expect("failed to write to stderr in `bunt::eprint`")
};
}
Expand All @@ -256,9 +294,12 @@ macro_rules! eprint {
#[macro_export]
macro_rules! eprintln {
($format_str:literal $(, $arg:expr)* $(,)?) => {
$crate::eprintln!([$format_str] $(, $arg )*)
};
([$($format_str:literal),+ $(,)?] $(, $arg:expr)* $(,)?) => {
$crate::bunt_macros::writeln!(
($crate::termcolor::StandardStream::stderr($crate::termcolor::ColorChoice::Auto))
$format_str $( $arg )*
[$($format_str)+] $( $arg )*
).expect("failed to write to stderr in `bunt::eprintln`")
};
}
Expand Down
28 changes: 27 additions & 1 deletion tests/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt;
use bunt::{
write, writeln, print, println,
write, writeln, print, println, eprint, eprintln,
termcolor::Buffer,
};

Expand Down Expand Up @@ -305,3 +305,29 @@ fn set_color_error() {
assert!(res.is_err());
assert_eq!(res.unwrap_err().kind(), io::ErrorKind::NotFound);
}

#[test]
fn concat_fmt_strings() {
check!("hello" == ["hello"]);
check!("hello" == ["hello",]);
check!("foobar" == ["foo", "bar"]);
check!("foobar" == ["foo", "bar",]);
check!("foo 27 bar" == ["foo ", "{} bar"], 27);
check!("abtruecd" == ["a", "b", "{}", "c", "d"], true);

check!(
"a\nb\tc\rd\0e\x48f\u{50}g\u{228}h\u{fffe}i\u{1F923}j" ==
["a\n", "b\tc\r", "d\0e\x48f\u{50}", "g\u{228}h\u{fffe}i", "\u{1F923}j"]
);
}

#[test]
fn concat_fmt_strings_all_strings() {
let mut b = buf();
let _ = write!(b, ["a", "{} b"], 27);
let _ = writeln!(b, ["a", "{} b"], 27);
print!(["a", "{} \r"], 27);
eprint!(["a", "{} \r"], 27);
println!(["", "{}"], "");
eprintln!(["", "{}"], "");
}