Skip to main content

fundu_core/
config.rs

1// Copyright (c) 2023 Joining7943 <joining@posteo.de>
2//
3// This software is released under the MIT License.
4// https://opensource.org/licenses/MIT
5
6//! Provide the [`Config`], [`ConfigBuilder`] and other structures used to adjust the parsing
7//! process
8
9use crate::time::{DEFAULT_TIME_UNIT, Multiplier, TimeUnit};
10
11pub(crate) const DEFAULT_CONFIG: Config = Config::new();
12
13/// An ascii delimiter defined as closure.
14///
15/// The [`Delimiter`] is a type alias for a closure taking a `u8` byte and returning a `bool`.
16///
17/// # Problems
18///
19/// The parser relies on the property that this delimiter is defined to match ascii characters. The
20/// delimiter takes a `u8` as input, but matching any non-ascii (`0x80 - 0xff`) bytes may lead to a
21/// [`crate::error::ParseError`] or panic if the input string contains multi-byte utf-8 characters.
22///
23/// # Examples
24///
25/// ```rust
26/// use fundu_core::config::Delimiter;
27///
28/// fn is_delimiter(delimiter: Delimiter, byte: u8) -> bool {
29///     delimiter(byte)
30/// }
31///
32/// assert!(is_delimiter(
33///     |byte| matches!(byte, b' ' | b'\n' | b'\t'),
34///     b' '
35/// ));
36/// assert!(!is_delimiter(
37///     |byte| matches!(byte, b' ' | b'\n' | b'\t'),
38///     b'\r'
39/// ));
40/// assert!(is_delimiter(|byte| byte.is_ascii_whitespace(), b'\r'));
41/// ```
42pub type Delimiter = fn(u8) -> bool;
43
44/// [`NumbersLike`] strings can occur where usually a number would occur in the source string
45///
46/// `NumbersLike` words or strings express a number as a word like `one` or `next` instead of `1` or
47/// `half` instead of `0.5` but also `last` instead of `-1` etc. This symbolic number is defined
48/// as a [`Multiplier`]. In contrast to numbers, `NumbersLike` without a time unit are considered an
49/// error and therefore have to be followed by a time unit.
50///
51/// # Examples
52///
53/// ```rust
54/// use fundu_core::config::NumbersLike;
55/// use fundu_core::time::Multiplier;
56///
57/// struct Numerals {}
58/// impl NumbersLike for Numerals {
59///     fn get(&self, input: &str) -> Option<Multiplier> {
60///         match input {
61///             "one" | "next" => Some(Multiplier(1, 0)),
62///             "this" => Some(Multiplier(0, 0)),
63///             "last" => Some(Multiplier(-1, 0)),
64///             "half" => Some(Multiplier(5, -1)),
65///             _ => None,
66///         }
67///     }
68/// }
69///
70/// let numerals = Numerals {};
71/// assert_eq!(numerals.get("one"), Some(Multiplier(1, 0)));
72/// ```
73pub trait NumbersLike {
74    fn get(&self, input: &str) -> Option<Multiplier>;
75}
76
77/// The structure containing all options for the [`crate::parse::Parser`]
78///
79/// This struct is highly likely to change often, so it is not possible to create the `Config` with
80/// `Config { ... }` outside of this crate. Instead, use [`Config::new`], [`Config::default`] or the
81/// [`ConfigBuilder`] to create a new `Config`.
82///
83/// # Examples
84///
85/// Here's a little bit more involved example to see the effects of the configuration in action. To
86/// keep the example small, [`crate::time::TimeUnitsLike`] is implemented in such a way that no time
87/// units are allowed in the input string. The final `Config` uses [`TimeUnit::MilliSecond`] as
88/// default time unit instead of [`TimeUnit::Second`] and allows negative durations.
89///
90/// ```rust
91/// use fundu_core::config::{Config, ConfigBuilder};
92/// use fundu_core::parse::Parser;
93/// use fundu_core::time::{Duration, Multiplier, TimeUnit, TimeUnitsLike};
94///
95/// struct TimeUnits {}
96/// impl TimeUnitsLike for TimeUnits {
97///     fn is_empty(&self) -> bool {
98///         true
99///     }
100///
101///     fn get(&self, string: &str) -> Option<(TimeUnit, Multiplier)> {
102///         None
103///     }
104/// }
105///
106/// const TIME_UNITS: TimeUnits = TimeUnits {};
107///
108/// const CONFIG: Config = ConfigBuilder::new()
109///     .default_unit(TimeUnit::MilliSecond)
110///     .allow_negative()
111///     .build();
112///
113/// const PARSER: Parser = Parser::with_config(CONFIG);
114///
115/// assert_eq!(
116///     PARSER.parse("1000", &TIME_UNITS, None, None),
117///     Ok(Duration::positive(1, 0))
118/// );
119/// assert_eq!(
120///     PARSER.parse("-1", &TIME_UNITS, None, None),
121///     Ok(Duration::negative(0, 1_000_000))
122/// );
123/// ```
124#[derive(Debug, PartialEq, Eq, Clone)]
125#[allow(clippy::struct_excessive_bools)]
126#[allow(unpredictable_function_pointer_comparisons)]
127#[non_exhaustive]
128pub struct Config<'a> {
129    /// The [`TimeUnit`] the parser applies if no time unit was given (Default: `TimeUnit::Second`)
130    ///
131    /// A configuration with `TimeUnit::MilliSecond` would parse a string without time units like
132    /// `"1000"` to a [`crate::time::Duration`] with `Duration::positive(1, 0)` which is worth `1`
133    /// second.
134    pub default_unit: TimeUnit,
135
136    /// The default [`Multiplier`] used internally to make custom time units possible
137    ///
138    /// This field is only used internally and it is not recommended to change this setting!
139    pub default_multiplier: Multiplier,
140
141    /// Disable parsing an exponent (Default: `false`)
142    ///
143    /// The exponent in the input string may start with an `e` or `E` followed by an optional sign
144    /// character and a mandatory number like in `"1e+20"`, `"2E-3"` ... Occurrences of an exponent
145    /// in strings like `"1e20"`,`"10E2"`, `"3.4e-10"` lead to a [`crate::error::ParseError`].
146    pub disable_exponent: bool,
147
148    /// Disable parsing a fraction (Default: `false`)
149    ///
150    /// The fraction in the input string starts with a point character `.` followed by an optional
151    /// number like in `"1.234"` but also `"1."` as long as there is a number present before the
152    /// point. Occurrences of a fraction like in `"1.234"` when a fraction is not allowed by this
153    /// setting lead to a [`crate::error::ParseError`].
154    pub disable_fraction: bool,
155
156    /// Disable parsing infinity (Default: `false`)
157    ///
158    /// An infinity in the input string is either `"inf"` or `"infinity"` case insensitive
159    /// optionally preceded by a sign character. Infinity values evaluate to the maximum possible
160    /// duration or if negative to the maximum negative value of the duration.
161    pub disable_infinity: bool,
162
163    /// Make a number in the input string optional (Default: `false`)
164    ///
165    /// Usually, a time unit needs a number like in `"1second"`. With this setting set to `true` a
166    /// time unit can occur without number like `"second"` and a number with value `1` is assumed.
167    pub number_is_optional: bool,
168
169    /// If `true`, this setting allows multiple `durations` in the input (Default: `false`)
170    ///
171    /// Multiple durations may be delimited by the [`Config::outer_delimiter`]. A subsequent
172    /// duration is also recognized if it starts with a digit or a sign character.
173    pub allow_multiple: bool,
174
175    /// If parsing multiple durations, allow conjunctions in addition to the [`Delimiter`]
176    /// (Default: `None`)
177    ///
178    /// Conjunctions are words like `"and"`, `"or"` but also single characters like `","` or ";".
179    /// So, a string like `"3seconds and 1second"` would parse to a `Duration::positive(4, 0)` and
180    /// `"1second, 2seconds"` would parse to a `Duration::positive(3, 0)`. Unlike a [`Delimiter`],
181    /// conjunctions can occur only once between two durations.
182    pub conjunctions: Option<&'a [&'a str]>,
183
184    /// Allow parsing negative durations (Default: `false`)
185    ///
186    /// Negative durations usually start with a `-` sign like in `-1second` which would evaluate to
187    /// a `Duration::negative(1, 0)`. But it can also be indicated by the `ago` keyword if the
188    /// `allow_ago` settings is set to `true`.
189    pub allow_negative: bool,
190
191    /// This delimiter may occur within a duration
192    ///
193    /// The occurrences of this delimiter depend on other configuration settings:
194    /// * If `allow_sign_delimiter` is set, this delimiter may separate the sign from the number
195    /// * If `allow_time_unit_delimiter` is set, this delimiter may separate the number from the
196    ///   time unit
197    /// * If `allow_ago` is set, this delimiter has to separate the time unit from the `ago`
198    ///   keyword
199    ///
200    /// If parsing with [`NumbersLike`] numerals, then this delimiter has to separate the numeral
201    /// from the time unit.
202    pub inner_delimiter: Delimiter,
203
204    /// This delimiter may occur to separate multiple durations and [`Config::conjunctions`]
205    ///
206    /// This delimiter is used only if `allow_multiple` is set.
207    pub outer_delimiter: Delimiter,
208
209    /// Allow the ago keyword to indicate a negative duration (Default: false)
210    ///
211    /// The `ago` keyword must be delimited by the [`Config::inner_delimiter`] from the time unit.
212    /// IMPORTANT: If `allow_ago` is set to `true` it's also necessary to set `allow_negative` to
213    /// `true` explicitly.
214    ///
215    /// The `ago` keyword can only occur in conjunction with a time unit like in `"1second ago"`
216    /// which would result in a `Duration::negative(1, 0)`. `"1 ago"` and `"1ago"` would result in
217    /// a [`crate::error::ParseError`].
218    pub allow_ago: bool,
219
220    /// Allow the [`Config::inner_delimiter`] between the sign and a number, time keyword ...
221    /// (Default: `false`)
222    ///
223    /// For example, setting the `inner_delimiter` to `|byte| matches!(byte, b' ' | b'\n')` would
224    /// parse strings like `"+1ms"`, `"- 1ms"`, `"+   yesterday"`, `"+\n4e2000years"` ...
225    pub allow_sign_delimiter: bool,
226
227    /// Allow a [`Delimiter`] between the number and time unit (Default: `false`)
228    ///
229    /// This setting does not enforce the [`Config::inner_delimiter`], so time units directly
230    /// following the number are still parsed without error. A delimiter may occur multiple times.
231    ///
232    /// For example, setting this option with an `inner_delimiter` of `|byte| matches!(byte, b' ' |
233    /// b'\n')` would parse strings like `"1ms"`, `"1 ms"`, `"3.2 minutes"`, `"4e2000 \n years"`
234    /// ...
235    pub allow_time_unit_delimiter: bool,
236}
237
238impl Default for Config<'_> {
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244impl<'a> Config<'a> {
245    /// Create a new default configuration
246    ///
247    /// Please see the documentation of the fields of this `struct` for more information and their
248    /// default values.
249    ///
250    /// # Examples
251    ///
252    /// ```rust
253    /// use fundu_core::config::Config;
254    /// use fundu_core::time::{Multiplier, TimeUnit};
255    ///
256    /// const DEFAULT_CONFIG: Config = Config::new();
257    ///
258    /// assert_eq!(DEFAULT_CONFIG.default_unit, TimeUnit::Second);
259    /// assert_eq!(DEFAULT_CONFIG.allow_time_unit_delimiter, false);
260    /// assert_eq!(DEFAULT_CONFIG.default_multiplier, Multiplier(1, 0));
261    /// assert_eq!(DEFAULT_CONFIG.disable_exponent, false);
262    /// assert_eq!(DEFAULT_CONFIG.disable_fraction, false);
263    /// assert_eq!(DEFAULT_CONFIG.number_is_optional, false);
264    /// assert_eq!(DEFAULT_CONFIG.disable_infinity, false);
265    /// assert_eq!(DEFAULT_CONFIG.allow_multiple, false);
266    /// assert_eq!(DEFAULT_CONFIG.conjunctions, None);
267    /// assert_eq!(DEFAULT_CONFIG.allow_negative, false);
268    /// assert_eq!(DEFAULT_CONFIG.allow_ago, false);
269    /// ```
270    pub const fn new() -> Self {
271        Self {
272            allow_time_unit_delimiter: false,
273            default_unit: DEFAULT_TIME_UNIT,
274            default_multiplier: Multiplier(1, 0),
275            disable_exponent: false,
276            disable_fraction: false,
277            number_is_optional: false,
278            disable_infinity: false,
279            allow_multiple: false,
280            conjunctions: None,
281            allow_negative: false,
282            allow_ago: false,
283            allow_sign_delimiter: false,
284            inner_delimiter: |byte| byte.is_ascii_whitespace(),
285            outer_delimiter: |byte| byte.is_ascii_whitespace(),
286        }
287    }
288
289    /// Convenience method to use the [`ConfigBuilder`] to build this `Config`
290    ///
291    /// # Examples
292    ///
293    /// ```rust
294    /// use fundu_core::config::{Config, ConfigBuilder};
295    /// use fundu_core::time::TimeUnit;
296    ///
297    /// let config = Config::builder()
298    ///     .disable_infinity()
299    ///     .allow_negative()
300    ///     .build();
301    ///
302    /// assert_eq!(config.disable_infinity, true);
303    /// assert_eq!(config.allow_negative, true);
304    /// ```
305    pub const fn builder() -> ConfigBuilder<'a> {
306        ConfigBuilder::new()
307    }
308}
309
310/// A builder to create a [`Config`]
311///
312/// The `ConfigBuilder` starts with the default `Config` and can create the `Config` with the
313/// [`ConfigBuilder::build`] method as const at compile time.
314///
315/// # Examples
316///
317/// ```rust
318/// use fundu_core::config::{Config, ConfigBuilder};
319/// use fundu_core::time::TimeUnit;
320///
321/// const CONFIG: Config = ConfigBuilder::new()
322///     .default_unit(TimeUnit::MilliSecond)
323///     .disable_fraction()
324///     .build();
325///
326/// assert_eq!(CONFIG.default_unit, TimeUnit::MilliSecond);
327/// assert_eq!(CONFIG.disable_fraction, true);
328/// ```
329#[derive(Debug, PartialEq, Eq, Clone, Default)]
330pub struct ConfigBuilder<'a> {
331    config: Config<'a>,
332}
333
334impl<'a> ConfigBuilder<'a> {
335    /// Create a new `ConfigBuilder` with the default [`Config`] as base.
336    ///
337    /// Note the `ConfigBuilder` can build the [`Config`] as const at compile time if needed.
338    ///
339    /// # Examples
340    ///
341    /// ```rust
342    /// use fundu_core::config::ConfigBuilder;
343    ///
344    /// let config = ConfigBuilder::new().allow_negative().build();
345    ///
346    /// assert_eq!(config.allow_negative, true);
347    /// ```
348    pub const fn new() -> Self {
349        Self {
350            config: Config::new(),
351        }
352    }
353
354    /// Build the [`Config`] with the configuration of this builder
355    ///
356    /// # Examples
357    ///
358    /// ```rust
359    /// use fundu_core::config::{Config, ConfigBuilder};
360    ///
361    /// const CONFIG: Config = ConfigBuilder::new().disable_fraction().build();
362    ///
363    /// assert_eq!(CONFIG.disable_fraction, true);
364    /// ```
365    pub const fn build(self) -> Config<'a> {
366        self.config
367    }
368
369    /// Allow a [`Delimiter`] between the number and time unit (Default: `None`)
370    ///
371    /// See also the documentation of [`Config::allow_time_unit_delimiter`]
372    ///
373    /// # Examples
374    ///
375    /// ```rust
376    /// use fundu_core::config::{Config, ConfigBuilder};
377    ///
378    /// const CONFIG: Config = ConfigBuilder::new().allow_time_unit_delimiter().build();
379    ///
380    /// assert!(CONFIG.allow_time_unit_delimiter);
381    /// ```
382    ///
383    /// or with another whitespace definition for the `inner_delimiter`
384    ///
385    /// ```rust
386    /// use fundu_core::config::{Config, ConfigBuilder};
387    ///
388    /// const CONFIG: Config = ConfigBuilder::new()
389    ///     .allow_time_unit_delimiter()
390    ///     .inner_delimiter(|byte| byte == b' ')
391    ///     .build();
392    ///
393    /// assert!(CONFIG.allow_time_unit_delimiter);
394    /// assert!((CONFIG.inner_delimiter)(b' '));
395    /// ```
396    pub const fn allow_time_unit_delimiter(mut self) -> Self {
397        self.config.allow_time_unit_delimiter = true;
398        self
399    }
400
401    /// The [`TimeUnit`] the parser applies if no time unit was given (Default: `TimeUnit::Second`)
402    ///
403    /// See also the documentation of [`Config::default_unit`]
404    ///
405    /// # Examples
406    ///
407    /// ```rust
408    /// use fundu_core::config::{Config, ConfigBuilder};
409    /// use fundu_core::time::TimeUnit;
410    ///
411    /// const CONFIG: Config = ConfigBuilder::new()
412    ///     .default_unit(TimeUnit::MilliSecond)
413    ///     .build();
414    ///
415    /// assert_eq!(CONFIG.default_unit, TimeUnit::MilliSecond);
416    /// ```
417    pub const fn default_unit(mut self, time_unit: TimeUnit) -> Self {
418        self.config.default_unit = time_unit;
419        self
420    }
421
422    /// Disable parsing an exponent (Default: `false`)
423    ///
424    /// See also the documentation of [`Config::disable_exponent`]
425    ///
426    /// # Examples
427    ///
428    /// ```rust
429    /// use fundu_core::config::{Config, ConfigBuilder};
430    ///
431    /// const CONFIG: Config = ConfigBuilder::new().disable_exponent().build();
432    ///
433    /// assert_eq!(CONFIG.disable_exponent, true);
434    /// ```
435    pub const fn disable_exponent(mut self) -> Self {
436        self.config.disable_exponent = true;
437        self
438    }
439
440    /// Disable parsing a fraction (Default: `false`)
441    ///
442    /// See also the documentation of [`Config::disable_fraction`]
443    ///
444    /// # Examples
445    ///
446    /// ```rust
447    /// use fundu_core::config::{Config, ConfigBuilder};
448    ///
449    /// const CONFIG: Config = ConfigBuilder::new().disable_fraction().build();
450    ///
451    /// assert_eq!(CONFIG.disable_fraction, true);
452    /// ```
453    pub const fn disable_fraction(mut self) -> Self {
454        self.config.disable_fraction = true;
455        self
456    }
457
458    /// Disable parsing infinity (Default: `false`)
459    ///
460    /// See also the documentation of [`Config::disable_infinity`]
461    ///
462    /// # Examples
463    ///
464    /// ```rust
465    /// use fundu_core::config::{Config, ConfigBuilder};
466    ///
467    /// const CONFIG: Config = ConfigBuilder::new().disable_infinity().build();
468    ///
469    /// assert_eq!(CONFIG.disable_infinity, true);
470    /// ```
471    pub const fn disable_infinity(mut self) -> Self {
472        self.config.disable_infinity = true;
473        self
474    }
475
476    /// Make a number in the input string optional (Default: `false`)
477    ///
478    /// See also the documentation of [`Config::number_is_optional`]
479    ///
480    /// # Examples
481    ///
482    /// ```rust
483    /// use fundu_core::config::{Config, ConfigBuilder};
484    ///
485    /// const CONFIG: Config = ConfigBuilder::new().number_is_optional().build();
486    ///
487    /// assert_eq!(CONFIG.number_is_optional, true);
488    /// ```
489    pub const fn number_is_optional(mut self) -> Self {
490        self.config.number_is_optional = true;
491        self
492    }
493
494    /// Allow parsing negative durations (Default: `false`)
495    ///
496    /// See also the documentation of [`Config::allow_negative`]
497    ///
498    /// # Examples
499    ///
500    /// ```rust
501    /// use fundu_core::config::{Config, ConfigBuilder};
502    ///
503    /// const CONFIG: Config = ConfigBuilder::new().allow_negative().build();
504    ///
505    /// assert_eq!(CONFIG.allow_negative, true);
506    /// ```
507    pub const fn allow_negative(mut self) -> Self {
508        self.config.allow_negative = true;
509        self
510    }
511
512    /// This setting allows multiple `durations` in the source string (Default: `None`)
513    ///
514    /// See also the documentation of [`Config::allow_multiple`] and
515    /// [`Config::conjunctions`].
516    ///
517    /// # Examples
518    ///
519    /// ```rust
520    /// use fundu_core::config::{Config, ConfigBuilder, Delimiter};
521    ///
522    /// const CONJUNCTIONS: &[&str] = &["and", ",", "also"];
523    /// const CONFIG: Config = ConfigBuilder::new()
524    ///     .parse_multiple(Some(CONJUNCTIONS))
525    ///     .build();
526    ///
527    /// assert_eq!(CONFIG.conjunctions, Some(CONJUNCTIONS));
528    /// ```
529    pub const fn parse_multiple(mut self, conjunctions: Option<&'a [&'a str]>) -> Self {
530        self.config.allow_multiple = true;
531        self.config.conjunctions = conjunctions;
532        self
533    }
534
535    /// Allow the ago keyword delimited by a [`Delimiter`] to indicate a negative duration
536    /// (Default: `None`)
537    ///
538    /// See also the documentation of [`Config::allow_ago`]. Setting `allow_ago` with this
539    /// method also enables parsing negative durations like with [`ConfigBuilder::allow_negative`]
540    ///
541    /// # Examples
542    ///
543    /// ```rust
544    /// use fundu_core::config::{Config, ConfigBuilder};
545    ///
546    /// const CONFIG: Config = ConfigBuilder::new().allow_ago().build();
547    ///
548    /// assert!(CONFIG.allow_ago);
549    /// assert!(CONFIG.allow_negative);
550    /// ```
551    pub const fn allow_ago(mut self) -> Self {
552        self.config.allow_ago = true;
553        self.config.allow_negative = true;
554        self
555    }
556
557    /// Allow a [`Delimiter`] between the sign and a number, time keyword ... (Default: `None`)
558    ///
559    /// See also the documentation of [`Config::allow_sign_delimiter`]
560    ///
561    /// # Examples
562    ///
563    /// ```rust
564    /// use fundu_core::config::{Config, ConfigBuilder};
565    ///
566    /// const CONFIG: Config = ConfigBuilder::new().allow_sign_delimiter().build();
567    ///
568    /// assert!(CONFIG.allow_sign_delimiter);
569    /// ```
570    pub const fn allow_sign_delimiter(mut self) -> Self {
571        self.config.allow_sign_delimiter = true;
572        self
573    }
574
575    /// Set the inner [`Delimiter`] to something different then the default
576    /// [`u8::is_ascii_whitespace`]
577    ///
578    /// Where the inner delimiter occurs, depends on other options:
579    /// * [`ConfigBuilder::allow_sign_delimiter`]: Between the sign and the number
580    /// * [`ConfigBuilder::allow_time_unit_delimiter`]: Between the number and the time unit
581    /// * [`ConfigBuilder::allow_ago`]: Between the time unit and the `ago` keyword
582    /// * If [`NumbersLike`] numerals are used, between the numeral and the time unit
583    ///
584    /// See also the documentation of [`Config::inner_delimiter`]
585    ///
586    /// # Examples
587    ///
588    /// ```rust
589    /// use fundu_core::config::{Config, ConfigBuilder};
590    ///
591    /// const CONFIG: Config = ConfigBuilder::new()
592    ///     .inner_delimiter(|byte| byte == b'#')
593    ///     .build();
594    ///
595    /// assert!((CONFIG.inner_delimiter)(b'#'));
596    /// ```
597    pub const fn inner_delimiter(mut self, delimiter: Delimiter) -> Self {
598        self.config.inner_delimiter = delimiter;
599        self
600    }
601
602    /// Set the outer [`Delimiter`] to something different then the default
603    /// [`u8::is_ascii_whitespace`]
604    ///
605    /// The outer delimiter is used to separate multiple durations like in `1second 1minute` and is
606    /// therefore used only if [`Config::allow_multiple`] is set to true. If
607    /// `conjunctions` are set, this delimiter also separates the conjunction from the durations
608    /// (like in `1second and 1minute`)
609    ///
610    /// See also the documentation of [`Config::outer_delimiter`]
611    ///
612    /// # Examples
613    ///
614    /// ```rust
615    /// use fundu_core::config::{Config, ConfigBuilder};
616    ///
617    /// const CONFIG: Config = ConfigBuilder::new()
618    ///     .outer_delimiter(|byte| byte == b'#')
619    ///     .build();
620    ///
621    /// assert!((CONFIG.outer_delimiter)(b'#'));
622    /// ```
623    pub const fn outer_delimiter(mut self, delimiter: Delimiter) -> Self {
624        self.config.outer_delimiter = delimiter;
625        self
626    }
627}
628
629#[cfg(test)]
630mod tests {
631    use rstest::{fixture, rstest};
632
633    use super::*;
634
635    #[fixture]
636    pub fn test_time_unit() -> TimeUnit {
637        TimeUnit::MilliSecond
638    }
639
640    #[test]
641    #[cfg_attr(miri, ignore)]
642    fn test_default_for_config() {
643        assert_eq!(Config::default(), Config::new());
644    }
645
646    #[test]
647    #[cfg_attr(miri, ignore)]
648    fn test_default_for_config_builder() {
649        assert_eq!(ConfigBuilder::new().build(), Config::new());
650    }
651
652    #[test]
653    #[cfg_attr(miri, ignore)]
654    fn test_config_method_builder() {
655        assert_eq!(Config::builder().build(), Config::new());
656    }
657
658    #[test]
659    #[cfg_attr(miri, ignore)]
660    fn test_config_builder_allow_delimiter() {
661        let config = ConfigBuilder::new().allow_time_unit_delimiter().build();
662
663        let mut expected = Config::new();
664        expected.allow_time_unit_delimiter = true;
665
666        assert_eq!(config, expected);
667    }
668
669    #[rstest]
670    #[cfg_attr(miri, ignore)]
671    fn test_config_builder_default_unit(test_time_unit: TimeUnit) {
672        let config = ConfigBuilder::new().default_unit(test_time_unit).build();
673
674        let mut expected = Config::new();
675        expected.default_unit = test_time_unit;
676
677        assert_eq!(config, expected);
678    }
679
680    #[test]
681    #[cfg_attr(miri, ignore)]
682    fn test_config_builder_disable_exponent() {
683        let config = ConfigBuilder::new().disable_exponent().build();
684
685        let mut expected = Config::new();
686        expected.disable_exponent = true;
687
688        assert_eq!(config, expected);
689    }
690
691    #[test]
692    #[cfg_attr(miri, ignore)]
693    fn test_config_builder_disable_fraction() {
694        let config = ConfigBuilder::new().disable_fraction().build();
695
696        let mut expected = Config::new();
697        expected.disable_fraction = true;
698
699        assert_eq!(config, expected);
700    }
701
702    #[test]
703    #[cfg_attr(miri, ignore)]
704    fn test_config_builder_number_is_optional() {
705        let config = ConfigBuilder::new().number_is_optional().build();
706
707        let mut expected = Config::new();
708        expected.number_is_optional = true;
709
710        assert_eq!(config, expected);
711    }
712
713    #[test]
714    #[cfg_attr(miri, ignore)]
715    fn test_config_builder_disable_infinity() {
716        let config = ConfigBuilder::new().disable_infinity().build();
717
718        let mut expected = Config::new();
719        expected.disable_infinity = true;
720
721        assert_eq!(config, expected);
722    }
723
724    #[test]
725    #[cfg_attr(miri, ignore)]
726    fn test_config_builder_allow_negative() {
727        let config = ConfigBuilder::new().allow_negative().build();
728
729        let mut expected = Config::new();
730        expected.allow_negative = true;
731
732        assert_eq!(config, expected);
733    }
734
735    #[test]
736    #[cfg_attr(miri, ignore)]
737    fn test_config_builder_parse_multiple_when_no_conjunctions() {
738        let config = ConfigBuilder::new().parse_multiple(None).build();
739
740        let mut expected = Config::new();
741        expected.allow_multiple = true;
742        expected.conjunctions = None;
743
744        assert_eq!(config, expected);
745    }
746
747    #[test]
748    #[cfg_attr(miri, ignore)]
749    fn test_config_builder_parse_multiple_when_conjunctions() {
750        let conjunctions = &["and", ","];
751        let config = ConfigBuilder::new()
752            .parse_multiple(Some(conjunctions))
753            .build();
754
755        let mut expected = Config::new();
756        expected.allow_multiple = true;
757        expected.conjunctions = Some(conjunctions);
758
759        assert_eq!(config, expected);
760    }
761
762    #[test]
763    #[cfg_attr(miri, ignore)]
764    fn test_config_builder_allow_ago() {
765        let config = ConfigBuilder::new().allow_ago().build();
766
767        let mut expected = Config::new();
768        expected.allow_ago = true;
769        expected.allow_negative = true;
770
771        assert_eq!(config, expected);
772    }
773
774    #[test]
775    #[cfg_attr(miri, ignore)]
776    fn test_config_builder_allow_sign_delimiter() {
777        let config = ConfigBuilder::new().allow_sign_delimiter().build();
778
779        let mut expected = Config::new();
780        expected.allow_sign_delimiter = true;
781
782        assert_eq!(config, expected);
783    }
784}