Skip to content

Commit

Permalink
Merge pull request #38 from baoyachi/issue/33
Browse files Browse the repository at this point in the history
fix parsing error location precise prompt
  • Loading branch information
baoyachi authored May 10, 2024
2 parents 4fd7ca3 + 6077ed1 commit b104c55
Show file tree
Hide file tree
Showing 7 changed files with 462 additions and 228 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "duration-str"
version = "0.9.1"
version = "0.10.0"
authors = ["baoyachi <liaoymxsdl@gmail.com>"]
edition = "2021"
description = "duration string parser"
Expand All @@ -20,7 +20,7 @@ time = { version = "0.3.17", optional = true }

serde = { version = "1.0.147", features = ["derive"], optional = true }
rust_decimal = { version = "1.29.1", default-features = false }
winnow = "0.6.6"
winnow = "0.6.8"

[dev-dependencies]
serde_json = { version = "1.0.87" }
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ fn main() {
let duration = parse("3m + 31").unwrap(); //the default duration unit is second.
assert_eq!(duration, Duration::new(211, 0));

let duration = parse("3m31s").unwrap();
assert_eq!(duration, Duration::new(211, 0));

let duration = parse("3m + 13s + 29ms").unwrap();
assert_eq!(duration, Duration::new(193, 29 * 1000 * 1000 + 0 + 0));

Expand Down
68 changes: 68 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::fmt::{Display, Formatter};
use thiserror::Error;
use winnow::error::{ErrorKind, FromExternalError, ParserError};
use winnow::stream::Stream;

#[derive(Error, Debug, PartialEq)]
pub enum DError {
#[error("{0}")]
ParseError(String),
#[error("overflow error")]
OverflowError,
}

#[derive(Debug, PartialEq, Eq)]
pub struct PError<I> {
pub partial_input: I,
kind: ErrorKind,
cause: String,
}

impl<I> PError<I> {
fn new(input: I, kind: ErrorKind) -> Self {
PError {
partial_input: input,
kind,
cause: "".to_string(),
}
}

pub fn append_cause<C: AsRef<str>>(mut self, cause: C) -> Self {
self.cause = cause.as_ref().to_string();
self
}
}

impl<I: Stream + Clone> ParserError<I> for PError<I> {
fn from_error_kind(input: &I, kind: ErrorKind) -> Self {
PError::new(input.clone(), kind)
}

fn append(self, _: &I, _: &<I as Stream>::Checkpoint, _: ErrorKind) -> Self {
self
}
}

impl<I: Clone, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E> for PError<I> {
#[inline]
fn from_external_error(input: &I, kind: ErrorKind, e: E) -> Self {
let mut err = Self::new(input.clone(), kind);
{
err.cause = e.to_string();
}
err
}
}

impl<I> Display for PError<I>
where
I: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "partial_input:{}", self.partial_input)?;
if !self.cause.is_empty() {
write!(f, ", {}", self.cause)?;
}
Ok(())
}
}
120 changes: 40 additions & 80 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,77 +165,32 @@
//! }
//! ```

mod error;
pub(crate) mod macros;
mod parser;
#[cfg(feature = "serde")]
mod serde;
mod unit;

pub use parser::parse;
#[cfg(feature = "serde")]
pub use serde::*;
use std::fmt::{Debug, Display};

use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use std::str::FromStr;
use std::time::Duration;
use thiserror::Error;

use crate::error::DError;
use crate::unit::TimeUnit;
#[cfg(feature = "chrono")]
pub use naive_date::{
after_naive_date, after_naive_date_time, before_naive_date, before_naive_date_time,
};

pub type DResult<T> = Result<T, DError>;

#[derive(Error, Debug, PartialEq)]
pub enum DError {
#[error("`{0}`")]
ParseError(String),
#[error("`{0}`")]
NormalError(String),
#[error("overflow error")]
OverflowError,
}

#[derive(Debug, Eq, PartialEq, Default, Clone)]
enum TimeUnit {
Year,
Month,
Week,
Day,
Hour,
Minute,
#[default]
Second,
MilliSecond,
MicroSecond,
NanoSecond,
}

impl FromStr for TimeUnit {
type Err = DError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match &*s.to_lowercase() {
"y" | "year" => Ok(TimeUnit::Year),
"mon" | "month" => Ok(TimeUnit::Month),
"w" | "week" => Ok(TimeUnit::Week),
"d" | "day" => Ok(TimeUnit::Day),
"h" | "hour" | "hr" => Ok(TimeUnit::Hour),
"m" | "min" | "minute" => Ok(TimeUnit::Minute),
"s" | "sec" | "second" => Ok(TimeUnit::Second),
"ms" | "msec" | "millisecond" => Ok(TimeUnit::MilliSecond),
"µs" | "µsec" | "µsecond" | "us" | "usec" | "usecond" | "microsecond" => {
Ok(TimeUnit::MicroSecond)
}
"ns" | "nsec" | "nanosecond" => Ok(TimeUnit::NanoSecond),
_ => Err(DError::ParseError(format!(
"expect one of [y,mon,w,d,h,m,s,ms,µs,us,ns] or their longer forms.but find:{}",
s,
))),
}
}
}

const ONE_MICROSECOND_NANOSECOND: u64 = 1000;
const ONE_MILLISECOND_NANOSECOND: u64 = 1000 * ONE_MICROSECOND_NANOSECOND;
const ONE_SECOND_NANOSECOND: u64 = 1000 * ONE_MILLISECOND_NANOSECOND;
Expand All @@ -251,37 +206,42 @@ fn one_second_decimal() -> Decimal {
1_000_000_000.into()
}

impl TimeUnit {
fn duration(&self, time_str: impl AsRef<str>) -> DResult<u64> {
let time = time_str
.as_ref()
.parse::<u64>()
.map_err(|err| DError::ParseError(err.to_string()))?;
let unit = match self {
TimeUnit::Year => ONE_YEAR_NANOSECOND,
TimeUnit::Month => ONE_MONTH_NANOSECOND,
TimeUnit::Week => ONE_WEEK_NANOSECOND,
TimeUnit::Day => ONE_DAY_NANOSECOND,
TimeUnit::Hour => ONE_HOUR_NANOSECOND,
TimeUnit::Minute => ONE_MINUTE_NANOSECOND,
TimeUnit::Second => ONE_SECOND_NANOSECOND,
TimeUnit::MilliSecond => ONE_MILLISECOND_NANOSECOND,
TimeUnit::MicroSecond => ONE_MICROSECOND_NANOSECOND,
TimeUnit::NanoSecond => 1,
};
time.checked_mul(unit).ok_or(DError::OverflowError)
}
}

const PLUS: &str = "+";
const STAR: &str = "*";

trait ExpectErr<const LEN: usize> {
fn expect_val() -> [&'static str; LEN];
fn expect_err<S: AsRef<str> + Display>(s: S) -> String;
}

#[derive(Debug, Eq, PartialEq, Clone)]
enum CondUnit {
Plus,
Star,
}

impl FromStr for CondUnit {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"+" => Ok(CondUnit::Plus),
"*" => Ok(CondUnit::Star),
_ => Err(Self::expect_err(s)),
}
}
}

impl ExpectErr<2> for CondUnit {
fn expect_val() -> [&'static str; 2] {
["+", "*"]
}

fn expect_err<S: AsRef<str> + Display>(s: S) -> String {
format!("expect one of:{:?}, but find:{}", Self::expect_val(), s)
}
}

impl CondUnit {
fn init() -> (Self, u64) {
(CondUnit::Star, ONE_SECOND_NANOSECOND)
Expand Down Expand Up @@ -324,10 +284,9 @@ impl Calc<(CondUnit, u64)> for Vec<(&str, CondUnit, TimeUnit)> {
init_cond = cond.clone();
init_duration = init_cond.change_duration();
} else if &init_cond != cond {
return Err(DError::NormalError(format!(
return Err(DError::ParseError(format!(
"not support '{}' with '{}' calculate",
init_cond.to_string(),
cond.to_string()
init_cond, cond
)));
}
match init_cond {
Expand All @@ -349,12 +308,13 @@ impl Calc<(CondUnit, u64)> for Vec<(&str, CondUnit, TimeUnit)> {
}
}

impl ToString for CondUnit {
fn to_string(&self) -> String {
match self {
impl Display for CondUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::Plus => PLUS.to_string(),
Self::Star => STAR.to_string(),
}
};
write!(f, "{}", str)
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(test)]
#[macro_export]
macro_rules! catch_err {
($result:expr) => {
format!("{}", $result.err().unwrap())
};
}
Loading

0 comments on commit b104c55

Please sign in to comment.