nmea 0.7.0

Simple NMEA 0183 parser
Documentation
use std::{
    error::Error,
    fs::{self, File},
    io::{BufRead, BufReader},
    path::Path,
};

use helpers::format_satellites;
use nmea::{parse_str, Nmea};

mod helpers;

#[test]
fn test_parse_file_log() {
    let res = process_file(&Path::new("tests").join("data").join("nmea1.log"))
        .unwrap_or_else(|err| panic!("process file failed with error '{}'", err));

    let expected: Vec<_> = BufReader::new(
        File::open(Path::new("tests").join("data").join("nmea1.log.expected")).unwrap(),
    )
    .lines()
    .collect::<Result<_, _>>()
    .expect("Should collect lines");

    assert_eq!(expected, res);
}

#[test]
fn test_parse_issue_2() {
    let mut input =
        BufReader::new(File::open(Path::new("tests").join("data").join("nmea2.log")).unwrap());
    let mut nmea = Nmea::default();
    for _ in 0..100 {
        let mut buffer = String::new();
        let size = input.read_line(&mut buffer).unwrap();
        eprintln!("buffer = {:?}", buffer);
        if size > 0 {
            if buffer.as_bytes()[0] == b'$' {
                let _ = nmea.parse(&buffer);
                println!("{:?}", nmea);
            }
        } else {
            break;
        }
    }
}

#[test]
fn test_parse_all_logs() {
    for (i, log_path) in [
        Path::new("tests")
            .join("data")
            .join("nmea_with_sat_info.log"),
        Path::new("tests").join("data").join("nmea1.log"),
        Path::new("tests").join("data").join("nmea2.log"),
    ]
    .iter()
    .enumerate()
    {
        println!("test parsing of {:?}", log_path);
        let full_log = fs::read_to_string(log_path).unwrap();

        let mut nmea1 = Nmea::default();
        let mut nmea2 = Nmea::default();

        for (line_index, line) in full_log.lines().enumerate() {
            let line_no = line_index + 1;
            if line.starts_with("$GNGRS")
                || line.starts_with("$GNGST")
                || line.starts_with("$GNZDA")
                || line.starts_with("$GNGBS")
            {
                println!(
                    "Ignoring unsupported {} at {:?}:{}",
                    line, log_path, line_no
                );
                continue;
            }

            let expect_msg = format!("Parsing of {} at {:?}:{} failed", line, log_path, line_no);

            parse_str(line).expect(&expect_msg);
            nmea1.parse(line).expect(&expect_msg);
            nmea2.parse_for_fix(line).expect(&expect_msg);
        }

        let sat_state = match i {
            0 => vec![
                "{Galileo 2 Some(56.0) Some(46.0) None}",
                "{Galileo 3 Some(11.0) Some(149.0) None}",
                "{Galileo 7 Some(54.0) Some(298.0) None}",
                "{Galileo 8 Some(60.0) Some(174.0) None}",
                "{Galileo 11 Some(1.0) Some(46.0) None}",
                "{Galileo 25 Some(8.0) Some(52.0) None}",
                "{Galileo 27 Some(11.0) Some(233.0) None}",
                "{Galileo 30 Some(66.0) Some(239.0) None}",
                "{Gps 5 Some(19.0) Some(222.0) Some(19.0)}",
                "{Gps 7 Some(5.0) Some(90.0) Some(18.0)}",
                "{Gps 13 Some(84.0) Some(239.0) Some(23.0)}",
                "{Gps 14 Some(56.0) Some(52.0) Some(16.0)}",
                "{Gps 15 Some(50.0) Some(296.0) Some(30.0)}",
                "{Gps 17 Some(35.0) Some(125.0) Some(24.0)}",
                "{Gps 19 Some(23.0) Some(147.0) None}",
                "{Gps 20 Some(3.0) Some(201.0) None}",
                "{Gps 23 Some(11.0) Some(319.0) Some(23.0)}",
                "{Gps 24 Some(16.0) Some(284.0) Some(25.0)}",
                "{Gps 28 Some(0.0) Some(0.0) Some(19.0)}",
                "{Gps 30 Some(28.0) Some(84.0) Some(20.0)}",
                "{Glonass 68 Some(39.0) Some(185.0) Some(28.0)}",
                "{Glonass 69 Some(63.0) Some(275.0) Some(34.0)}",
                "{Glonass 70 Some(14.0) Some(330.0) Some(22.0)}",
                "{Glonass 79 Some(61.0) Some(298.0) Some(36.0)}",
                "{Glonass 81 Some(0.0) Some(0.0) Some(17.0)}",
                "{Glonass 87 Some(14.0) Some(39.0) None}",
                "{Glonass 88 Some(15.0) Some(82.0) None}",
                "{Qzss 2 Some(11.0) Some(112.0) None}",
                "{Qzss 5 Some(28.0) Some(135.0) None}",
                "{Qzss 7 Some(22.0) Some(49.0) None}",
                "{Qzss 9 Some(2.0) Some(118.0) None}",
                "{Qzss 10 Some(36.0) Some(54.0) Some(18.0)}",
                "{Qzss 11 Some(20.0) Some(75.0) Some(18.0)}",
                "{Qzss 12 Some(5.0) Some(29.0) None}",
                "{Qzss 19 Some(0.0) Some(0.0) None}",
                "{Qzss 20 Some(37.0) Some(296.0) Some(30.0)}",
                "{Qzss 23 Some(66.0) Some(39.0) Some(17.0)}",
                "{Qzss 25 Some(19.0) Some(68.0) None}",
                "{Qzss 28 Some(3.0) Some(153.0) None}",
            ],
            1 => vec![
                "{Gps 1 Some(9.0) Some(74.0) None}",
                "{Gps 8 Some(3.0) Some(29.0) Some(22.0)}",
                "{Gps 10 Some(4.0) Some(350.0) Some(18.0)}",
                "{Gps 11 Some(19.0) Some(59.0) Some(19.0)}",
                "{Gps 13 Some(59.0) Some(220.0) None}",
                "{Gps 15 Some(45.0) Some(281.0) None}",
                "{Gps 17 Some(36.0) Some(151.0) None}",
                "{Gps 18 Some(11.0) Some(323.0) None}",
                "{Gps 19 Some(17.0) Some(170.0) None}",
                "{Gps 20 Some(6.0) Some(258.0) None}",
                "{Gps 24 Some(13.0) Some(288.0) None}",
                "{Gps 28 Some(65.0) Some(71.0) None}",
                "{Gps 30 Some(35.0) Some(109.0) None}",
                "{Glonass 65 Some(24.0) Some(229.0) None}",
                "{Glonass 66 Some(38.0) Some(296.0) None}",
                "{Glonass 67 Some(11.0) Some(347.0) Some(18.0)}",
                "{Glonass 74 Some(35.0) Some(78.0) None}",
                "{Glonass 75 Some(76.0) Some(343.0) None}",
                "{Glonass 76 Some(29.0) Some(279.0) None}",
                "{Glonass 83 Some(13.0) Some(12.0) Some(10.0)}",
                "{Glonass 84 Some(41.0) Some(67.0) None}",
                "{Glonass 85 Some(26.0) Some(132.0) None}",
            ],
            2 => vec![
                "{Gps 2 Some(35.0) Some(291.0) None}",
                "{Gps 3 Some(9.0) Some(129.0) None}",
                "{Gps 5 Some(14.0) Some(305.0) None}",
                "{Gps 6 Some(38.0) Some(226.0) None}",
                "{Gps 7 Some(56.0) Some(177.0) None}",
                "{Gps 9 Some(70.0) Some(67.0) None}",
                "{Gps 16 Some(20.0) Some(55.0) None}",
                "{Gps 23 Some(41.0) Some(76.0) None}",
                "{Gps 26 Some(10.0) Some(30.0) None}",
                "{Gps 29 Some(5.0) Some(341.0) None}",
                "{Gps 30 Some(26.0) Some(199.0) None}",
                "{Gps 36 Some(30.0) Some(158.0) None}",
                "{Gps 49 Some(32.0) Some(192.0) None}",
                "{Glonass 66 Some(45.0) Some(91.0) None}",
                "{Glonass 67 Some(67.0) Some(334.0) None}",
                "{Glonass 68 Some(17.0) Some(297.0) None}",
                "{Glonass 75 Some(13.0) Some(25.0) None}",
                "{Glonass 76 Some(49.0) Some(59.0) None}",
                "{Glonass 77 Some(40.0) Some(156.0) None}",
                "{Glonass 78 Some(0.0) Some(183.0) None}",
                "{Glonass 82 Some(15.0) Some(246.0) None}",
                "{Glonass 83 Some(28.0) Some(298.0) None}",
                "{Glonass 84 Some(10.0) Some(352.0) None}",
            ],
            _ => panic!("You need to add sat state for new log here"),
        };
        assert_eq!(sat_state, format_satellites(nmea1.satellites()));
        assert_eq!(sat_state, format_satellites(nmea2.satellites()));
    }
}

fn err_to_string<E: Error>(e: E) -> String {
    e.to_string()
}

fn process_file(n: &Path) -> Result<Vec<String>, String> {
    let input = BufReader::new(File::open(n).map_err(err_to_string)?);
    let mut nmea = nmea::Nmea::default();
    let mut ret = Vec::with_capacity(15_000);
    for (num, line) in input.lines().enumerate() {
        let line = line
            .map_err(err_to_string)
            .map_err(|s| format!("{} at line {}", s, num + 1))?;
        let parse_res = nmea
            .parse(&line)
            .map_err(|s| format!("{:?} at line {}", s, num + 1))?;
        ret.push(format!("{:?}", parse_res));
    }
    Ok(ret)
}