keyboard_codes/
lib.rs

1//! keyboard-codes: Cross-platform keyboard key code mapping and conversion
2//!
3//! This crate provides comprehensive keyboard key definitions and cross-platform
4//! code mapping for Windows, Linux, and macOS. It supports standard keys,
5//! modifiers, custom key mapping, and bidirectional conversion between key names
6//! and platform-specific codes.
7//!
8//! # Examples
9//!
10//! ```rust
11//! use keyboard_codes::{Key, Modifier, Platform, KeyCodeMapper, KeyboardInput};
12//!
13//! // Parse key from string
14//! let key: Key = "Enter".parse().unwrap();
15//! assert_eq!(key, Key::Enter);
16//!
17//! // Convert key to platform-specific code
18//! let windows_code = key.to_code(Platform::Windows);
19//! let linux_code = key.to_code(Platform::Linux);
20//!
21//! // Parse key from code
22//! let key_from_code = Key::from_code(0x0D, Platform::Windows).unwrap();
23//! assert_eq!(key_from_code, Key::Enter);
24//!
25//! // Unified keyboard input handling
26//! let input1: KeyboardInput = "A".parse().unwrap();        // Key
27//! let input2: KeyboardInput = "Ctrl".parse().unwrap();     // Modifier
28//! let input3: KeyboardInput = "ctrl".parse().unwrap();     // Modifier with alias
29//!
30//! // Unified code conversion
31//! let code1 = input1.to_code(Platform::Windows);
32//! let code2 = input2.to_code(Platform::Windows);
33//! ```
34//!
35//! # Features
36//!
37//! - `serde`: Enables serialization/deserialization support
38//! - `phf`: Uses perfect hash functions for faster lookups
39
40#![deny(missing_docs)]
41#![warn(clippy::all)]
42
43/// Error types for keyboard parsing and mapping
44pub mod error;
45/// Key code mapping implementations
46pub mod mapping;
47/// Advanced keyboard input parsing with alias support
48pub mod parser;
49/// Core type definitions for keyboard keys and platforms
50pub mod types;
51/// Utility functions and helpers
52pub mod utils;
53
54// Re-export main types for convenient access
55pub use error::KeyParseError;
56pub use mapping::custom::{CustomKey, CustomKeyMap};
57pub use types::{Key, KeyCodeMapper, KeyboardInput, Modifier, Platform};
58
59// Re-export core parsing functions
60pub use mapping::standard::{parse_key_ignore_case, parse_modifier_ignore_case};
61
62// Re-export advanced parser functionality
63pub use parser::{
64    parse_input, parse_keyboard_input, parse_modifier_with_aliases, parse_shortcut_flexible,
65    parse_shortcut_sequence, parse_shortcut_with_aliases, Shortcut,
66};
67
68use std::str::FromStr;
69
70// Implement FromStr for Key using the standard mappings
71impl FromStr for Key {
72    type Err = KeyParseError;
73
74    fn from_str(s: &str) -> Result<Self, Self::Err> {
75        mapping::standard::parse_key_from_str(s)
76    }
77}
78
79// Implement FromStr for Modifier using the standard mappings
80impl FromStr for Modifier {
81    type Err = KeyParseError;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        mapping::standard::parse_modifier_from_str(s)
85    }
86}
87
88/// Get the current platform based on compilation target
89pub fn current_platform() -> Platform {
90    #[cfg(target_os = "windows")]
91    return Platform::Windows;
92
93    #[cfg(target_os = "linux")]
94    return Platform::Linux;
95
96    #[cfg(target_os = "macos")]
97    return Platform::MacOS;
98
99    #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
100    panic!("Unsupported platform");
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_key_from_str() {
109        assert_eq!("Escape".parse::<Key>().unwrap(), Key::Escape);
110        assert_eq!("A".parse::<Key>().unwrap(), Key::A);
111        assert!("UnknownKey".parse::<Key>().is_err());
112    }
113
114    #[test]
115    fn test_modifier_from_str() {
116        assert_eq!("Shift".parse::<Modifier>().unwrap(), Modifier::Shift);
117        assert_eq!("Control".parse::<Modifier>().unwrap(), Modifier::Control);
118        assert!("UnknownModifier".parse::<Modifier>().is_err());
119    }
120
121    #[test]
122    fn test_keyboard_input_unified() {
123        // Test unified parsing
124        let input1: KeyboardInput = "A".parse().unwrap();
125        let input2: KeyboardInput = "Control".parse().unwrap();
126        let input3: KeyboardInput = "ctrl".parse().unwrap();
127        let input4: KeyboardInput = "esc".parse().unwrap();
128
129        assert_eq!(input1, KeyboardInput::Key(Key::A));
130        assert_eq!(input2, KeyboardInput::Modifier(Modifier::Control));
131        assert_eq!(input3, KeyboardInput::Modifier(Modifier::Control));
132        assert_eq!(input4, KeyboardInput::Key(Key::Escape));
133
134        // Test unified code conversion
135        assert_eq!(input1.to_code(Platform::Windows), 0x41);
136        assert_eq!(input2.to_code(Platform::Windows), 0x11);
137
138        // Test from_code
139        assert_eq!(
140            KeyboardInput::from_code(0x41, Platform::Windows),
141            Some(KeyboardInput::Key(Key::A))
142        );
143        assert_eq!(
144            KeyboardInput::from_code(0x11, Platform::Windows),
145            Some(KeyboardInput::Modifier(Modifier::Control))
146        );
147
148        // Test parser function
149        assert_eq!(
150            parse_keyboard_input("A").unwrap(),
151            KeyboardInput::Key(Key::A)
152        );
153        assert_eq!(
154            parse_keyboard_input("ctrl").unwrap(),
155            KeyboardInput::Modifier(Modifier::Control)
156        );
157    }
158
159    #[test]
160    fn test_current_platform() {
161        let platform = current_platform();
162        assert!(matches!(
163            platform,
164            Platform::Windows | Platform::Linux | Platform::MacOS
165        ));
166    }
167
168    #[test]
169    fn test_parse_modifier_with_aliases() {
170        assert_eq!(
171            parse_modifier_with_aliases("ctrl").unwrap(),
172            Modifier::Control
173        );
174        assert_eq!(parse_modifier_with_aliases("Cmd").unwrap(), Modifier::Meta);
175        assert_eq!(parse_modifier_with_aliases("win").unwrap(), Modifier::Meta);
176        assert_eq!(
177            parse_modifier_with_aliases("lctrl").unwrap(),
178            Modifier::LeftControl
179        );
180    }
181
182    #[test]
183    fn test_parse_shortcut_with_aliases() {
184        let shortcut = parse_shortcut_with_aliases("ctrl+shift+a").unwrap();
185        assert_eq!(shortcut.modifiers, vec![Modifier::Control, Modifier::Shift]);
186        assert_eq!(shortcut.key, Key::A);
187
188        let shortcut = parse_shortcut_with_aliases("cmd+q").unwrap();
189        assert_eq!(shortcut.modifiers, vec![Modifier::Meta]);
190        assert_eq!(shortcut.key, Key::Q);
191    }
192
193    #[test]
194    fn test_parse_input() {
195        let shortcut = parse_input("a").unwrap();
196        assert!(shortcut.is_simple());
197        assert_eq!(shortcut.key, Key::A);
198
199        let shortcut = parse_input("ctrl+a").unwrap();
200        assert_eq!(shortcut.modifiers, vec![Modifier::Control]);
201        assert_eq!(shortcut.key, Key::A);
202    }
203}