textgridde_rs/
textgrid.rs

1use std::{
2    fmt::{self, Debug, Display, Formatter},
3    fs::{self, File},
4    io::{Read, Result, Write},
5    path::PathBuf,
6};
7
8use derive_more::Constructor;
9use getset::{Getters, MutGetters, Setters};
10
11use crate::{interval::Tier as IntervalTier, parse_textgrid, point::Tier as PointTier};
12
13/// Represents a tier in a `TextGrid`.
14#[derive(Clone, Debug)]
15pub enum Tier {
16    IntervalTier(IntervalTier),
17    PointTier(PointTier),
18}
19
20#[allow(dead_code)]
21impl Tier {
22    /// Unwraps a `Tier` into an `IntervalTier`.
23    ///
24    /// # Returns
25    ///
26    /// Returns the `IntervalTier` if the `Tier` is an `IntervalTier`, otherwise None.
27    const fn get_interval_tier(&self) -> Option<&IntervalTier> {
28        match self {
29            Self::IntervalTier(interval_tier) => Some(interval_tier),
30            Self::PointTier(_) => None,
31        }
32    }
33
34    /// Unwraps a `Tier` into a `PointTier`.
35    ///
36    /// # Returns
37    ///
38    /// Returns the `PointTier` if the `Tier` is a `PointTier`, otherwise None.
39    const fn get_point_tier(&self) -> Option<&PointTier> {
40        match self {
41            Self::PointTier(point_tier) => Some(point_tier),
42            Self::IntervalTier(_) => None,
43        }
44    }
45}
46
47impl Default for Tier {
48    fn default() -> Self {
49        Self::IntervalTier(IntervalTier::default())
50    }
51}
52
53impl Display for Tier {
54    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
55        match self {
56            Self::IntervalTier(interval_tier) => write!(f, "{interval_tier}"),
57            Self::PointTier(point_tier) => write!(f, "{point_tier}"),
58        }
59    }
60}
61
62/// Represents the output format for writing the `TextGrid` to a file.
63#[derive(Copy, Clone)]
64pub enum OutputFormat {
65    Long,
66    Short,
67}
68
69#[derive(Clone, Constructor, Debug, Default, Getters, MutGetters, Setters)]
70/// Represents a `TextGrid`, which is a data structure used in the linguistic research program Praat
71/// to annotate speech data. It can support either
72pub struct TextGrid {
73    #[getset(get = "pub")]
74    xmin: f64,
75    #[getset(get = "pub")]
76    xmax: f64,
77    #[getset(get = "pub", get_mut = "pub")]
78    tiers: Vec<Tier>,
79    #[getset(get = "pub", set = "pub")]
80    name: String,
81}
82
83impl TextGrid {
84    /// Returns the number of tiers in the `TextGrid`.
85    #[must_use]
86    pub fn get_size(&self) -> usize {
87        self.tiers.len()
88    }
89
90    /// Sets the xmin time value of the whole `TextGrid` in seconds.
91    ///
92    /// # Arguments
93    ///
94    /// * `xmin` - The new xmin value.
95    /// * `warn` - If Some(true), displays a warning if any tier has an xmin lesser than `xmin`.
96    pub fn set_xmin<W: Into<Option<bool>>>(&mut self, xmin: f64, warn: W) {
97        if xmin > self.xmax {
98            if warn.into().unwrap_or_default() {
99                eprintln!("Warning: xmin cannot be greater than xmax. Setting to xmax.");
100            }
101            self.xmin = self.xmax;
102            return;
103        } else if xmin < 0.0 {
104            if warn.into().unwrap_or_default() {
105                eprintln!("Warning: xmin cannot be less than 0.0. Setting to 0.0.");
106            }
107            self.xmin = 0.0;
108            return;
109        }
110
111        if warn.into().unwrap_or_default() {
112            for tier in &self.tiers {
113                match tier {
114                    Tier::IntervalTier(interval_tier) => {
115                        if *interval_tier.xmin() < xmin {
116                            eprintln!(
117                                "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
118                                interval_tier.name(), interval_tier.xmin(), xmin
119                            );
120                        }
121                    }
122                    Tier::PointTier(point_tier) => {
123                        if *point_tier.xmin() < xmin {
124                            eprintln!(
125                                "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
126                                point_tier.name(), point_tier.xmin(), xmin
127                            );
128                        }
129                    }
130                }
131            }
132        }
133
134        self.xmin = xmin;
135    }
136
137    /// Sets the xmax time value of the whole `TextGrid` in seconds.
138    ///
139    /// # Arguments
140    ///
141    /// * `xmax` - The new xmax value.
142    /// * `warn` - If Some(true), displays a warning if any tier has an xmax greater than `xmax`.
143    pub fn set_xmax<W: Into<Option<bool>>>(&mut self, xmax: f64, warn: W) {
144        if xmax < self.xmin {
145            if warn.into().unwrap_or_default() {
146                eprintln!("Warning: xmax cannot be less than xmin. Setting to xmin.");
147            }
148            self.xmax = self.xmin;
149            return;
150        } else if xmax < 0.0 {
151            if warn.into().unwrap_or_default() {
152                eprintln!("Warning: xmax cannot be less than 0.0. Setting to 0.0.");
153            }
154            self.xmax = 0.0;
155            return;
156        }
157
158        if warn.into().unwrap_or_default() {
159            for tier in &self.tiers {
160                match tier {
161                    Tier::IntervalTier(interval_tier) => {
162                        if *interval_tier.xmax() > xmax {
163                            eprintln!(
164                                "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
165                                interval_tier.name(), interval_tier.xmax(), xmax
166                            );
167                        }
168                    }
169                    Tier::PointTier(point_tier) => {
170                        if *point_tier.xmax() > xmax {
171                            eprintln!(
172                                "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
173                                point_tier.name(), point_tier.xmax(), xmax
174                            );
175                        }
176                    }
177                }
178            }
179        }
180
181        self.xmax = xmax;
182    }
183
184    /// Pushes a new, user-made tier to the `TextGrid`.
185    ///
186    /// # Arguments
187    ///
188    /// * `tier` - The tier to be added.
189    /// * `warn` - If Some(true), displays a warning if the tier has a minimum or maximum point
190    ///            that is outside the range of the `TextGrid`.
191    pub fn push_tier<W: Into<Option<bool>> + Copy>(&mut self, mut tier: Tier, warn: W) {
192        let name = match &tier {
193            Tier::IntervalTier(interval_tier) => interval_tier.name(),
194            Tier::PointTier(point_tier) => point_tier.name(),
195        };
196
197        let mut increment = 0;
198        let mut new_name = name.to_string();
199        while self.get_tier(&new_name).is_some() {
200            increment += 1;
201            new_name = format!("{name}{increment}");
202        }
203        if increment > 0 && warn.into().unwrap_or_default() {
204            eprintln!("Warning: Tier name `{name}` already exists. Renaming to `{new_name}`");
205        }
206
207        if warn.into().unwrap_or_default() {
208            match tier {
209                Tier::IntervalTier(ref mut interval_tier) => {
210                    interval_tier.set_name(new_name);
211
212                    if *interval_tier.xmin() < self.xmin {
213                        eprintln!(
214                            "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
215                            interval_tier.name(), interval_tier.xmin(), self.xmin
216                        );
217                    }
218                    if *interval_tier.xmax() > self.xmax {
219                        eprintln!(
220                            "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
221                            interval_tier.name(), interval_tier.xmax(), self.xmax
222                        );
223                    }
224                }
225                Tier::PointTier(ref mut point_tier) => {
226                    point_tier.set_name(new_name);
227
228                    if *point_tier.xmin() < self.xmin {
229                        eprintln!(
230                            "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
231                            point_tier.name(), point_tier.xmin(), self.xmin
232                        );
233                    }
234                    if *point_tier.xmax() > self.xmax {
235                        eprintln!(
236                            "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
237                            point_tier.name(), point_tier.xmax(), self.xmax
238                        );
239                    }
240                }
241            }
242        }
243
244        self.tiers.push(tier);
245    }
246
247    /// Gets a tier using it's name.
248    ///
249    /// # Arguments
250    ///
251    /// * `name` - The name of the tier to get.
252    ///
253    /// # Returns
254    ///
255    /// Returns the tier if it exists, otherwise None.
256    #[must_use]
257    pub fn get_tier(&self, name: &str) -> Option<&Tier> {
258        self.tiers.iter().find(|tier| match tier {
259            Tier::IntervalTier(interval_tier) => interval_tier.name() == name,
260            Tier::PointTier(point_tier) => point_tier.name() == name,
261        })
262    }
263
264    /// Deletes a tier using it's name.
265    ///
266    /// # Arguments
267    ///
268    /// * `name` - The name of the tier to delete.
269    /// * `warn` - If true, displays a warning if the tier does not exist.
270    pub fn delete_tier<W: Into<Option<bool>> + Copy>(&mut self, name: &str, warn: W) {
271        let index = self.tiers.iter().position(|tier| match tier {
272            Tier::IntervalTier(interval_tier) => interval_tier.name() == name,
273            Tier::PointTier(point_tier) => point_tier.name() == name,
274        });
275
276        if let Some(index) = index {
277            self.tiers.remove(index);
278        } else if warn.into().unwrap_or_default() {
279            eprintln!("Warning: Tier `{name}` does not exist therefore cannot be deleted.");
280        }
281    }
282
283    /// Writes the `TextGrid` to a file or folder in the specified format.
284    ///
285    /// If given a folder path, the `TextGrid` will be written to a file in the folder with the same name as the `TextGrid`'s name field.
286    ///
287    /// Long `TextGrid`s are the typical format, while short
288    /// `TextGrid`s are readable by Praat and do not include
289    /// extraneous data.
290    ///
291    /// # Arguments
292    ///
293    /// * `path` - The path to the file.
294    /// * `format` - The output format.
295    ///
296    /// # Errors
297    ///
298    /// Returns an error if there was a problem creating or writing to the file.
299    pub fn write(&self, path: PathBuf, format: OutputFormat) -> Result<()> {
300        let mut file = if path.extension().is_none() || path.is_dir() {
301            fs::create_dir_all(path.clone())?;
302
303            let mut path = path;
304            path.push(format!("{}.TextGrid", self.name));
305
306            File::create(path)?
307        } else {
308            let mut parent_path = path.clone();
309
310            if parent_path.pop() {
311                fs::create_dir_all(parent_path)?;
312            };
313
314            File::create(path)?
315        };
316
317        let textgrid_data = match format {
318            OutputFormat::Long => self.format_as_long(),
319            OutputFormat::Short => self.format_as_short(),
320        };
321
322        file.write_all(textgrid_data.join("\n").as_bytes())?;
323
324        Ok(())
325    }
326
327    /// Outputs a String vector containing the `TextGrid` to a file in the long format.
328    ///
329    /// # Returns
330    ///
331    /// A vector of strings containing the `TextGrid` in the long format.
332    fn format_as_long(&self) -> Vec<String> {
333        let mut out_strings: Vec<String> = vec![
334            "File type = \"ooTextFile\"".into(),
335            "Object class = \"TextGrid\"".into(),
336            String::new(),
337            format!("xmin = {}", self.xmin),
338            format!("xmax = {}", self.xmax),
339            "tiers? <exists>".into(),
340            format!("size = {}", self.tiers.len()),
341            "item []:".into(),
342        ];
343
344        for (tier_index, tier) in self.tiers.iter().enumerate() {
345            match tier {
346                Tier::IntervalTier(interval_tier) => {
347                    out_strings.push(format!("\titem [{}]:", tier_index + 1));
348                    out_strings.push("\t\tclass = \"IntervalTier\"".into());
349                    out_strings.push(format!("\t\tname = \"{}\"", interval_tier.name()));
350                    out_strings.push(format!("\t\txmin = {}", interval_tier.xmin()));
351                    out_strings.push(format!("\t\txmax = {}", interval_tier.xmax()));
352                    out_strings.push(format!(
353                        "\t\tintervals: size = {}",
354                        interval_tier.get_size()
355                    ));
356
357                    for (interval_index, interval) in interval_tier.intervals().iter().enumerate() {
358                        out_strings.push(format!("\t\tintervals [{}]:", interval_index + 1));
359                        out_strings.push(format!("\t\t\txmin = {}", interval.xmin()));
360                        out_strings.push(format!("\t\t\txmax = {}", interval.xmax()));
361                        out_strings.push(format!("\t\t\ttext = \"{}\"", interval.text()));
362                    }
363                }
364                Tier::PointTier(point_tier) => {
365                    out_strings.push(format!("\titem [{}]:", tier_index + 1));
366                    out_strings.push("\t\tclass = \"TextTier\"".into());
367                    out_strings.push(format!("\t\tname = \"{}\"", point_tier.name()));
368                    out_strings.push(format!("\t\txmin = {}", point_tier.xmin()));
369                    out_strings.push(format!("\t\txmax = {}", point_tier.xmax()));
370                    out_strings.push(format!("\t\tpoints: size = {}", point_tier.get_size()));
371
372                    for (point_index, point) in point_tier.points().iter().enumerate() {
373                        out_strings.push(format!("\t\tpoints [{}]:", point_index + 1));
374                        out_strings.push(format!("\t\t\tnumber = {}", point.number()));
375                        out_strings.push(format!("\t\t\tmark = \"{}\"", point.mark()));
376                    }
377                }
378            }
379        }
380
381        out_strings
382    }
383
384    /// Outputs a String vector containing the `TextGrid` to a file in the short format.
385    ///
386    /// # Returns
387    ///
388    /// A vector of strings containing the `TextGrid` in the short format.
389    fn format_as_short(&self) -> Vec<String> {
390        let mut out_strings: Vec<String> = vec![
391            "\"ooTextFile\"".into(),
392            "\"TextGrid\"".into(),
393            String::new(),
394            self.xmin.to_string(),
395            self.xmax.to_string(),
396            "<exists>".into(),
397            self.tiers.len().to_string(),
398        ];
399
400        for tier in &self.tiers {
401            match tier {
402                Tier::IntervalTier(interval_tier) => {
403                    out_strings.push("\"IntervalTier\"".into());
404                    out_strings.push(format!("\"{}\"", interval_tier.name()));
405                    out_strings.push(interval_tier.xmin().to_string());
406                    out_strings.push(interval_tier.xmax().to_string());
407                    out_strings.push(interval_tier.get_size().to_string());
408
409                    for interval in interval_tier.intervals() {
410                        out_strings.push(interval.xmin().to_string());
411                        out_strings.push(interval.xmax().to_string());
412                        out_strings.push(format!("\"{}\"", interval.text()));
413                    }
414                }
415                Tier::PointTier(point_tier) => {
416                    out_strings.push("\"TextTier\"".into());
417                    out_strings.push(format!("\"{}\"", point_tier.name()));
418                    out_strings.push(point_tier.xmin().to_string());
419                    out_strings.push(point_tier.xmax().to_string());
420                    out_strings.push(point_tier.get_size().to_string());
421
422                    for point in point_tier.points() {
423                        out_strings.push(point.number().to_string());
424                        out_strings.push(format!("\"{}\"", point.mark()));
425                    }
426                }
427            }
428        }
429
430        out_strings
431    }
432
433    /// Checks the `TextGrid` for overlapping intervals or duplicate points.
434    ///
435    /// # Returns
436    ///
437    /// Returns Some([`tier_name`, (`index1`, `index2`)]) if an overlapping interval or point is found, otherwise None.
438    #[must_use]
439    pub fn check_overlaps(&self) -> Option<Vec<(String, (u64, u64))>> {
440        let mut overlaps: Vec<(String, (u64, u64))> = Vec::new();
441
442        for tier in &self.tiers {
443            match tier {
444                Tier::IntervalTier(interval_tier) => {
445                    if let Some(interval_overlaps) = interval_tier.check_overlaps() {
446                        overlaps.append(
447                            &mut interval_overlaps
448                                .into_iter()
449                                .map(|overlap| (interval_tier.name().into(), overlap))
450                                .collect(),
451                        );
452                    }
453                }
454                Tier::PointTier(point_tier) => {
455                    if let Some(point_overlaps) = point_tier.check_overlaps() {
456                        overlaps.append(
457                            &mut point_overlaps
458                                .into_iter()
459                                .map(|overlap| (point_tier.name().into(), overlap))
460                                .collect(),
461                        );
462                    }
463                }
464            }
465        }
466
467        if overlaps.is_empty() {
468            None
469        } else {
470            Some(overlaps)
471        }
472    }
473
474    /// Calls `fix_overlaps` on all `IntervalTier`s in the `TextGrid`.
475    ///
476    /// # Arguments
477    ///
478    /// * `prefer_first` - If true, the first interval's `xmax` will be raised or lowered to the
479    ///                     new interval's `xmin` in the case of a gap or overlap. If false, the
480    ///                     second interval's `xmin` will be raised or lowered to the first interval's.
481    pub fn fix_boundaries<P: Into<Option<bool>> + Copy>(&mut self, prefer_first: P) {
482        for tier in &mut self.tiers {
483            match tier {
484                Tier::IntervalTier(interval_tier) => {
485                    interval_tier.fix_boundaries(prefer_first);
486                }
487                Tier::PointTier(_) => {}
488            }
489        }
490    }
491
492    /// Calls `fill_gaps` on all interval tiers in the `TextGrid`.
493    ///
494    /// # Arguments
495    ///
496    /// * `text` - The text to fill the gaps with.
497    pub fn fill_gaps(&mut self, text: &str) {
498        for tier in &mut self.tiers {
499            match tier {
500                Tier::IntervalTier(interval_tier) => {
501                    interval_tier.fill_gaps(text);
502                }
503                Tier::PointTier(_) => {}
504            }
505        }
506    }
507}
508
509impl Display for TextGrid {
510    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
511        write!(
512            f,
513            "TextGrid {}:
514                xmin = {}
515                xmax = {}
516                tier count = {}",
517            self.name,
518            self.xmin,
519            self.xmax,
520            self.tiers.len(),
521        )
522    }
523}
524
525/// `TextGrid::try_from` implementation for `PathBuf`.
526impl TryFrom<PathBuf> for TextGrid {
527    type Error = std::io::Error;
528
529    fn try_from(path: PathBuf) -> Result<Self> {
530        parse_textgrid(path, None)
531    }
532}
533
534/// `TextGrid::try_from` implementation for `&str`.
535impl TryFrom<&str> for TextGrid {
536    type Error = std::io::Error;
537
538    fn try_from(textgrid: &str) -> Result<Self> {
539        parse_textgrid(textgrid, None)
540    }
541}
542
543/// `TextGrid::try_from` implementation for `String`.
544impl TryFrom<String> for TextGrid {
545    type Error = std::io::Error;
546
547    fn try_from(textgrid: String) -> Result<Self> {
548        parse_textgrid(textgrid, None)
549    }
550}
551
552impl TryFrom<Vec<String>> for TextGrid {
553    type Error = std::io::Error;
554
555    fn try_from(textgrid: Vec<String>) -> Result<Self> {
556        parse_textgrid(textgrid, None)
557    }
558}
559
560impl TryFrom<Box<dyn Read>> for TextGrid {
561    type Error = std::io::Error;
562
563    fn try_from(textgrid: Box<dyn Read>) -> Result<Self> {
564        parse_textgrid(textgrid, None)
565    }
566}
567
568impl TryFrom<File> for TextGrid {
569    type Error = std::io::Error;
570
571    fn try_from(textgrid: File) -> Result<Self> {
572        parse_textgrid(textgrid, None)
573    }
574}
575
576/// Tests for the `TextGrid` struct.
577#[cfg(test)]
578#[allow(clippy::float_cmp)]
579mod test_textgrid {
580    use std::env;
581
582    use crate::get_file_content;
583    use crate::input::Source;
584    use crate::interval::{Interval, Tier as IntervalTier};
585    use crate::point::{Point, Tier as PointTier};
586    use crate::textgrid::{TextGrid, Tier};
587
588    mod set_xmin {
589        use crate::textgrid::TextGrid;
590
591        #[test]
592        fn normal() {
593            let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
594
595            textgrid.set_xmin(1.0, false);
596
597            assert_eq!(*textgrid.xmin(), 1.0);
598        }
599
600        #[test]
601        fn negative_with_warn() {
602            let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
603
604            textgrid.set_xmin(-1.0, false);
605
606            assert_eq!(*textgrid.xmin(), 0.0);
607        }
608
609        #[test]
610        fn more_than_xmax() {
611            let mut textgrid = TextGrid::new(5.0, 10.0, Vec::new(), "test".to_string());
612
613            textgrid.set_xmin(11.0, false);
614
615            assert_eq!(*textgrid.xmin(), 10.0);
616        }
617    }
618
619    mod set_xmax {
620        use crate::textgrid::TextGrid;
621
622        #[test]
623        fn normal() {
624            let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
625
626            textgrid.set_xmax(11.0, false);
627
628            assert_eq!(*textgrid.xmax(), 11.0);
629        }
630
631        #[test]
632        fn negative() {
633            let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
634
635            textgrid.set_xmax(-1.0, false);
636
637            assert_eq!(*textgrid.xmax(), 0.0);
638        }
639
640        #[test]
641        fn less_than_xmin() {
642            let mut textgrid = TextGrid::new(5.0, 10.0, Vec::new(), "test".to_string());
643
644            textgrid.set_xmax(1.0, false);
645
646            assert_eq!(*textgrid.xmax(), 5.0);
647        }
648    }
649
650    #[test]
651    fn push_tier() {
652        let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
653        let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
654        let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
655
656        textgrid.push_tier(Tier::IntervalTier(interval_tier), false);
657        textgrid.push_tier(Tier::PointTier(point_tier), false);
658
659        assert_eq!(textgrid.get_size(), 2);
660    }
661
662    #[test]
663    fn from_pathbuf() {
664        let cwd = env::current_dir();
665        assert!(cwd.is_ok());
666
667        let textgrid = TextGrid::try_from(cwd.unwrap().join("example/long.TextGrid")).unwrap();
668
669        assert_eq!(textgrid.xmax(), &2.3);
670    }
671
672    #[test]
673    fn format_as_long() {
674        let cwd = env::current_dir();
675        assert!(cwd.is_ok());
676
677        let long_out = get_file_content(
678            Source::String(
679                cwd.unwrap()
680                    .join("example/long.TextGrid")
681                    .to_str()
682                    .unwrap()
683                    .into(),
684            ),
685            None,
686        )
687        .unwrap();
688
689        let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
690
691        textgrid.push_tier(
692            Tier::IntervalTier(IntervalTier::new(
693                "John".to_string(),
694                0.0,
695                2.3,
696                vec![Interval::new(0.0, 2.3, "daisy bell".to_string())],
697            )),
698            false,
699        );
700
701        textgrid.push_tier(
702            Tier::IntervalTier(IntervalTier::new(
703                "Kelly".to_string(),
704                0.0,
705                2.3,
706                vec![Interval::new(0.0, 2.3, String::new())],
707            )),
708            false,
709        );
710
711        textgrid.push_tier(
712            Tier::PointTier(PointTier::new(
713                "Bell".to_string(),
714                0.0,
715                2.3,
716                vec![Point::new(1.0, "give me your answer do".to_string())],
717            )),
718            false,
719        );
720
721        let format = textgrid.format_as_long();
722
723        for (i, line) in long_out.0.iter().enumerate() {
724            assert_eq!(
725                line.replace('\r', "").replace(" ! comment test", ""),
726                format[i]
727            );
728        }
729    }
730
731    #[test]
732    fn format_as_short() {
733        let cwd = env::current_dir();
734        assert!(cwd.is_ok());
735
736        let short_out = get_file_content(
737            Source::String(
738                cwd.unwrap()
739                    .join("example/short.TextGrid")
740                    .to_str()
741                    .unwrap()
742                    .into(),
743            ),
744            None,
745        )
746        .unwrap();
747
748        let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
749
750        textgrid.push_tier(
751            Tier::IntervalTier(IntervalTier::new(
752                "John".to_string(),
753                0.0,
754                2.3,
755                vec![Interval::new(0.0, 2.3, "daisy bell".to_string())],
756            )),
757            false,
758        );
759
760        textgrid.push_tier(
761            Tier::IntervalTier(IntervalTier::new(
762                "Kelly".to_string(),
763                0.0,
764                2.3,
765                vec![Interval::new(0.0, 2.3, String::new())],
766            )),
767            false,
768        );
769
770        textgrid.push_tier(
771            Tier::PointTier(PointTier::new(
772                "Bell".to_string(),
773                0.0,
774                2.3,
775                vec![Point::new(1.0, "give me your answer do".to_string())],
776            )),
777            false,
778        );
779
780        let format = textgrid.format_as_short();
781
782        for (i, line) in short_out.0.iter().enumerate() {
783            assert_eq!(
784                line.replace('\r', "").replace(" ! comment test", ""),
785                format[i]
786            );
787        }
788    }
789}
790
791#[cfg(test)]
792mod test_tier {
793    use crate::interval::Tier as IntervalTier;
794    use crate::point::Tier as PointTier;
795    use crate::textgrid::Tier;
796
797    #[test]
798    fn get_interval_tier() {
799        let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
800        let tier = Tier::IntervalTier(interval_tier);
801
802        assert!(tier.get_interval_tier().is_some());
803    }
804
805    #[test]
806    fn get_point_tier() {
807        let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
808        let tier = Tier::PointTier(point_tier);
809
810        assert!(tier.get_point_tier().is_some());
811    }
812
813    #[test]
814    fn default() {
815        let tier = Tier::default();
816
817        assert!(matches!(tier, Tier::IntervalTier(_)));
818    }
819
820    #[test]
821    fn to_string() {
822        let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
823        let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
824
825        assert_eq!(
826            interval_tier.to_string(),
827            "IntervalTier test:\n                xmin:  0\n                xmax:  10\n                interval count: 0"
828        );
829        assert_eq!(
830            point_tier.to_string(),
831            "PointTier test:\n                xmin:  0\n                xmax:  10\n                point count: 0"
832        );
833    }
834}