import re
import requests
def parse(text):
display = []
for match in re.findall(r"id=\".*?\">\"(.*?)\"\n.*\n.*
(((.*?)\n)+?)\s+( | |)", text):
# Skip F keys here
if re.match("^F\d+$", match[0]):
continue
doc = re.sub(r"[ \t][ \t]+", "\n", match[1])
doc = re.sub(r"(.*?)", "\\1", doc)
doc_comment = ""
for line in doc.split('\n'):
line = line.strip()
if not line:
continue
doc_comment += " /// {}\n".format(line)
display.append([match[0], doc_comment, []])
return display
def emit_enum_entries(display, file):
for [key, doc_comment, alternatives] in display:
print("{} {},".format(doc_comment, key), file=file)
def print_display_entries(display, file):
for [key, doc_comment, alternatives] in display:
print(" {0} => f.write_str(\"{0}\"),".format(
key), file=file)
def print_from_str_entries(display, file):
for [key, doc_comment, alternatives] in display:
print(" \"{0}\"".format(key), file=file, end='')
for alternative in alternatives:
print(" | \"{0}\"".format(alternative), file=file, end='')
print(" => Ok({0}),".format(key), file=file)
def add_alternative_for(display, key, alternative):
for [found_key, doc_comment, alternatives] in display:
if found_key != key:
continue
alternatives.append(alternative)
def convert_key(text, file):
print("""
// AUTO GENERATED CODE - DO NOT EDIT
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::fmt::{self, Display};
use std::str::FromStr;
use std::error::Error;
/// Key represents the meaning of a keypress.
///
/// Specification:
///
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Key {
/// A key string that corresponds to the character typed by the user,
/// taking into account the user’s current locale setting, modifier state,
/// and any system-level keyboard mapping overrides that are in effect.
Character(String),
""", file=file)
display = parse(text)
for i in range(1, 36):
display.append([
'F{}'.format(i),
' /// The F{0} key, a general purpose function key, as index {0}.\n'.format(i),
[]
])
emit_enum_entries(display, file)
print("}", file=file)
print("""
impl Display for Key {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Key::*;
match *self {
Character(ref s) => write!(f, "{}", s),
""", file=file)
print_display_entries(display, file)
print("""
}
}
}
impl FromStr for Key {
type Err = UnrecognizedKeyError;
fn from_str(s: &str) -> Result {
use crate::Key::*;
match s {
s if is_key_string(s) => Ok(Character(s.to_string())),""", file=file)
print_from_str_entries(display, file)
print("""
_ => Err(UnrecognizedKeyError),
}
}
}
/// Parse from string error, returned when string does not match to any Key variant.
#[derive(Clone, Debug)]
pub struct UnrecognizedKeyError;
impl fmt::Display for UnrecognizedKeyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Unrecognized key")
}
}
impl Error for UnrecognizedKeyError {}
/// Check if string can be used as a `Key::Character` _keystring_.
///
/// This check is simple and is meant to prevents common mistakes like mistyped keynames
/// (e.g. `Ennter`) from being recognized as characters.
fn is_key_string(s: &str) -> bool {
s.chars().all(|c| !c.is_control()) && s.chars().skip(1).all(|c| !c.is_ascii())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_is_key_string() {
assert!(is_key_string("A"));
assert!(!is_key_string("AA"));
assert!(!is_key_string("\t"));
}
}
""", file=file)
def convert_code(text, file):
print("""
// AUTO GENERATED CODE - DO NOT EDIT
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::fmt::{self, Display};
use std::str::FromStr;
use std::error::Error;
/// Code is the physical position of a key.
///
/// The names are based on the US keyboard. If the key
/// is not present on US keyboards a name from another
/// layout is used.
///
/// Specification:
///
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Code {""", file=file)
display = parse(text)
for i in range(1, 36):
display.append([
'F{}'.format(i),
' /// F{}
\n'.format(i),
[]
])
chromium_key_codes = [
'BrightnessDown',
'BrightnessUp',
'DisplayToggleIntExt',
'KeyboardLayoutSelect',
'LaunchAssistant',
'LaunchControlPanel',
'LaunchScreenSaver',
'MailForward',
'MailReply',
'MailSend',
'MediaFastForward',
'MediaPause',
'MediaPlay',
'MediaRecord',
'MediaRewind',
'MicrophoneMuteToggle',
'PrivacyScreenToggle',
'SelectTask',
'ShowAllWindows',
'ZoomToggle',
]
for chromium_only in chromium_key_codes:
display.append([
chromium_only,
' /// Non-standard code value supported by Chromium.\n',
[]
])
add_alternative_for(display, 'MetaLeft', 'OSLeft')
add_alternative_for(display, 'MetaRight', 'OSRight')
add_alternative_for(display, 'AudioVolumeDown', 'VolumeDown')
add_alternative_for(display, 'AudioVolumeMute', 'VolumeMute')
add_alternative_for(display, 'AudioVolumeUp', 'VolumeUp')
add_alternative_for(display, 'MediaSelect', 'LaunchMediaPlayer')
emit_enum_entries(display, file)
print("}", file=file)
print("""
impl Display for Code {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Code::*;
match *self {
""", file=file)
print_display_entries(display, file)
print("""
}
}
}
impl FromStr for Code {
type Err = UnrecognizedCodeError;
fn from_str(s: &str) -> Result {
use crate::Code::*;
match s {""", file=file)
print_from_str_entries(display, file)
print("""
_ => Err(UnrecognizedCodeError),
}
}
}
/// Parse from string error, returned when string does not match to any Code variant.
#[derive(Clone, Debug)]
pub struct UnrecognizedCodeError;
impl fmt::Display for UnrecognizedCodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Unrecognized code")
}
}
impl Error for UnrecognizedCodeError {}
""", file=file)
if __name__ == '__main__':
input = requests.get('https://w3c.github.io/uievents-key/').text
with open('src/key.rs', 'w', encoding='utf-8') as output:
convert_key(input, output)
input = requests.get('https://w3c.github.io/uievents-code/').text
with open('src/code.rs', 'w', encoding='utf-8') as output:
convert_code(input, output)