Created
April 1, 2019 08:55
-
-
Save KamilaBorowska/45235f7228b857c732dfe1c6febbf5b6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::result; | |
#[derive(Copy, Clone)] | |
pub enum Alignment { | |
Left, | |
Right, | |
Center, | |
} | |
pub type Result = result::Result<(), Error>; | |
#[derive(Debug)] | |
pub struct Error; | |
pub trait Write { | |
fn write_str(&mut self, s: &str) -> Result; | |
fn write_char(&mut self, c: char) -> Result { | |
self.write_str(c.encode_utf8(&mut [0; 4])) | |
} | |
fn write_fmt(mut self: &mut Self, args: Arguments) -> Result { | |
write(&mut self, args) | |
} | |
} | |
pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result { | |
let mut formatter = Formatter::new(output); | |
for arg in args.arguments { | |
arg.fmt(&mut formatter)?; | |
} | |
Ok(()) | |
} | |
impl<W: Write + ?Sized> Write for &mut W { | |
fn write_str(&mut self, s: &str) -> Result { | |
(**self).write_str(s) | |
} | |
fn write_char(&mut self, c: char) -> Result { | |
(**self).write_char(c) | |
} | |
fn write_fmt(&mut self, args: Arguments) -> Result { | |
(**self).write_fmt(args) | |
} | |
} | |
impl Write for Formatter<'_> { | |
fn write_str(&mut self, s: &str) -> Result { | |
self.buf.write_str(s) | |
} | |
} | |
impl Write for String { | |
fn write_str(&mut self, s: &str) -> Result { | |
self.push_str(s); | |
Ok(()) | |
} | |
} | |
pub struct Formatter<'a> { | |
flags: u32, | |
fill: char, | |
align: Option<Alignment>, | |
width: Option<usize>, | |
precision: Option<usize>, | |
buf: &'a mut (dyn Write + 'a), | |
} | |
impl Formatter<'_> { | |
fn new(buf: &mut dyn Write) -> Formatter<'_> { | |
Formatter { | |
flags: 0, | |
fill: ' ', | |
align: None, | |
width: None, | |
precision: None, | |
buf, | |
} | |
} | |
pub fn write_str(&mut self, data: &str) -> Result { | |
self.buf.write_str(data) | |
} | |
pub fn pad(&mut self, s: &str) -> Result { | |
// Make sure there's a fast path up front | |
if self.width.is_none() && self.precision.is_none() { | |
return self.buf.write_str(s); | |
} | |
// The `precision` field can be interpreted as a `max-width` for the | |
// string being formatted. | |
let s = if let Some(max) = self.precision { | |
// If our string is longer that the precision, then we must have | |
// truncation. However other flags like `fill`, `width` and `align` | |
// must act as always. | |
if let Some((i, _)) = s.char_indices().nth(max) { | |
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but | |
// we know that it can't panic. Use `get` + `unwrap_or` to avoid | |
// `unsafe` and otherwise don't emit any panic-related code | |
// here. | |
s.get(..i).unwrap_or(&s) | |
} else { | |
&s | |
} | |
} else { | |
&s | |
}; | |
// The `width` field is more of a `min-width` parameter at this point. | |
match self.width { | |
// If we're under the maximum length, and there's no minimum length | |
// requirements, then we can just emit the string | |
None => self.buf.write_str(s), | |
// If we're under the maximum width, check if we're over the minimum | |
// width, if so it's as easy as just emitting the string. | |
Some(width) if s.chars().count() >= width => self.buf.write_str(s), | |
// If we're under both the maximum and the minimum width, then fill | |
// up the minimum width with the specified string + some alignment. | |
Some(width) => { | |
let align = Some(Alignment::Left); | |
self.with_padding(width - s.chars().count(), align, |me| me.buf.write_str(s)) | |
} | |
} | |
} | |
fn with_padding<F>(&mut self, padding: usize, default: Option<Alignment>, f: F) -> Result | |
where | |
F: FnOnce(&mut Formatter) -> Result, | |
{ | |
let align = self.align.or(default); | |
let (pre_pad, post_pad) = match align { | |
Some(Alignment::Left) => (0, padding), | |
Some(Alignment::Right) | None => (padding, 0), | |
Some(Alignment::Center) => (padding / 2, (padding + 1) / 2), | |
}; | |
let mut fill = [0; 4]; | |
let fill = self.fill.encode_utf8(&mut fill); | |
for _ in 0..pre_pad { | |
self.buf.write_str(fill)?; | |
} | |
f(self)?; | |
for _ in 0..post_pad { | |
self.buf.write_str(fill)?; | |
} | |
Ok(()) | |
} | |
} | |
pub struct Arguments<'a> { | |
arguments: &'a [&'a (dyn Argument + 'a)], | |
} | |
trait Argument { | |
fn fmt(&self, f: &mut Formatter<'_>) -> Result; | |
} | |
struct DisplayArgument<T>(T); | |
impl<T> Argument for DisplayArgument<T> | |
where | |
T: Display, | |
{ | |
fn fmt(&self, f: &mut Formatter<'_>) -> Result { | |
self.0.fmt(f) | |
} | |
} | |
trait Display { | |
fn fmt(&self, f: &mut Formatter<'_>) -> Result; | |
} | |
impl Display for char { | |
fn fmt(&self, f: &mut Formatter) -> Result { | |
if f.width.is_none() && f.precision.is_none() { | |
f.write_char(*self) | |
} else { | |
f.pad(self.encode_utf8(&mut [0; 4])) | |
} | |
} | |
} | |
impl Display for str { | |
fn fmt(&self, f: &mut Formatter<'_>) -> Result { | |
f.pad(self) | |
} | |
} | |
impl<T> Display for &T | |
where | |
T: ?Sized + Display, | |
{ | |
fn fmt(&self, f: &mut Formatter<'_>) -> Result { | |
(**self).fmt(f) | |
} | |
} | |
fn main() -> Result { | |
let mut out = String::new(); | |
let world = "world"; | |
out.write_fmt(Arguments { | |
arguments: &[ | |
&DisplayArgument("Hello, "), | |
&DisplayArgument(world), | |
&DisplayArgument('!'), | |
], | |
})?; | |
println!("{}", out); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment