1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Try to load the file with initial positions and velocities

use csv::{ByteRecord, ReaderBuilder};
use serde::Deserialize;

use crate::orbit::{HCInitials, Orbit, F};

/// A representation of the results after parsing the files
pub struct Log<'a> {
    /// Number of files parsed
    len: usize,
    /// Paths to the parsed files (as specified by a user)
    files: Vec<&'a str>,
    /// A vector of status codes for each file
    statuses: Vec<String>,
}

/// A representation of a file record
#[derive(Deserialize)]
struct Record<'a> {
    /// ID of the object
    id: &'a str,
    /// X component of the radius vector $\[ \text{kpc} \]$
    x_0: F,
    /// Standard deviation of the X component of the radius vector $\[ \text{kpc} \]$
    x_0_err: F,
    /// Y component of the radius vector $\[ \text{kpc} \]$
    y_0: F,
    /// Standard deviation of the Y component of the radius vector $\[ \text{kpc} \]$
    y_0_err: F,
    /// Z component of the radius vector $\[ \text{kpc} \]$
    z_0: F,
    /// Standard deviation of the Z component of the radius vector $\[ \text{kpc} \]$
    z_0_err: F,
    /// U component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    u_0: F,
    /// Standard deviation of the U component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    u_0_err: F,
    /// V component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    v_0: F,
    /// Standard deviation of the V component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    v_0_err: F,
    /// W component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    w_0: F,
    /// Standard deviation of the W component of the velocity vector $\[ \text{km} \\, \text{s}^{-1} \]$
    w_0_err: F,
}

impl Orbit {
    /// Parse the file(s) with initial positions and velocities
    ///
    /// Return all of the proper orbits found in the file(s) and
    /// the log of status codes.
    #[must_use]
    pub fn load(files: Vec<&str>) -> (Vec<Orbit>, Log) {
        // Get the length of files
        let len = files.len();

        // Prepare an array of orbits
        let mut orbits = Vec::<Orbit>::new();
        // Prepare a mask of status codes
        let mut statuses = Vec::<String>::with_capacity(len);
        // Prepare two byte records for headers and data
        let mut headers = ByteRecord::new();
        let mut record = ByteRecord::new();
        // Prepare a counter of records that were successfully deserialized and parsed
        let mut counter;

        // For each file in the files
        for file in &files {
            // If it's possible to build a CSV reader off the file
            if let Ok(mut rdr) = ReaderBuilder::new().delimiter(b' ').from_path(file) {
                // Parse the first row of the file (this operation borrows mutably)
                if let Ok(hs) = rdr.byte_headers() {
                    // Clone the headers, end the mutable borrow
                    headers = hs.clone();
                };
                // If the headers are empty
                if headers.is_empty() {
                    // Put the appropriate status and skip this file
                    statuses.push("PF".to_string());
                // Or, if the header isn't correct
                } else if headers.as_slice()
                    != &b"idx_0x_0_erry_0y_0_errz_0z_0_erru_0u_0_errv_0v_0_errw_0w_0_err"[..]
                    || headers.len() != 13
                {
                    // Put the appropriate status and skip this file
                    statuses.push("WH".to_string());
                // Otherwise,
                } else {
                    // Reset the count of records that were successfully deserialized and parsed
                    counter = 0;
                    // While there are records that could be read
                    loop {
                        // Try to read a record
                        if let Ok(read) = rdr.read_byte_record(&mut record) {
                            // If a record was read successfully
                            if read {
                                // If the record could be deserialized, process
                                // the results; otherwise, skip this line
                                if let Ok(record) = record.deserialize::<Record>(Some(&headers)) {
                                    // Use the data of the record to create and save an orbit
                                    orbits.push(Orbit::from(
                                        record.id.to_string(),
                                        HCInitials {
                                            x: record.x_0,
                                            x_err: record.x_0_err,
                                            y: record.y_0,
                                            y_err: record.y_0_err,
                                            z: record.z_0,
                                            z_err: record.z_0_err,
                                            u: record.u_0,
                                            u_err: record.u_0_err,
                                            v: record.v_0,
                                            v_err: record.v_0_err,
                                            w: record.w_0,
                                            w_err: record.w_0_err,
                                        },
                                    ));
                                    counter += 1;
                                }
                            // Otherwise, break since this is the end of file
                            } else {
                                break;
                            }
                        }
                    }
                    // Put the appropriate status and finish with this file
                    statuses.push(counter.to_string());
                }
            // Otherwise,
            } else {
                // Put the appropriate status and skip this file
                statuses.push("RF".to_string());
            }
        }

        (
            orbits,
            Log {
                len,
                files,
                statuses,
            },
        )
    }
}

impl<'a> Log<'a> {
    /// Format the contents of the log with the specified padding, return a string
    pub fn format(&self, padding: usize) -> String {
        // Prepare strings for the output and the status codes
        let mut s = String::new();
        // For each file
        for i in 0..self.len {
            // Append a string with the stringified status code and the file path
            s.push_str(&format!(
                "{:1$}({2}) {3:?}\n",
                "", padding, &self.statuses[i], self.files[i]
            ));
        }
        s
    }
}