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);
}
}