hours 0.0.1

A parser for the opening hours format
Documentation
//!
//! Opening hours implementation for Rust
//!
//! Specification: https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
//!

// #[macro_use]
// extern crate nom;

use nom::{
    branch::alt,
    bytes::complete::{is_not, tag},
    character::complete::char,
    // character::complete::one_of,
    // character::is_digit,
    combinator::{map, opt},
    error::ErrorKind,
    sequence::delimited,
    sequence::preceded,
    Err,
    IResult,
};

use crate::{Modifier, State};
const QUOTE_CHAR: char = '"';
const QUOTE_STR: &str = "\"";

fn parse_comment(i: &str) -> IResult<&str, &str> {
    delimited(
        char(QUOTE_CHAR),
        alt((is_not(QUOTE_STR), tag(""))),
        char(QUOTE_CHAR),
    )(i)
}

#[test]
fn parse_comment_test_empty() {
    assert_eq!(parse_comment("\"\""), Ok(("", "")));
}

#[test]
fn parse_comment_test_good() {
    assert_eq!(parse_comment("\"this is good\""), Ok(("", "this is good")));
}

#[test]
fn parse_comment_test_non_comment() {
    assert_eq!(
        parse_comment("foo"),
        Err(Err::Error(("foo", ErrorKind::Char)))
    );
}

#[test]
fn parse_comment_test_with_quote() {
    assert_eq!(
        parse_comment("\"this \" isn't good\""),
        Ok((" isn't good\"", "this "))
    );
}

fn parse_state(i: &str) -> IResult<&str, State> {
    map(
        alt((tag("open"), tag("closed"), tag("off"), tag("unknown"))),
        |s: &str| match s {
            "open" => State::Open,
            "closed" => State::Closed,
            "off" => State::Closed,
            "unknown" => State::Unknown,
            _ => State::Unknown, // this shouldn't happen
        },
    )(i)
}

#[test]
fn parse_state_test() {
    assert_eq!(parse_state("open"), Ok(("", State::Open)));
    assert_eq!(parse_state("closed"), Ok(("", State::Closed)));
    assert_eq!(parse_state("off"), Ok(("", State::Closed)));
    assert_eq!(parse_state("unknown"), Ok(("", State::Unknown)));
    assert_eq!(parse_state(""), Err(Err::Error(("", ErrorKind::Tag))));
}

fn parse_modifier(i: &str) -> IResult<&str, Modifier> {
    let (i, state_opt) = opt(parse_state)(i)?;
    let (i, _) = opt(tag(" "))(i)?;
    let (i, c_opt) = opt(parse_comment)(i)?;
    Ok((
        i,
        Modifier {
            state: state_opt.unwrap_or(State::Open),
            comment: c_opt.unwrap_or("").to_owned(),
        },
    ))
}

#[test]
fn parse_modifier_test_open_no_comment() {
    assert_eq!(
        parse_modifier("open"),
        Ok((
            "",
            Modifier {
                state: State::Open,
                comment: "".to_owned(),
            }
        ))
    );
}

#[test]
fn parse_modifier_test_open_comment() {
    assert_eq!(
        parse_modifier("open \"hi\""),
        Ok((
            "",
            Modifier {
                state: State::Open,
                comment: "hi".to_owned(),
            }
        ))
    );
}

#[test]
fn parse_modifier_test_only_comment() {
    assert_eq!(
        parse_modifier("\"comment\""),
        Ok((
            "",
            Modifier {
                state: State::Open,
                comment: "comment".to_owned(),
            }
        ))
    );
}