From d921f9fab2c5f25b90ae8fffd9980952abac6329 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Wed, 27 Jul 2022 16:11:54 +0300 Subject: [PATCH] core: implement to_estring for each type --- src/core.rs | 122 ++++++++++++++++++++++++++++++++++++----- src/low/trim.rs | 11 +++- src/std/bool.rs | 15 ++++- src/std/number.rs | 16 +++++- src/std/option.rs | 22 +++++++- src/structs/pair.rs | 30 ++++++++-- src/structs/sep_vec.rs | 36 +++++++++++- src/structs/trio.rs | 40 +++++++++++++- 8 files changed, 266 insertions(+), 26 deletions(-) diff --git a/src/core.rs b/src/core.rs index ac75545..872a975 100644 --- a/src/core.rs +++ b/src/core.rs @@ -2,11 +2,49 @@ //! string types //! -// TODO: add more info and examples. -/// Format a value fragment into a ``EString``. -pub trait FormatFragment { +/// Format this type and wrap into ``EString``. +/// +/// ``ToEString``’s `to_estring` method is often used implicitly, through ``EString``’s from. +/// +/// # Examples +/// +/// Basic implementation of ``ToEString`` on an example ``Point``. +/// +/// ```rust +/// use std::fmt::Write; +/// use estring::{EString, ToEString}; +/// +/// #[derive(Debug, PartialEq)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// +/// impl ToEString for Point { +/// fn to_estring(&self) -> EString { +/// let mut res = String::new(); +/// write!(res, "({},{})", self.x, self.y).ok().expect("Cannot format Point into EString"); +/// EString(res) +/// } +/// } +/// +/// let point = Point { x: 1, y: 2 }; +/// assert_eq!(point.to_estring(), EString::from("(1,2)")); +/// ``` +/// +pub trait ToEString { /// Format this type and returns ``EString``. - fn fmt_frag(&self) -> EString; + /// + /// # Examples + /// + /// ```rust + /// use estring::{EString, ToEString}; + /// + /// let i = 5; + /// let five = EString::from(5); + /// assert_eq!(five, i.to_estring()); + /// ``` + fn to_estring(&self) -> EString; } /// Parse a value fragment from a ``EString``. @@ -70,11 +108,55 @@ pub trait ParseFragment: Sized { fn parse_frag(es: EString) -> crate::Result; } -/// Wrapper under String type. +/// Wrapper under ``String`` type. +/// +/// # Examples +/// +/// You can create a ``EString`` from a any type that implement [Display] with ``EString::from`` +/// +/// ```rust +/// # use estring::EString; +/// let hello = EString::from("Hello, world"); +/// let num = EString::from("999"); +/// ``` +/// +/// You can create a ``EString`` from a any type that implement ``ToEString`` with +/// ``ToEString::to_estring``. +/// +/// ```rust +/// # use estring::ToEString; +/// let some_opt = Some(999).to_estring(); +/// let none_opt = None::.to_estring(); +/// ``` +/// #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct EString(pub String); +impl std::fmt::Display for EString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + impl EString { + /// Creates a new empty ``EString``. + /// + /// This will not allocate any inital buffer. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```rust + /// # use estring::EString; + /// let s = EString::new(); + /// ``` + #[must_use] + #[inline] + pub fn new() -> Self { + Self(String::new()) + } + /// Parses this inner string into another type. /// /// `parse` can parse into any type that implements the ``ParseFragment`` trait. @@ -110,11 +192,11 @@ impl EString { impl From for EString where - T: std::fmt::Display, + T: ToEString, { #[inline] fn from(val: T) -> Self { - Self(val.to_string()) + val.to_estring() } } @@ -129,22 +211,36 @@ impl std::ops::Deref for EString { impl ParseFragment for EString { #[inline] - fn parse_frag(value: EString) -> crate::Result { - Ok(value) + fn parse_frag(es: EString) -> crate::Result { + Ok(es) } } impl ParseFragment for String { #[inline] - fn parse_frag(s: EString) -> crate::Result { - Ok(s.0) + fn parse_frag(es: EString) -> crate::Result { + Ok(es.0) + } +} + +impl ToEString for String { + #[inline] + fn to_estring(&self) -> EString { + EString(self.clone()) } } impl ParseFragment for &'static str { #[inline] - fn parse_frag(s: EString) -> crate::Result { - Ok(Box::leak(s.0.into_boxed_str())) + fn parse_frag(es: EString) -> crate::Result { + Ok(Box::leak(es.0.into_boxed_str())) + } +} + +impl<'a> ToEString for &'a str { + #[inline] + fn to_estring(&self) -> EString { + EString((*self).to_string()) } } diff --git a/src/low/trim.rs b/src/low/trim.rs index 224826c..212b3d3 100644 --- a/src/low/trim.rs +++ b/src/low/trim.rs @@ -1,4 +1,4 @@ -use crate::{core::EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; /// Wrapper that allow to trim substring before continue /// @@ -42,6 +42,15 @@ where } } +impl ToEString for Trim +where + T: ToEString, +{ + fn to_estring(&self) -> EString { + self.0.to_estring() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/std/bool.rs b/src/std/bool.rs index 0edf4ae..b041acd 100644 --- a/src/std/bool.rs +++ b/src/std/bool.rs @@ -1,4 +1,4 @@ -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; use crate::error::{Error, Reason}; impl ParseFragment for bool { @@ -12,6 +12,13 @@ impl ParseFragment for bool { } } +impl ToEString for bool { + #[inline] + fn to_estring(&self) -> EString { + EString(self.to_string()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -52,4 +59,10 @@ mod tests { _ => unreachable!(), }; } + + #[test] + fn should_format_bool() { + assert_eq!(true.to_estring(), EString(String::from("true"))); + assert_eq!(false.to_estring(), EString(String::from("false"))); + } } diff --git a/src/std/number.rs b/src/std/number.rs index 555ebb8..44d2f0e 100644 --- a/src/std/number.rs +++ b/src/std/number.rs @@ -1,4 +1,4 @@ -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; use crate::error::{Error, Reason}; #[doc(hidden)] @@ -11,6 +11,13 @@ macro_rules! from_env_string_numbers_impl { s.0.parse::().map_err(|_| Error(s, Reason::Parse)) } } + + impl ToEString for $ty { + #[inline] + fn to_estring(&self) -> EString { + EString(self.to_string()) + } + } )+ }; } @@ -56,4 +63,11 @@ mod tests { _ => unreachable!(), }; } + + #[test] + fn should_format_number() { + assert_eq!((-1).to_estring(), EString(String::from("-1"))); + assert_eq!(10.to_estring(), EString(String::from("10"))); + assert_eq!(1.1.to_estring(), EString(String::from("1.1"))); + } } diff --git a/src/std/option.rs b/src/std/option.rs index b592bdd..191ad39 100644 --- a/src/std/option.rs +++ b/src/std/option.rs @@ -1,4 +1,16 @@ -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; + +impl ToEString for Option +where + T: ToEString, +{ + fn to_estring(&self) -> EString { + match self { + Some(inner) => inner.to_estring(), + None => EString::new(), + } + } +} impl ParseFragment for Option where @@ -20,7 +32,7 @@ mod tests { #[test] fn should_parse_empty_string_as_none() { - let estr = EString::from(""); + let estr = EString::new(); match estr.parse::>() { Ok(res) => assert_eq!(res, None), _ => unreachable!(), @@ -44,4 +56,10 @@ mod tests { _ => unreachable!(), } } + + #[test] + fn should_format_option() { + assert_eq!(None::.to_estring(), EString::new()); + assert_eq!(Some(99).to_estring(), EString(String::from("99"))); + } } diff --git a/src/structs/pair.rs b/src/structs/pair.rs index 40911cc..18e2bb6 100644 --- a/src/structs/pair.rs +++ b/src/structs/pair.rs @@ -1,7 +1,7 @@ //! Contains the implementations to pair tuple type //! -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; use crate::{Error, Reason}; use std::fmt::Write; @@ -39,13 +39,25 @@ where B: std::fmt::Display, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.to_string())?; - f.write_char(S1)?; - f.write_str(&self.1.to_string()) + write!(f, "{}{}{}", self.0, S1, self.1) } } -impl ParseFragment for Pair +impl ToEString for Pair +where + A: ToEString, + B: ToEString, +{ + fn to_estring(&self) -> EString { + let mut res = String::new(); + write!(res, "{}{}{}", self.0.to_estring(), S1, self.1.to_estring()) + .ok() + .expect("Cannot parse Pair to EString"); + EString(res) + } +} + +impl ParseFragment for Pair where A: ParseFragment, B: ParseFragment, @@ -102,4 +114,12 @@ hello=bar", _ => unreachable!(), }; } + + #[test] + fn should_format_pair() { + let pair = Pair::<_, '+', _>(1, 2); + assert_eq!(pair.clone().to_estring(), EString(String::from("1+2"))); + let pair_in_pair = Pair::<_, '=', _>(3, pair); + assert_eq!(pair_in_pair.to_estring(), EString(String::from("3=1+2"))); + } } diff --git a/src/structs/sep_vec.rs b/src/structs/sep_vec.rs index ca1ab18..1208642 100644 --- a/src/structs/sep_vec.rs +++ b/src/structs/sep_vec.rs @@ -1,7 +1,7 @@ //! Contains the implementations to vec type //! -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; use std::fmt::Write; /// Wrapper for ``Vec`` to split string by a separator (`SEP`). @@ -51,11 +51,32 @@ where f.write_char(SEP)?; } - f.write_str(&part.to_string()) + write!(f, "{}", part) }) } } +impl ToEString for SepVec +where + T: ToEString, +{ + fn to_estring(&self) -> EString { + self.0 + .iter() + .enumerate() + .try_fold(String::new(), |mut res, (i, part)| { + if i != 0 { + res.write_char(SEP).ok()?; + } + + write!(res, "{}", part.to_estring()).ok()?; + Some(res) + }) + .map(EString) + .expect("Cannot format SepVec ${self.0} to EString") + } +} + impl ParseFragment for SepVec where T: ParseFragment, @@ -74,6 +95,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::Pair; use crate::{Error, Reason}; const COMMA: char = ','; @@ -139,4 +161,14 @@ d,e"; _ => unreachable!(), }; } + + #[test] + fn should_format_vec() { + type PlusPair = Pair; + + let vec = SepVec::<_, ','>::from(vec![1, 2, 3]); + assert_eq!(vec.to_estring(), EString(String::from("1,2,3"))); + let vec = SepVec::<_, ','>::from(vec![PlusPair::from((1, 2)), PlusPair::from((3, 4))]); + assert_eq!(vec.to_estring(), EString(String::from("1+2,3+4"))); + } } diff --git a/src/structs/trio.rs b/src/structs/trio.rs index cea088f..986892c 100644 --- a/src/structs/trio.rs +++ b/src/structs/trio.rs @@ -2,7 +2,7 @@ //! use super::Pair; -use crate::core::{EString, ParseFragment}; +use crate::core::{EString, ParseFragment, ToEString}; use std::fmt::Write; /// Wrapper for trio (A, B, C) tuple to split string by separators (`S1` and `S2`). @@ -46,6 +46,29 @@ where } } +impl ToEString for Trio +where + A: ToEString, + B: ToEString, + C: ToEString, +{ + fn to_estring(&self) -> EString { + let mut res = String::new(); + write!( + res, + "{}{}{}{}{}", + self.0.to_estring(), + S1, + self.1.to_estring(), + S2, + self.2.to_estring() + ) + .ok() + .expect("Cannot parse Pair to EString"); + EString(res) + } +} + impl ParseFragment for Trio where A: ParseFragment, @@ -91,4 +114,19 @@ mod tests { _ => unreachable!(), }; } + + #[test] + fn should_format_trio() { + let trio = Trio::<_, '+', _, '-', _>::from(("foo", "baz", "bar")); + assert_eq!( + trio.clone().to_estring(), + EString(String::from("foo+baz-bar")) + ); + + let trio_in_trio = Trio::<_, '*', _, '=', _>::from(("foo", "baz", trio)); + assert_eq!( + trio_in_trio.clone().to_estring(), + EString(String::from("foo*baz=foo+baz-bar")) + ); + } }