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}