capitol 0.6.1

Parse United States Congress legislative document citations
Documentation
use crate::legislation::{Chamber, CommitteeDocumentType, Congress, MeasureType};
use crate::Citation;

use winnow::ascii::{alphanumeric1, digit1};
use winnow::combinator::{alt, opt, peek};
use winnow::Parser;
use winnow::Result;

fn clean(input: &str) -> String {
    input.to_lowercase().replace(['.', ' ', ':'], "")
}

pub fn parse(input: &str) -> Result<Citation> {
    let cleaned = clean(input);
    let mut input = cleaned.as_str();

    alt((
        parse_measure,
        parse_amendment,
        parse_committee_document,
        parse_statute,
        parse_law,
    ))
    .parse_next(&mut input)
}

fn parse_congress(input: &mut &str) -> Result<Option<Congress>> {
    Ok(parse_prefix.parse_next(input).ok().map(Congress))
}

fn parse_long_law(input: &mut &str) -> Result<Citation> {
    let (_, congress, _, number) =
        ("publiclawno", parse_congress, "-", parse_number).parse_next(input)?;
    Ok(Citation::Law { congress, number })
}

fn parse_short_law(input: &mut &str) -> Result<Citation> {
    let (congress, _, number) =
        (parse_congress, alt(("pl", "publ")), parse_number).parse_next(input)?;
    Ok(Citation::Law { congress, number })
}

fn parse_law(input: &mut &str) -> Result<Citation> {
    alt((parse_long_law, parse_short_law)).parse_next(input)
}

fn parse_amendment(input: &mut &str) -> Result<Citation> {
    let (congress, chamber, _, number) = (
        parse_congress,
        parse_chamber,
        alt(("amdt", "a")),
        parse_number,
    )
        .parse_next(input)?;
    Ok(Citation::Amendment {
        congress,
        chamber,
        number,
    })
}

fn parse_statute(input: &mut &str) -> Result<Citation> {
    let (volume, _, page) = (parse_prefix, "stat", parse_number).parse_next(input)?;
    Ok(Citation::Statute { volume, page })
}

fn parse_measure(input: &mut &str) -> Result<Citation> {
    let (congress, chamber, measure_type, number, suffix) = (
        parse_congress,
        parse_chamber,
        parse_measure_type,
        parse_number,
        parse_suffix,
    )
        .parse_next(input)?;
    Ok(Citation::Measure {
        congress,
        chamber,
        measure_type,
        number,
        version: suffix.map(String::from),
    })
}

fn parse_committee_document(input: &mut &str) -> Result<Citation> {
    let (congress, chamber, document_type, number) = (
        parse_congress,
        parse_chamber,
        parse_committee_document_type,
        parse_number,
    )
        .parse_next(input)?;
    Ok(Citation::CommitteeDocument {
        congress,
        chamber,
        document_type,
        number,
    })
}

fn parse_committee_document_type(input: &mut &str) -> Result<CommitteeDocumentType> {
    alt((
        alt(("rpt", "rept")).map(|_| CommitteeDocumentType::Report),
        "prt".map(|_| CommitteeDocumentType::Print),
    ))
    .parse_next(input)
}

fn parse_house(input: &mut &str) -> Result<Chamber> {
    alt((
        ("hr", peek(digit1)).map(|_| Chamber::House),
        "h".map(|_| Chamber::House),
    ))
    .parse_next(input)
}

fn parse_chamber(input: &mut &str) -> Result<Chamber> {
    alt((
        parse_house,
        'j'.map(|_| Chamber::Joint),
        's'.map(|_| Chamber::Senate),
    ))
    .parse_next(input)
}

fn parse_measure_type(input: &mut &str) -> Result<MeasureType> {
    alt((
        "cres".map(|_| MeasureType::ConcurrentResolution),
        "jres".map(|_| MeasureType::JointResolution),
        "res".map(|_| MeasureType::Resolution),
        "".map(|_| MeasureType::Bill),
    ))
    .parse_next(input)
}

fn parse_prefix(input: &mut &str) -> Result<usize> {
    digit1.parse_to().parse_next(input)
}

fn parse_number(input: &mut &str) -> Result<usize> {
    digit1.parse_to().parse_next(input)
}

fn parse_suffix<'s>(input: &mut &'s str) -> Result<Option<&'s str>> {
    opt(alphanumeric1).parse_next(input)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_house_bill() {
        let mut input = "118hr8070";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::Bill,
            number: 8070,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_senate_bill() {
        let mut input = "118s8070";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::Senate,
            measure_type: MeasureType::Bill,
            number: 8070,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_sr_fails() {
        let mut input = "118sr8070";
        let result = parse_measure(&mut input);
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_house_resolution() {
        let mut input = "118hres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::Resolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_house_concurrent_resolution() {
        let mut input = "118hcres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::ConcurrentResolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_house_joint_resolution() {
        let mut input = "118hjres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::JointResolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_senate_resolution() {
        let mut input = "118sres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::Senate,
            measure_type: MeasureType::Resolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_senate_concurrent_resolution() {
        let mut input = "118scres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::Senate,
            measure_type: MeasureType::ConcurrentResolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_senate_joint_resolution() {
        let mut input = "118sjres111";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::Senate,
            measure_type: MeasureType::JointResolution,
            number: 111,
            version: None,
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_house_bill_with_version() {
        let mut input = "118hr8070ih";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::Bill,
            number: 8070,
            version: Some("ih".to_string()),
        };
        let result = parse_measure(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_house_report() {
        let mut input = "118hrpt529";
        let expected = Citation::CommitteeDocument {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            document_type: CommitteeDocumentType::Report,
            number: 529,
        };
        let result = parse_committee_document(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_new_parse_measure() {
        let mut input = "118hr8070ih";
        let expected = Citation::Measure {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            measure_type: MeasureType::Bill,
            number: 8070,
            version: Some("ih".to_string()),
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_new_parse_committee_document() {
        let mut input = "118hrpt529";
        let expected = Citation::CommitteeDocument {
            congress: Some(Congress(118)),
            chamber: Chamber::House,
            document_type: CommitteeDocumentType::Report,
            number: 529,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_new_parse_amendtment() {
        let mut input = "118samdt529";
        let expected = Citation::Amendment {
            congress: Some(Congress(118)),
            chamber: Chamber::Senate,
            number: 529,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_statute() {
        let mut input = "86Stat1326";
        let expected = Citation::Statute {
            volume: 86,
            page: 1326,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap());
    }

    #[test]
    fn test_parse_long_law() {
        let mut input = "Public Law No: 119-68";
        let expected = Citation::Law {
            congress: Some(Congress(119)),
            number: 68,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap())
    }

    #[test]
    fn test_parse_short_law() {
        let mut input = "119pl68";
        let expected = Citation::Law {
            congress: Some(Congress(119)),
            number: 68,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap())
    }

    #[test]
    fn test_parse_longer_short_law() {
        let mut input = "119publ68";
        let expected = Citation::Law {
            congress: Some(Congress(119)),
            number: 68,
        };
        let result = parse(&mut input);
        assert_eq!(expected, result.unwrap())
    }
}