psrutils/
timfile.rs

1//! Allows for reading the data of `.tim` files.
2
3use crate::error::{PsruError, TimContext};
4use std::{
5    fs::File,
6    io::{BufRead, BufReader},
7    path::{Path, PathBuf},
8};
9
10pub use toa::*;
11
12mod tests;
13mod toa;
14
15/// Reads a .tim file recursively. Returns errors for missing TOA values,
16/// flags without values, and malformed entries.
17///
18/// Currently, the only implemented format is for Tempo2.
19///
20/// # Errors
21/// Will throw errors for bad files or contents.
22pub fn read_tim(
23    path: &Path,
24    format: TimFormat,
25) -> Result<Vec<TOAInfo>, PsruError> {
26    let mut toa_infos = Vec::new();
27
28    let file = File::open(path)?;
29    let reader = BufReader::new(file);
30
31    let directory = path.parent().ok_or(PsruError::OrphanFile)?.to_path_buf();
32    let mut ctx = TimContext::new(&path.to_string_lossy(), 0);
33
34    for (line_number, result) in reader.lines().enumerate() {
35        let line = result?;
36        if line.is_empty() {
37            continue;
38        }
39
40        ctx.line(line_number + 1);
41
42        parse_line(format, directory.clone(), &mut toa_infos, &line)
43            .map_err(|err| err.set_tim_ctx(&ctx))?;
44    }
45
46    Ok(toa_infos)
47}
48
49fn parse_line(
50    mode: TimFormat,
51    mut directory: PathBuf,
52    toa_infos: &mut Vec<TOAInfo>,
53    line: &str,
54) -> Result<(), PsruError> {
55    let parts = line.split_whitespace().collect::<Vec<_>>();
56
57    if parts[0] == "INCLUDE" {
58        directory.push(parts[1]);
59        let mut nested_tim = read_tim(&directory, mode)?;
60
61        toa_infos.append(&mut nested_tim);
62        return Ok(());
63    }
64
65    if parts[0] == "FORMAT" && parts[1] == "1" {
66        if mode != TimFormat::Tempo2 {
67            return Err(PsruError::TimFormatDiscrepancy(
68                None,
69                String::from("Tempo2"),
70            ));
71        }
72        return Ok(());
73    }
74
75    if parts[0] == "MODE" && parts[1] == "1" {
76        // I don't know what this means
77        return Ok(());
78    }
79
80    let toa_info = match mode {
81        TimFormat::Tempo2 => TOAInfo::parse_tempo2(&parts)?,
82        TimFormat::Parkes => TOAInfo::parse_parkes(line)?,
83    };
84
85    toa_infos.push(toa_info);
86
87    Ok(())
88}
89
90#[derive(Debug, PartialEq, Eq, Clone, Copy)]
91/// The format used for parsing TOAs in .tim files.
92pub enum TimFormat {
93    /// Read `.tim` files the way Tempo2 likes it.
94    Tempo2,
95    /// Not implemented.
96    Parkes,
97}