i-dunno 0.6.0

RFC 8771 Internationalized Deliberately Unreadable Network Notation
Documentation
use std::io::{BufRead, Write};

pub fn process_idna<R: BufRead, W: Write>(input: R, mut output: W) {
    output
        .write(
            br#"/// Return true if the supplied char is disallowed by IDNA2008 (RFC5894).
pub fn idna_disallowed(ch: char) -> bool {
    match ch {
"#,
        )
        .unwrap();

    for cp in input
        .lines()
        .filter_map(|line| {
            if let Ok(line) = line {
                Some(line)
            } else {
                None
            }
        })
        .filter_map(ParsedLine::from_string)
        .filter(|parsed_line| parsed_line.rule.starts_with("disallowed"))
        .filter(|parsed_line| !parsed_line.comment.starts_with("NA "))
        .filter(|parsed_line| !parsed_line.comment.contains("<reserved"))
        .filter(|parsed_line| !parsed_line.comment.contains("<surrogate"))
        .filter(|parsed_line| !parsed_line.comment.contains("<private-use"))
        .filter(|parsed_line| !parsed_line.comment.contains("<noncharacter"))
        .flat_map(|parsed_line| parsed_line.code_points())
    {
        output
            .write(format!("        '\\u{{{:04x}}}' => true,\n", cp).as_bytes())
            .unwrap();
    }

    output
        .write(
            r#"         _ => false,
    }
}
"#
            .as_bytes(),
        )
        .unwrap();
}

struct ParsedLine {
    char_range: String,
    rule: String,
    comment: String,
}

impl ParsedLine {
    fn from_string(line: String) -> Option<ParsedLine> {
        let line_spl: Vec<&str> = line.splitn(2, ";").collect();
        if line_spl.len() != 2 {
            return None;
        }

        let char_range = line_spl[0].trim().to_string();
        let rest = line_spl[1];

        let rest_spl: Vec<&str> = rest.splitn(2, "#").collect();
        if rest_spl.len() != 2 {
            return None;
        }

        let rule = rest_spl[0].trim().to_string();
        let comment = rest_spl[1].trim().to_string();

        Some(ParsedLine {
            char_range,
            rule,
            comment,
        })
    }

    fn code_points(&self) -> Vec<u32> {
        let spl: Vec<&str> = self.char_range.splitn(2, "..").collect();
        if spl.len() == 2 {
            let start = u32::from_str_radix(spl[0], 16).unwrap();
            let end = u32::from_str_radix(spl[1], 16).unwrap();
            (start..=end).collect()
        } else {
            vec![u32::from_str_radix(&self.char_range, 16).unwrap()]
        }
    }
}

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

    #[test]
    fn example() {
        let input = r#"
# IdnaMappingTable.txt
# Date: 2019-11-07, 13:44:35 GMT
# © 2019 Unicode®, Inc.
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
# For terms of use, see http://www.unicode.org/terms_of_use.html
#
# Unicode IDNA Compatible Preprocessing for UTS #46
# Version: 13.0.0
#
# For documentation and usage, see http://www.unicode.org/reports/tr46
#
0000..002C    ; disallowed_STD3_valid                  # 1.1  <control-0000>..COMMA
002D..002E    ; valid                                  # 1.1  HYPHEN-MINUS..FULL STOP
002F          ; disallowed_STD3_valid                  # 1.1  SOLIDUS
0030..0039    ; valid                                  # 1.1  DIGIT ZERO..DIGIT NINE
003A..0040    ; disallowed_STD3_valid                  # 1.1  COLON..COMMERCIAL AT
0041          ; mapped                 ; 0061          # 1.1  LATIN CAPITAL LETTER A
0042          ; mapped                 ; 0062          # 1.1  LATIN CAPITAL LETTER B
0043          ; mapped                 ; 0063          # 1.1  LATIN CAPITAL LETTER C
0044          ; mapped                 ; 0064          # 1.1  LATIN CAPITAL LETTER D
0045          ; mapped                 ; 0065          # 1.1  LATIN CAPITAL LETTER E
0046          ; mapped                 ; 0066          # 1.1  LATIN CAPITAL LETTER F
0047          ; mapped                 ; 0067          # 1.1  LATIN CAPITAL LETTER G
0048          ; mapped                 ; 0068          # 1.1  LATIN CAPITAL LETTER H
0049          ; mapped                 ; 0069          # 1.1  LATIN CAPITAL LETTER I
004A          ; mapped                 ; 006A          # 1.1  LATIN CAPITAL LETTER J
004B          ; mapped                 ; 006B          # 1.1  LATIN CAPITAL LETTER K
004C          ; mapped                 ; 006C          # 1.1  LATIN CAPITAL LETTER L
004D          ; mapped                 ; 006D          # 1.1  LATIN CAPITAL LETTER M
004E          ; mapped                 ; 006E          # 1.1  LATIN CAPITAL LETTER N
004F          ; mapped                 ; 006F          # 1.1  LATIN CAPITAL LETTER O
0050          ; mapped                 ; 0070          # 1.1  LATIN CAPITAL LETTER P
0051          ; mapped                 ; 0071          # 1.1  LATIN CAPITAL LETTER Q
0052          ; mapped                 ; 0072          # 1.1  LATIN CAPITAL LETTER R
0053          ; mapped                 ; 0073          # 1.1  LATIN CAPITAL LETTER S
0054          ; mapped                 ; 0074          # 1.1  LATIN CAPITAL LETTER T
0055          ; mapped                 ; 0075          # 1.1  LATIN CAPITAL LETTER U
0056          ; mapped                 ; 0076          # 1.1  LATIN CAPITAL LETTER V
0057          ; mapped                 ; 0077          # 1.1  LATIN CAPITAL LETTER W
0058          ; mapped                 ; 0078          # 1.1  LATIN CAPITAL LETTER X
0059          ; mapped                 ; 0079          # 1.1  LATIN CAPITAL LETTER Y
005A          ; mapped                 ; 007A          # 1.1  LATIN CAPITAL LETTER Z
005B..0060    ; disallowed_STD3_valid                  # 1.1  LEFT SQUARE BRACKET..GRAVE ACCENT
0061..007A    ; valid                                  # 1.1  LATIN SMALL LETTER A..LATIN SMALL LETTER Z
007B..007F    ; disallowed_STD3_valid                  # 1.1  LEFT CURLY BRACKET..<control-007F>
0080..009F    ; disallowed                             # 1.1  <control-0080>..<control-009F>
00A0          ; disallowed_STD3_mapped ; 0020          # 1.1  NO-BREAK SPACE
00A1..00A7    ; valid                  ;      ; NV8    # 1.1  INVERTED EXCLAMATION MARK..SECTION SIGN
00A8          ; disallowed_STD3_mapped ; 0020 0308     # 1.1  DIAERESIS
00A9          ; valid                  ;      ; NV8    # 1.1  COPYRIGHT SIGN
0590          ; disallowed                             # NA   <reserved-0590>
D800..DFFF    ; disallowed                             # 2.0  <surrogate-D800>..<surrogate-DFFF>
E000..F8FF    ; disallowed                             # 1.1  <private-use-E000>..<private-use-F8FF>
FFFE..FFFF    ; disallowed                             # 1.1  <noncharacter-FFFE>..<noncharacter-FFFF>
"#;

        let expected = r#"/// Return true if the supplied char is disallowed by IDNA2008 (RFC5894).
pub fn idna_disallowed(ch: char) -> bool {
    match ch {
        '\u{0000}' => true,
        '\u{0001}' => true,
        '\u{0002}' => true,
        '\u{0003}' => true,
        '\u{0004}' => true,
        '\u{0005}' => true,
        '\u{0006}' => true,
        '\u{0007}' => true,
        '\u{0008}' => true,
        '\u{0009}' => true,
        '\u{000a}' => true,
        '\u{000b}' => true,
        '\u{000c}' => true,
        '\u{000d}' => true,
        '\u{000e}' => true,
        '\u{000f}' => true,
        '\u{0010}' => true,
        '\u{0011}' => true,
        '\u{0012}' => true,
        '\u{0013}' => true,
        '\u{0014}' => true,
        '\u{0015}' => true,
        '\u{0016}' => true,
        '\u{0017}' => true,
        '\u{0018}' => true,
        '\u{0019}' => true,
        '\u{001a}' => true,
        '\u{001b}' => true,
        '\u{001c}' => true,
        '\u{001d}' => true,
        '\u{001e}' => true,
        '\u{001f}' => true,
        '\u{0020}' => true,
        '\u{0021}' => true,
        '\u{0022}' => true,
        '\u{0023}' => true,
        '\u{0024}' => true,
        '\u{0025}' => true,
        '\u{0026}' => true,
        '\u{0027}' => true,
        '\u{0028}' => true,
        '\u{0029}' => true,
        '\u{002a}' => true,
        '\u{002b}' => true,
        '\u{002c}' => true,
        '\u{002f}' => true,
        '\u{003a}' => true,
        '\u{003b}' => true,
        '\u{003c}' => true,
        '\u{003d}' => true,
        '\u{003e}' => true,
        '\u{003f}' => true,
        '\u{0040}' => true,
        '\u{005b}' => true,
        '\u{005c}' => true,
        '\u{005d}' => true,
        '\u{005e}' => true,
        '\u{005f}' => true,
        '\u{0060}' => true,
        '\u{007b}' => true,
        '\u{007c}' => true,
        '\u{007d}' => true,
        '\u{007e}' => true,
        '\u{007f}' => true,
        '\u{0080}' => true,
        '\u{0081}' => true,
        '\u{0082}' => true,
        '\u{0083}' => true,
        '\u{0084}' => true,
        '\u{0085}' => true,
        '\u{0086}' => true,
        '\u{0087}' => true,
        '\u{0088}' => true,
        '\u{0089}' => true,
        '\u{008a}' => true,
        '\u{008b}' => true,
        '\u{008c}' => true,
        '\u{008d}' => true,
        '\u{008e}' => true,
        '\u{008f}' => true,
        '\u{0090}' => true,
        '\u{0091}' => true,
        '\u{0092}' => true,
        '\u{0093}' => true,
        '\u{0094}' => true,
        '\u{0095}' => true,
        '\u{0096}' => true,
        '\u{0097}' => true,
        '\u{0098}' => true,
        '\u{0099}' => true,
        '\u{009a}' => true,
        '\u{009b}' => true,
        '\u{009c}' => true,
        '\u{009d}' => true,
        '\u{009e}' => true,
        '\u{009f}' => true,
        '\u{00a0}' => true,
        '\u{00a8}' => true,
         _ => false,
    }
}
"#;

        let mut output_bytes = Vec::new();

        process_idna(input.as_bytes(), &mut output_bytes);
        let output = String::from_utf8(output_bytes).unwrap();
        println!("{}", output);
        assert_eq!(output, expected);
    }
}