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
#[cfg(feature = "pylib")]
use pyo3::prelude::*;
use std::fmt::Display;

mod station_num;
pub use station_num::StationNumber;

mod state_prov;
pub use state_prov::StateProv;

/// Description of a site with a sounding.
#[cfg_attr(feature = "pylib", pyclass(module = "bufkit_data"))]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SiteInfo {
    /// Station number, this should be unique to the site. Site ids sometimes change around.
    pub station_num: StationNumber,
    /// A longer, more human readable name.
    pub name: Option<String>,
    /// Any relevant notes about the site.
    pub notes: Option<String>,
    /// The state or providence where this location is located. This allows querying sites by what
    /// state or providence they are in.
    pub state: Option<StateProv>,
    /// Time zone information
    pub time_zone: Option<chrono::FixedOffset>,
}

impl SiteInfo {
    /// Return true if there is any missing data. It ignores the notes field since this is only
    /// rarely used. Also, there is no requirement for a site to have an id.
    pub fn incomplete(&self) -> bool {
        self.name.is_none()
            || self.state.is_none()
            || self.time_zone.is_none()
            || !self.station_num.is_valid()
    }

    /// Get description of the site without all the meta-data details.
    pub fn description(&self) -> String {
        let mut desc = String::new();

        if let Some(ref nm) = self.name {
            desc += nm;
            if let Some(st) = self.state {
                desc += ", ";
                desc += st.as_static_str();
            }

            desc += " ";
        }

        desc += &format!("({})", self.station_num);

        desc
    }
}

impl Display for SiteInfo {
    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        writeln!(
            formatter,
            "Site: station_num - {:6} | name - {:20} | state - {:2} | notes - {}",
            self.station_num,
            self.name.as_deref().unwrap_or("None"),
            self.state.map(|s| s.as_static_str()).unwrap_or("None"),
            self.notes.as_deref().unwrap_or("None"),
        )
    }
}

impl Default for SiteInfo {
    fn default() -> Self {
        SiteInfo {
            station_num: StationNumber::from(0),
            name: None,
            notes: None,
            state: None,
            time_zone: None,
        }
    }
}

#[cfg(feature = "pylib")]
#[cfg_attr(feature = "pylib", pymethods)]
impl SiteInfo {
    #[getter]
    fn get_station_num(&self) -> StationNumber {
        self.station_num
    }

    #[getter]
    fn get_station_name(&self) -> String {
        self.name.clone().unwrap_or_else(|| "No Name".to_owned())
    }
}

#[cfg(feature = "pylib")]
#[cfg_attr(feature = "pylib", pyproto)]
impl pyo3::PyObjectProtocol<'_> for SiteInfo {
    fn __repr__(&self) -> PyResult<String> {
        Ok(self.description())
    }
}

/*--------------------------------------------------------------------------------------------------
                                          Unit Tests
--------------------------------------------------------------------------------------------------*/
#[cfg(test)]
mod unit {
    use super::*;

    #[test]
    fn test_site_incomplete() {
        let complete_site = SiteInfo {
            station_num: StationNumber::from(1),
            name: Some("tv station".to_owned()),
            state: Some(StateProv::VI),
            notes: Some("".to_owned()),
            time_zone: Some(chrono::FixedOffset::west(7 * 3600)),
        };

        let incomplete_site = SiteInfo {
            station_num: StationNumber::from(1),
            name: Some("tv station".to_owned()),
            state: None,
            notes: None,
            time_zone: None,
        };

        assert!(!complete_site.incomplete());
        assert!(incomplete_site.incomplete());
    }
}