I'm working on a crate annotate-snippets. In it, I need to format text to produce a String, and also optionally style it using one of potentially many "stylesheets".
The challenge I encountered comes from the fact that write! and formatter::write_fmt require a literal for the pattern, so I'm not sure how to turn:
write!(f, "[{}]", some_str);
into:
trait Style {
fn format(f: Formatter, pattern: &str, value: &str) {
write!(f, pattern, value);
}
}
impl Style for ColorStyle {
fn format(f: Formatter, pattern: &str, value: &str) {
write!(f, "..."); // color set to red
write!(f, pattern, value);
write!(f, "..."); // color reset
}
}
The abstraction used by the macros is the Arguments; whenever you want to output that to a Writeable you use the .write_fmt() method.
For a user, providing an Arguments is done through the format_args! macro. Since interacting your Style::format() through an inner format_args! macro will be cumbersome for users, you can then abstract that with your own macro :
use ::core::fmt;
pub
trait Style {
fn style (fmt_args: fmt::Arguments)
-> String
;
}
pub
struct Red;
impl Style for Red {
fn style (fmt_args: fmt::Arguments)
-> String
{
format!("[color=red]{}[/color]", fmt_args)
}
}
fn main ()
{
println!("{}", Red::style(format_args!("Hello, {}!", "World")));
macro_rules! style {(
$Red:ty, for $($fmt:tt)*
) => (
<$Red as $crate::Style>::style(format_args!($($fmt)*))
)}
println!("{}", style!(Red, for "Hello, {}!", "World"));
}
Just to offer a generalization of the fmt_args solution, fmt::Arguments implements Display, and thus if you make the function argument this type instead: fn style(args: &Display) then you accept both a &format_args!(..) (with & this time, unfortunately) and also many other types like string literals or references to other values.
Here is a version using the "impl Display into &dyn Display" suggested by @bluss, and with those ugly intermediate Strings (and thus heap-allocations) removed:
Click to see the code
use ::core::fmt;
/// Since we cannot have `fn(...)` signatures with generic (lifetimes) involved
/// we use Box<dyn Fn(...) -> _ + 'static> with the idea to only use
/// capture-less closures, which will be equivalent to a `&'static fn(...) -> _`
type Printer = Box<
dyn
Fn(&(dyn fmt::Display + '_), &mut fmt::Formatter<'_>) -> fmt::Result +
'static +
>;
pub
struct StyleFmt<Displayable : fmt::Display> {
printer: Printer,
displayable: Displayable,
}
impl<Displayable : fmt::Display> fmt::Display
for StyleFmt<Displayable>
{
#[inline]
fn fmt (self: &'_ Self, stream: &'_ mut fmt::Formatter<'_>)
-> fmt::Result
{
(self.printer)(&self.displayable, stream)
}
}
pub
trait Style {
fn style<Displayable : fmt::Display> (displayable: Displayable)
-> StyleFmt<Displayable>
;
}
pub
struct Red;
impl Style for Red {
fn style<Displayable : fmt::Display> (displayable: Displayable)
-> StyleFmt<Displayable>
{
StyleFmt {
displayable,
printer: Box::new(|displayable, stream| {
write!(stream, "[color=red]{}[/color]", displayable)
}), /* given that the closure is zero-sized,
this Box<dyn ...> is more like a `&'static fn(...)`
in particular: there is no heap allocation */
}
}
}