1use 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
15pub 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 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)]
91pub enum TimFormat {
93 Tempo2,
95 Parkes,
97}