Skip to content

Commit

Permalink
Refactored WriteInto.
Browse files Browse the repository at this point in the history
Use `std::io::Write` instead of `std::fmt::Write`.

Remove `write_into::Display` later.
  • Loading branch information
TheVeryDarkness committed Aug 30, 2024
1 parent 6a9039a commit 6379df2
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 91 deletions.
8 changes: 7 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,19 @@
//! let s = format!("{}", v.sep_by(", "));
//! assert_eq!(s, "1, 2, 3");
//! ```
//!
//! Some higher-level functions are provided to write data sequence with default format to output:
//!
//! - [WriteInto::write()] (or [WriteInto::try_write()]) writes to [standard output](std::io::Stdout) with default format.
//! - [WriteInto::write_into()] (or [WriteInto::try_write_into()]) writes to given buffer that implements [std::fmt::Write] with default format.
//! - [WriteInto::write_into_string()] (or [WriteInto::try_write_into_string()]) writes to a new string with default format.
pub use {
formatted::SepBy,
mat::Mat,
read_into::{from_str::FromStr, ReadInto, ReadIntoError, ReadIntoSingle},
stdio::read_into::*,
stream::{InputStream, OutputStream},
stream::InputStream,
write_into::{display::Display, WriteInto},
};

Expand Down
25 changes: 25 additions & 0 deletions src/mat/row_iter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::Mat;
use std::{fmt::Debug, iter::FusedIterator};

/// Iterator over all rows.
#[derive(Copy)]
pub struct RowIter<'a, T> {
mat: &'a Mat<T>,
i: usize,
Expand Down Expand Up @@ -50,3 +52,26 @@ impl<'a, T> Iterator for RowIter<'a, T> {
self.mat.last_row()
}
}

impl<'a, T> Clone for RowIter<'a, T> {
fn clone(&self) -> Self {
Self {
mat: self.mat,
i: self.i,
}
}
}

impl<'a, T: Debug> Debug for RowIter<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.clone()).finish()
}
}

impl<'a, T> ExactSizeIterator for RowIter<'a, T> {
fn len(&self) -> usize {
self.mat.len_rows() - self.i
}
}

impl<'a, T> FusedIterator for RowIter<'a, T> {}
25 changes: 12 additions & 13 deletions src/read_into/from_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,23 @@ pub trait FromStr: Sized {
fn from_str(s: &str) -> Result<Self, Self::Err>;
}

macro_rules! impl_parse {
($ty:ty) => {
impl FromStr for $ty {
type Err = <$ty as str::FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
<$ty as str::FromStr>::from_str(s)
}
}
};
}
macro_rules! impl_parse_for {
/// Implement [FromStr] for given types that already implement [str::FromStr].
#[macro_export]
macro_rules! impl_from_str {
($($tys:ty)*) => {
$(impl_parse!($tys);)*
$(
impl $crate::read_into::FromStr for $tys {
type Err = <$tys as ::std::str::FromStr>::Err;
fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
<$tys as ::std::str::FromStr>::from_str(s)
}
}
)*
};
}

// Implement `Parse` for all types that implement FromStr.
impl_parse_for!(
impl_from_str!(
bool

i8 u8
Expand Down
21 changes: 20 additions & 1 deletion src/sep_by.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::fmt::{self, Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex};
use std::{
fmt::{self, Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex},
io::{self, Write},
};

use crate::WriteInto;

/// Separate items with given separator in [fmt::Display].
///
Expand Down Expand Up @@ -50,3 +55,17 @@ impl_for_sep_by!(UpperHex);
impl_for_sep_by!(Pointer);
impl_for_sep_by!(LowerExp);
impl_for_sep_by!(UpperExp);

impl<I: Iterator<Item = T> + Clone, T: WriteInto> WriteInto for SepBy<'_, I> {
fn try_write_into<S: Write>(&self, s: &mut S) -> Result<(), io::Error> {
let mut iter = self.iter.clone();
if let Some(first) = iter.next() {
first.try_write_into(s)?;
}
for item in iter {
let _ = s.write(self.sep.as_bytes())?;
item.try_write_into(s)?
}
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//!
//! Calling functions in this module will lock the standard input/output streams, so it is not
//! recommended to use this module in a multi-threaded environment.
use crate::stream::{InputStream, OutputStream};
use crate::stream::InputStream;
use std::{
cell::RefCell,
io::{StdinLock, StdoutLock},
Expand All @@ -14,5 +14,5 @@ pub(crate) mod read_into;

thread_local! {
pub(crate) static STDIN: RefCell<InputStream<StdinLock<'static>>> = RefCell::new(InputStream::new(std::io::stdin().lock()));
pub(crate) static STDOUT: RefCell<OutputStream<StdoutLock<'static>>> = RefCell::new(OutputStream::new(std::io::stdout().lock()));
pub(crate) static STDOUT: RefCell<StdoutLock<'static>> = RefCell::new(std::io::stdout().lock());
}
20 changes: 0 additions & 20 deletions src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,3 @@ impl<S: ReadInto<T> + ?Sized, T> Iterator for RealAll<'_, S, T> {
self.stream.try_read().ok()
}
}

/// A wrapper that converts [std::io::Write] into [std::fmt::Write].
pub struct OutputStream<W> {
buffer: W,
}

impl<W> OutputStream<W> {
/// Create an output stream from a buffer that implements [std::io::Write].
pub fn new(buffer: W) -> Self {
Self { buffer }
}
}

impl<W: std::io::Write> std::fmt::Write for OutputStream<W> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer
.write_all(s.as_bytes())
.map_err(|_| std::fmt::Error)
}
}
83 changes: 49 additions & 34 deletions src/write_into.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{mat::Mat, stdio::STDOUT};
use display::Display;
use std::{fmt, fmt::Write, ops::DerefMut};
use crate::{mat::Mat, stdio::STDOUT, SepBy};
use std::{io, io::Write, ops::DerefMut};

pub(crate) mod display;

Expand All @@ -10,6 +9,8 @@ macro_rules! unwrap {
};
}

type Result<T = ()> = io::Result<T>;

/// Write into a stream.
///
/// - All types that implement [display::Display] also implement this.
Expand All @@ -19,24 +20,24 @@ macro_rules! unwrap {
/// They write each row separated by a newline, and each item in a row separated by a space.
pub trait WriteInto {
/// Write into a stream.
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result;
fn try_write_into<S: Write>(&self, s: &mut S) -> Result;
/// Unwrapping version of [WriteInto::try_write_into].
fn write_into<S: Write>(&self, s: &mut S) {
unwrap!(self.try_write_into(s))
}
/// Write into a string.
fn try_write_into_string(&self) -> Result<String, fmt::Error> {
let mut s = String::new();
fn try_write_into_string(&self) -> Result<String> {
let mut s = Vec::new();
self.try_write_into(&mut s)?;
// What if the string is not valid UTF-8?
Ok(s)
Ok(String::from_utf8(s).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?)
}
/// Unwrapping version of [WriteInto::try_write_into_string].
fn write_into_string(&self) -> String {
unwrap!(self.try_write_into_string())
}
/// Write into [std::io::Stdout].
fn try_write(&self) -> fmt::Result {
fn try_write(&self) -> Result {
STDOUT.with(|lock| self.try_write_into(lock.borrow_mut().deref_mut()))
}
/// Unwrapping version of [WriteInto::try_write].
Expand All @@ -45,48 +46,62 @@ pub trait WriteInto {
}
}

impl<T: Display + ?Sized> WriteInto for T {
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result {
Display::fmt(self, s)
/// Implement [WriteInto] for given types that already implements [std::fmt::Display].
#[macro_export]
macro_rules! impl_write_into {
($($ty:ty)*) => {
$(
impl $crate::WriteInto for $ty {
fn try_write_into<S: Write>(&self, s: &mut S) -> ::std::io::Result<()> {
::std::write!(s, "{}", self)
}
}
)*
};
}

impl_write_into!(
i8 i16 i32 i64 i128 isize
u8 u16 u32 u64 u128 usize
f32 f64
bool
char str String
);

impl<T: WriteInto + ?Sized> WriteInto for &T {
fn try_write_into<S: Write>(&self, s: &mut S) -> Result {
WriteInto::try_write_into(*self, s)
}
}

impl<T: WriteInto> WriteInto for Vec<T> {
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result {
fn try_write_into<S: Write>(&self, s: &mut S) -> Result {
self.as_slice().try_write_into(s)
}
}

impl<T: WriteInto, const N: usize> WriteInto for [T; N] {
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result {
fn try_write_into<S: Write>(&self, s: &mut S) -> Result {
self.as_slice().try_write_into(s)
}
}

impl<T: WriteInto> WriteInto for [T] {
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result {
let mut iter = self.iter();
if let Some(first) = iter.next() {
first.try_write_into(s)?;
}
for item in iter {
s.write_str(" ")?;
item.try_write_into(s)?
}
Ok(())
impl<T> WriteInto for [T]
where
T: WriteInto,
{
fn try_write_into<S: Write>(&self, s: &mut S) -> Result {
WriteInto::try_write_into(&self.sep_by(" "), s)
}
}

impl<T: Display> WriteInto for Mat<T> {
fn try_write_into<S: Write>(&self, s: &mut S) -> fmt::Result {
let mut iter = self.iter();
if let Some(first) = iter.next() {
first.try_write_into(s)?;
}
for row in iter {
s.write_str("\n")?;
row.try_write_into(s)?
impl<T: WriteInto> WriteInto for Mat<T> {
fn try_write_into<S: Write>(&self, s: &mut S) -> Result {
fn row_sep_by<T: WriteInto>(
row: &[T],
) -> crate::sep_by::SepBy<'_, std::slice::Iter<'_, T>> {
row.iter().sep_by(" ")
}
Ok(())
self.iter().map(row_sep_by).sep_by("\n").try_write_into(s)
}
}
23 changes: 8 additions & 15 deletions src/write_into/display.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
use crate::sep_by;
use std::{fmt, fmt::Write};

type Result = fmt::Result;

/// Format trait with default formatting.
///
/// This is similar to [fmt::Display], but we need this to avoid conflicting.
pub trait Display {
#[deprecated]
pub trait Display: fmt::Display {
/// Write into a [Write].
fn fmt(&self, f: &mut impl Write) -> Result;
}

/// Implement [Display] for given types that already implements [fmt::Display].
#[macro_export]
macro_rules! impl_display {
($($ty:ty)*) => {
$(
impl Display for $ty {
fn fmt(&self, f: &mut impl Write) -> Result {
write!(f, "{}", self)
impl $crate::Display for $ty {
fn fmt(&self, f: &mut impl ::std::fmt::Write) -> ::std::fmt::Result {
::std::write!(f, "{}", self)
}
}
)*
Expand All @@ -33,15 +35,6 @@ impl_display!(

impl<T: Display + ?Sized> Display for &T {
fn fmt(&self, f: &mut impl Write) -> Result {
(*self).fmt(f)
}
}

impl<'a, I: Clone + Iterator> Display for sep_by::SepBy<'a, I>
where
I::Item: fmt::Display,
{
fn fmt(&self, f: &mut impl Write) -> Result {
write!(f, "{}", self)
Display::fmt(*self, f)
}
}
9 changes: 4 additions & 5 deletions tests/ill_buffer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use iof::{InputStream, OutputStream, ReadInto, ReadIntoSingle, WriteInto};
use iof::{InputStream, ReadInto, ReadIntoSingle, WriteInto};
use std::io;

struct IllBuffer;
Expand Down Expand Up @@ -46,16 +46,15 @@ fn try_read_remained_line_ill() {

#[test]
fn try_write_ill() {
let mut buf = OutputStream::new(IllBuffer);
let mut buf = IllBuffer;
let res: Result<(), _> = [1, 2, 3].try_write_into(&mut buf);
assert!(res.is_err());
let res: Result<(), _> = ["", "", ""].try_write_into(&mut buf);
assert!(res.is_err());
}

#[test]
#[should_panic = "an error occurred when formatting an argument"]
#[should_panic = "ill buffer"]
fn write_into_ill() {
let mut buf = OutputStream::new(IllBuffer);
[1, 2, 3].write_into(&mut buf);
[1, 2, 3].write_into(&mut IllBuffer);
}

0 comments on commit 6379df2

Please sign in to comment.