Skip to content

Instantly share code, notes, and snippets.

@KamilaBorowska
Created April 1, 2019 08:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KamilaBorowska/45235f7228b857c732dfe1c6febbf5b6 to your computer and use it in GitHub Desktop.
Save KamilaBorowska/45235f7228b857c732dfe1c6febbf5b6 to your computer and use it in GitHub Desktop.
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