From 1fc714a4fd6d5b507f9d269409957c77b19fc8e3 Mon Sep 17 00:00:00 2001 From: Brady Fomegne Date: Tue, 17 Oct 2023 21:10:55 +0100 Subject: [PATCH 1/2] rebase --- .github/workflows/coverage.yml | 4 +- Cargo.lock | 115 +++-- Cargo.toml | 7 +- clafrica-lib/src/lib.rs | 342 --------------- clafrica/Cargo.toml | 24 -- clafrica/src/processor.rs | 127 ------ {clafrica-lib => config}/Cargo.toml | 12 +- {clafrica => config}/data/bad_script.toml | 0 {clafrica => config}/data/bad_script2.toml | 0 {clafrica => config}/data/blank_sample.toml | 0 {clafrica => config}/data/config_sample.toml | 0 {clafrica => config}/data/data_sample.toml | 0 {clafrica => config}/data/data_sample2.toml | 0 {clafrica => config}/data/dictionary.toml | 0 {clafrica => config}/data/invalid_data.toml | 0 {clafrica => config}/data/invalid_file.toml | 0 .../data/invalid_translator.toml | 0 .../data/scripts/datetime.toml | 0 .../data/scripts/datetime/core.rhai | 0 .../data/scripts/datetime/date.rhai | 0 .../data/scripts/datetime/time.rhai | 0 .../data/scripts/invalid.rhai | 0 clafrica/src/config.rs => config/src/lib.rs | 16 +- engine/preprocessor/Cargo.toml | 16 + engine/preprocessor/data/sample.txt | 10 + engine/preprocessor/src/lib.rs | 395 ++++++++++++++++++ engine/preprocessor/src/message.rs | 11 + engine/translator/Cargo.toml | 15 + .../translator/src/lib.rs | 61 +++ memory/Cargo.toml | 14 + {clafrica-lib => memory}/data/sample.txt | 0 memory/src/lib.rs | 293 +++++++++++++ memory/src/utils.rs | 57 +++ service/Cargo.toml | 28 ++ {clafrica => service}/data/scripts/hi.rhai | 0 {clafrica => service}/data/test.toml | 0 service/src/convert.rs | 50 +++ .../src/api.rs => service/src/frontend.rs | 4 +- {clafrica => service}/src/lib.rs | 67 +-- {clafrica => service}/src/main.rs | 4 +- 40 files changed, 1098 insertions(+), 574 deletions(-) delete mode 100644 clafrica-lib/src/lib.rs delete mode 100644 clafrica/Cargo.toml delete mode 100644 clafrica/src/processor.rs rename {clafrica-lib => config}/Cargo.toml (51%) rename {clafrica => config}/data/bad_script.toml (100%) rename {clafrica => config}/data/bad_script2.toml (100%) rename {clafrica => config}/data/blank_sample.toml (100%) rename {clafrica => config}/data/config_sample.toml (100%) rename {clafrica => config}/data/data_sample.toml (100%) rename {clafrica => config}/data/data_sample2.toml (100%) rename {clafrica => config}/data/dictionary.toml (100%) rename {clafrica => config}/data/invalid_data.toml (100%) rename {clafrica => config}/data/invalid_file.toml (100%) rename {clafrica => config}/data/invalid_translator.toml (100%) rename {clafrica => config}/data/scripts/datetime.toml (100%) rename {clafrica => config}/data/scripts/datetime/core.rhai (100%) rename {clafrica => config}/data/scripts/datetime/date.rhai (100%) rename {clafrica => config}/data/scripts/datetime/time.rhai (100%) rename {clafrica => config}/data/scripts/invalid.rhai (100%) rename clafrica/src/config.rs => config/src/lib.rs (95%) create mode 100644 engine/preprocessor/Cargo.toml create mode 100644 engine/preprocessor/data/sample.txt create mode 100644 engine/preprocessor/src/lib.rs create mode 100644 engine/preprocessor/src/message.rs create mode 100644 engine/translator/Cargo.toml rename clafrica/src/translator.rs => engine/translator/src/lib.rs (58%) create mode 100644 memory/Cargo.toml rename {clafrica-lib => memory}/data/sample.txt (100%) create mode 100644 memory/src/lib.rs create mode 100644 memory/src/utils.rs create mode 100644 service/Cargo.toml rename {clafrica => service}/data/scripts/hi.rhai (100%) rename {clafrica => service}/data/test.toml (100%) create mode 100644 service/src/convert.rs rename clafrica/src/api.rs => service/src/frontend.rs (97%) rename {clafrica => service}/src/lib.rs (83%) rename {clafrica => service}/src/main.rs (88%) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8c8a6c8..800aee6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -50,7 +50,7 @@ jobs: - name: test env: RUSTFLAGS: -Cinstrument-coverage - LLVM_PROFILE_FILE: clafrica-%p-%m.profraw + LLVM_PROFILE_FILE: name-%p-%m.profraw run: cargo test --all-features --verbose - name: Run grcov @@ -63,7 +63,7 @@ jobs: --branch \ --ignore-not-existing \ --ignore "/*" \ - --ignore "clafrica/src/{main}.rs" \ + --ignore "service/src/{main}.rs" \ --excl-line "#\\[derive\\(" \ -o ./coverage/lcov.info diff --git a/Cargo.lock b/Cargo.lock index 8d0e66d..6a604dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -95,21 +98,44 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clafrica" -version = "0.4.0" +version = "0.5.0" dependencies = [ - "clafrica-lib", + "clafrica-config", + "clafrica-preprocessor", + "clafrica-translator", "clap", "enigo", "rdev", - "rhai", "rstk", +] + +[[package]] +name = "clafrica-config" +version = "0.4.1" +dependencies = [ + "rhai", "serde", "toml", ] [[package]] -name = "clafrica-lib" -version = "0.3.1" +name = "clafrica-memory" +version = "0.3.2" + +[[package]] +name = "clafrica-preprocessor" +version = "0.5.0" +dependencies = [ + "clafrica-memory", + "keyboard-types", +] + +[[package]] +name = "clafrica-translator" +version = "0.0.1" +dependencies = [ + "rhai", +] [[package]] name = "clap" @@ -354,9 +380,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "heck" @@ -366,9 +392,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", "hashbrown", @@ -383,6 +409,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.4.1", + "serde", + "unicode-segmentation", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -391,9 +428,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "malloc_buf" @@ -406,15 +443,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -448,9 +485,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -482,12 +519,12 @@ dependencies = [ [[package]] name = "rhai" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637a4f79f65571b1fd1a0ebbae05bbbf58a01faf612abbc3eea15cda34f0b87a" +checksum = "206cee941730eaf90a22c84235b25193df661393688162e15551164f92f09eca" dependencies = [ "ahash", - "bitflags 2.4.0", + "bitflags 2.4.1", "instant", "num-traits", "once_cell", @@ -518,18 +555,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", @@ -547,9 +584,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smartstring" @@ -576,9 +613,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.33" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -596,9 +633,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", @@ -617,9 +654,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", "serde", @@ -634,6 +671,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "utf8parse" version = "0.2.1" @@ -761,9 +804,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index b99f77e..d15f054 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,10 @@ [workspace] members = [ - "clafrica-lib", - "clafrica" + "service", + "config", + "memory", + "engine/preprocessor", + "engine/translator", ] resolver = "2" diff --git a/clafrica-lib/src/lib.rs b/clafrica-lib/src/lib.rs deleted file mode 100644 index 3974cf0..0000000 --- a/clafrica-lib/src/lib.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! # Clafrica Lib -//! -//! `clafrica-lib` is a collection of utilities to make handling -//! of clafrica code more convenient. -//! -//! Example -//! ``` -//! use clafrica_lib::{text_buffer, utils}; -//! -//! // Build a TextBuffer -//! let root = text_buffer::Node::default(); -//! root.insert(vec!['a', 'f'], "ɑ".to_owned()); -//! root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned()); -//! -//! // Bulk insert of data in the TextBuffer -//! let data = vec![["af11", "ɑ̀ɑ̀"], ["?.", "ʔ"]]; -//! utils::build_map(data); -//! -//! // or directly from a file -//! let data = utils::load_data("data/sample.txt") -//! .expect("Failed to load the clafrica code file"); -//! let data = data.iter() -//! .map(|e| [e[0].as_str(), e[1].as_str()]) -//! .collect(); -//! -//! utils::build_map(data); -//! -//! // Traverse the tree -//! let node = root.goto('a').and_then(|e| e.goto('f')); -//! assert_eq!(node.unwrap().take(), Some("ɑ".to_owned())); -//! -//! // Test our cursor -//! let mut cursor = text_buffer::Cursor::new(root, 10); -//! let code = "af1"; -//! code.chars().for_each(|c| {cursor.hit(c);}); -//! assert_eq!(cursor.state(), (Some("ɑ̀".to_owned()), 3, '1')); -//! assert_eq!(cursor.undo(), Some("ɑ̀".to_owned())); -//! ``` - -pub mod text_buffer { - use std::collections::{HashMap, VecDeque}; - use std::{cell::RefCell, rc::Rc}; - - #[derive(Debug)] - pub struct Node { - neighbors: RefCell>>, - pub depth: usize, - pub key: char, - value: RefCell>, - } - - impl Default for Node { - fn default() -> Self { - Self::new('\0', 0) - } - } - - impl Node { - /// Initialize a new node. - pub fn new(key: char, depth: usize) -> Self { - Self { - neighbors: HashMap::new().into(), - depth, - key, - value: None.into(), - } - } - - /// Insert a sequence in the TextBuffer. - pub fn insert(&self, sequence: Vec, value: String) { - if let Some(character) = sequence.clone().first() { - let new_node = Rc::new(Self::new(*character, self.depth + 1)); - - self.neighbors - .borrow() - .get(character) - .unwrap_or(&new_node) - .insert(sequence.into_iter().skip(1).collect(), value); - - self.neighbors - .borrow_mut() - .entry(*character) - .or_insert(new_node); - } else { - *self.value.borrow_mut() = Some(value); - }; - } - - /// Move from one node to another - pub fn goto(&self, character: char) -> Option> { - self.neighbors.borrow().get(&character).map(Rc::clone) - } - - /// Extract the value from a node . - pub fn take(&self) -> Option { - self.value.borrow().as_ref().map(ToOwned::to_owned) - } - - /// Return true is the node is at the initial depth - pub fn is_root(&self) -> bool { - self.depth == 0 - } - } - - #[derive(Clone)] - pub struct Cursor { - buffer: VecDeque>, - root: Rc, - } - - impl Cursor { - /// Initialize the cursor - pub fn new(root: Node, capacity: usize) -> Self { - Self { - buffer: VecDeque::with_capacity(capacity), - root: Rc::new(root), - } - } - /// Enter a character and return his corresponding out - pub fn hit(&mut self, character: char) -> Option { - let node = self - .buffer - .iter() - .last() - .unwrap_or(&Rc::new(Node::new('\0', 0))) - .goto(character) - .or_else(|| { - // We end the current sequence - self.insert(Rc::new(Node::new('\0', 0))); - // and start a new one - self.root.goto(character) - }) - .unwrap_or(Rc::new(Node::new(character, 0))); - - let out = node.take(); - self.insert(node); - - out - } - - fn insert(&mut self, node: Rc) { - if self.buffer.len() == self.buffer.capacity() { - self.buffer.pop_front(); - } - self.buffer.push_back(node); - } - - /// Remove the previous enter and return his corresponding out - pub fn undo(&mut self) -> Option { - let node = self.buffer.pop_back(); - - node.and_then(|node| { - if node.key == '\0' { - self.undo() - } else { - node.take() - } - }) - } - - /// Return the current state of the cursor - pub fn state(&self) -> (Option, usize, char) { - self.buffer - .iter() - .last() - .map(|n| (n.take(), n.depth, n.key)) - .unwrap_or_default() - } - - /// Return the current sequence in the cursor - pub fn to_sequence(&self) -> Vec { - self.buffer.iter().map(|node| node.key).collect() - } - - /// Clear the memory of the cursor - pub fn clear(&mut self) { - self.buffer.clear(); - } - - /// Verify if the cursor is empty - pub fn is_empty(&self) -> bool { - return self.buffer.iter().filter(|c| c.key != '\0').count() == 0; - } - } -} - -pub mod utils { - use crate::text_buffer; - use std::{fs, io}; - - /// Load the clafrica code from a plain text file. - pub fn load_data(file_path: &str) -> Result>, io::Error> { - let data = fs::read_to_string(file_path)?; - let data = data - .trim() - .split('\n') - .map(|line| { - line.split_whitespace() - .filter(|token| !token.is_empty()) - .map(ToOwned::to_owned) - .collect() - }) - .collect(); - Ok(data) - } - - /// Build a TextBuffer from the clafrica code. - pub fn build_map(data: Vec<[&str; 2]>) -> text_buffer::Node { - let root = text_buffer::Node::default(); - - data.iter().for_each(|e| { - root.insert(e[0].chars().collect(), e[1].to_owned()); - }); - - root - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_load_data() { - use crate::utils; - - utils::load_data("data/sample.txt") - .unwrap() - .iter() - .for_each(|pair| assert_eq!(pair.len(), 2)); - } - - #[test] - fn test_build_map() { - use crate::utils; - - let data = vec![["af11", "ɑ̀ɑ̀"], ["?.", "ʔ"]]; - utils::build_map(data); - - let data = utils::load_data("data/sample.txt").unwrap(); - utils::build_map( - data.iter() - .map(|e| [e[0].as_str(), e[1].as_str()]) - .collect(), - ); - } - - #[test] - fn test_node() { - use crate::text_buffer; - - let root = text_buffer::Node::default(); - - assert!(root.is_root()); - - root.insert(vec!['a', 'f'], "ɑ".to_owned()); - root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned()); - - assert!(root.goto('a').is_some()); - assert!(!root.goto('a').unwrap().is_root()); - assert!(root.goto('b').is_none()); - - let node = root.goto('a').and_then(|e| e.goto('f')); - assert_eq!(node.as_ref().unwrap().take(), Some("ɑ".to_owned())); - - let node = node.and_then(|e| e.goto('1')); - assert_eq!(node.as_ref().unwrap().take(), Some("ɑ̀".to_owned())); - } - - #[test] - fn test_cursor() { - use crate::text_buffer; - use crate::utils; - - macro_rules! hit { - ( $cursor:ident $( $c:expr ),* ) => ( - $( $cursor.hit($c); )* - ); - } - - macro_rules! undo { - ( $cursor:ident $occ:expr ) => { - (0..$occ).into_iter().for_each(|_| { - $cursor.undo(); - }); - }; - } - - let data = utils::load_data("data/sample.txt").unwrap(); - let root = utils::build_map( - data.iter() - .map(|e| [e[0].as_str(), e[1].as_str()]) - .collect(), - ); - - let mut cursor = text_buffer::Cursor::new(root, 8); - - assert_eq!(cursor.state(), (None, 0, '\0')); - - hit!(cursor '2', 'i', 'a', 'f'); - assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a', 'f']); - - assert_eq!(cursor.state(), (Some("íɑ́".to_owned()), 4, 'f')); - - undo!(cursor 1); - assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a']); - - undo!(cursor 1); - cursor.hit('e'); - assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'e']); - - undo!(cursor 2); - hit!(cursor 'o', 'o'); - assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'o', 'o']); - - undo!(cursor 3); - assert_eq!(cursor.to_sequence(), vec!['\0']); - - hit!(cursor '2', '2', 'u', 'a'); - assert_eq!( - cursor.to_sequence(), - vec!['\0', '\0', '2', '\0', '2', 'u', 'a'] - ); - undo!(cursor 4); - assert_eq!(cursor.to_sequence(), vec!['\0', '\0']); - assert!(cursor.is_empty()); - undo!(cursor 1); - assert_eq!(cursor.to_sequence(), vec![]); - - hit!( - cursor - 'a', 'a', '2', 'a', 'e', 'a', '2', 'f', 'a', - '2', '2', 'x', 'x', '2', 'i', 'a', '2', '2', '_', 'f', - '2', 'a', '2', 'a', '_' - ); - assert_eq!( - cursor.to_sequence(), - vec!['f', '\0', '2', 'a', '\0', '2', 'a', '_'] - ); - - cursor.clear(); - assert_eq!(cursor.to_sequence(), vec![]); - } -} diff --git a/clafrica/Cargo.toml b/clafrica/Cargo.toml deleted file mode 100644 index 92ace19..0000000 --- a/clafrica/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "clafrica" -version = "0.4.0" -edition = "2021" -description = "This application allows you to type most of the characters in the african alphabet in any text field." -keywords = ["african", "ime", "typing"] -repository = "https://github.com/pythonbrad/clafrica" -license = "MIT" -readme = "../README.md" -authors = ["Brady Fomegne "] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -clafrica-lib = { version = "0.3.0", path = "../clafrica-lib" } -clap = { version = "4.4.6", features = ["derive"] } -enigo = "0.1.2" -rdev = "0.5.2" -rhai = "1.15.1" -serde = { version = "1.0.163", features = ["serde_derive"] } -toml = "0.7.3" - -[dev-dependencies] -rstk = "0.1.0" diff --git a/clafrica/src/processor.rs b/clafrica/src/processor.rs deleted file mode 100644 index ff6ff68..0000000 --- a/clafrica/src/processor.rs +++ /dev/null @@ -1,127 +0,0 @@ -use clafrica_lib::text_buffer::{Cursor, Node}; -use enigo::{Enigo, Key, KeyboardControllable}; -use rdev::{self, Event, EventType, Key as E_Key}; - -pub struct Processor { - keyboard: Enigo, - cursor: Cursor, -} - -impl Processor { - pub fn new(map: Node, buffer_size: usize) -> Self { - let cursor = Cursor::new(map, buffer_size); - - Self { - keyboard: Enigo::new(), - cursor, - } - } - - fn rollback(&mut self) -> bool { - self.keyboard.key_up(Key::Backspace); - - if let Some(out) = self.cursor.undo() { - (1..out.chars().count()).for_each(|_| self.keyboard.key_click(Key::Backspace)); - - // Clear the remaining code - while let (None, 1.., ..) = self.cursor.state() { - self.cursor.undo(); - } - - if let (Some(_in), ..) = self.cursor.state() { - self.keyboard.key_sequence(&_in); - } - - true - } else { - false - } - } - - pub fn process(&mut self, event: Event) -> (bool, bool) { - let character = event.name.and_then(|s| s.chars().next()); - let is_valid = character - .map(|c| c.is_alphanumeric() || c.is_ascii_punctuation()) - .unwrap_or_default(); - let (mut changed, mut committed) = (false, false); - - match event.event_type { - EventType::KeyPress(E_Key::Backspace) => { - self.pause(); - committed = self.rollback(); - self.resume(); - changed = true; - } - EventType::KeyPress( - E_Key::Unknown(_) | E_Key::ShiftLeft | E_Key::ShiftRight | E_Key::CapsLock, - ) => { - // println!("[ignore] {:?}", event.event_type) - } - EventType::ButtonPress(_) | EventType::KeyPress(_) if !is_valid => { - self.cursor.clear(); - changed = true; - } - EventType::KeyPress(_) => { - let character = character.unwrap(); - - if let Some(_in) = self.cursor.hit(character) { - self.pause(); - - let mut prev_cursor = self.cursor.clone(); - prev_cursor.undo(); - self.keyboard.key_click(Key::Backspace); - - // Remove the remaining code - while let (None, 1.., ..) = prev_cursor.state() { - prev_cursor.undo(); - self.keyboard.key_click(Key::Backspace); - } - - if let (Some(out), ..) = prev_cursor.state() { - (0..out.chars().count()) - .for_each(|_| self.keyboard.key_click(Key::Backspace)) - } - - self.keyboard.key_sequence(&_in); - self.resume(); - committed = true; - }; - - changed = true; - } - _ => (), - }; - - (changed, committed) - } - - pub fn commit(&mut self, text: &str) { - self.pause(); - while !self.cursor.is_empty() { - self.keyboard.key_down(Key::Backspace); - self.rollback(); - } - self.keyboard.key_sequence(text); - self.resume(); - // We clear the buffer - self.cursor.clear(); - } - - fn pause(&mut self) { - rdev::simulate(&EventType::KeyPress(E_Key::Pause)) - .expect("We couldn't pause the listeners"); - } - - fn resume(&mut self) { - rdev::simulate(&EventType::KeyRelease(E_Key::Pause)) - .expect("We couldn't resume the listeners"); - } - - pub fn get_input(&self) -> String { - self.cursor - .to_sequence() - .into_iter() - .filter(|c| *c != '\0') - .collect::() - } -} diff --git a/clafrica-lib/Cargo.toml b/config/Cargo.toml similarity index 51% rename from clafrica-lib/Cargo.toml rename to config/Cargo.toml index 4f81cef..80af4f1 100644 --- a/clafrica-lib/Cargo.toml +++ b/config/Cargo.toml @@ -1,13 +1,17 @@ [package] -name = "clafrica-lib" -version = "0.3.1" +name = "clafrica-config" +version = "0.4.1" edition = "2021" -description = "This library contains a set of tools to make handling of clafrica code more convenient." -keywords = ["ime", "typing"] +description = "Handle the configuration of the clafrica input method." +keywords = ["ime", "config"] repository = "https://github.com/pythonbrad/clafrica" license = "MIT" +readme = "README.md" authors = ["Brady Fomegne "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rhai = "1.16.2" +serde = { version = "1.0.188", features = ["derive"] } +toml = "0.8.2" diff --git a/clafrica/data/bad_script.toml b/config/data/bad_script.toml similarity index 100% rename from clafrica/data/bad_script.toml rename to config/data/bad_script.toml diff --git a/clafrica/data/bad_script2.toml b/config/data/bad_script2.toml similarity index 100% rename from clafrica/data/bad_script2.toml rename to config/data/bad_script2.toml diff --git a/clafrica/data/blank_sample.toml b/config/data/blank_sample.toml similarity index 100% rename from clafrica/data/blank_sample.toml rename to config/data/blank_sample.toml diff --git a/clafrica/data/config_sample.toml b/config/data/config_sample.toml similarity index 100% rename from clafrica/data/config_sample.toml rename to config/data/config_sample.toml diff --git a/clafrica/data/data_sample.toml b/config/data/data_sample.toml similarity index 100% rename from clafrica/data/data_sample.toml rename to config/data/data_sample.toml diff --git a/clafrica/data/data_sample2.toml b/config/data/data_sample2.toml similarity index 100% rename from clafrica/data/data_sample2.toml rename to config/data/data_sample2.toml diff --git a/clafrica/data/dictionary.toml b/config/data/dictionary.toml similarity index 100% rename from clafrica/data/dictionary.toml rename to config/data/dictionary.toml diff --git a/clafrica/data/invalid_data.toml b/config/data/invalid_data.toml similarity index 100% rename from clafrica/data/invalid_data.toml rename to config/data/invalid_data.toml diff --git a/clafrica/data/invalid_file.toml b/config/data/invalid_file.toml similarity index 100% rename from clafrica/data/invalid_file.toml rename to config/data/invalid_file.toml diff --git a/clafrica/data/invalid_translator.toml b/config/data/invalid_translator.toml similarity index 100% rename from clafrica/data/invalid_translator.toml rename to config/data/invalid_translator.toml diff --git a/clafrica/data/scripts/datetime.toml b/config/data/scripts/datetime.toml similarity index 100% rename from clafrica/data/scripts/datetime.toml rename to config/data/scripts/datetime.toml diff --git a/clafrica/data/scripts/datetime/core.rhai b/config/data/scripts/datetime/core.rhai similarity index 100% rename from clafrica/data/scripts/datetime/core.rhai rename to config/data/scripts/datetime/core.rhai diff --git a/clafrica/data/scripts/datetime/date.rhai b/config/data/scripts/datetime/date.rhai similarity index 100% rename from clafrica/data/scripts/datetime/date.rhai rename to config/data/scripts/datetime/date.rhai diff --git a/clafrica/data/scripts/datetime/time.rhai b/config/data/scripts/datetime/time.rhai similarity index 100% rename from clafrica/data/scripts/datetime/time.rhai rename to config/data/scripts/datetime/time.rhai diff --git a/clafrica/data/scripts/invalid.rhai b/config/data/scripts/invalid.rhai similarity index 100% rename from clafrica/data/scripts/invalid.rhai rename to config/data/scripts/invalid.rhai diff --git a/clafrica/src/config.rs b/config/src/lib.rs similarity index 95% rename from clafrica/src/config.rs rename to config/src/lib.rs index 4f210b5..1b62e39 100644 --- a/clafrica/src/config.rs +++ b/config/src/lib.rs @@ -184,17 +184,17 @@ impl Config { .as_ref() .unwrap_or(&empty) .iter() - .filter_map(|(name, filename)| { - let filename = match filename { - Data::Simple(filename) => Some(filename), + .filter_map(|(name, file_path)| { + let file_path = match file_path { + Data::Simple(file_path) => Some(file_path), _ => None, }; - filename.map(|filename| { - let parent = Path::new(&filename).parent().unwrap().to_str().unwrap(); + file_path.map(|file_path| { + let parent = Path::new(&file_path).parent().unwrap().to_str().unwrap(); let header = format!(r#"const DIR = {parent:?};"#); - let ast = engine.compile_file(filename.into()).map_err(|err| { - format!("Failed to parse script file `{filename}`.\nCaused by:\n\t{err}.") + let ast = engine.compile_file(file_path.into()).map_err(|err| { + format!("Failed to parse script file `{file_path}`.\nCaused by:\n\t{err}.") })?; let ast = engine.compile(header).unwrap().merge(&ast); @@ -226,7 +226,7 @@ impl Config { #[cfg(test)] mod tests { - use crate::config::Config; + use crate::Config; use std::path::Path; #[test] diff --git a/engine/preprocessor/Cargo.toml b/engine/preprocessor/Cargo.toml new file mode 100644 index 0000000..8f3b521 --- /dev/null +++ b/engine/preprocessor/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "clafrica-preprocessor" +version = "0.5.0" +edition = "2021" +description = "A preprocessor for processing key input of an input method." +keywords = ["ime", "processor", "keyboard"] +repository = "https://github.com/pythonbrad/name" +license = "MIT" +readme = "README.md" +authors = ["Brady Fomegne "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +keyboard-types = "0.7.0" +clafrica-memory = { version = "0.3.2", path = "../../memory" } diff --git a/engine/preprocessor/data/sample.txt b/engine/preprocessor/data/sample.txt new file mode 100644 index 0000000..cb6097b --- /dev/null +++ b/engine/preprocessor/data/sample.txt @@ -0,0 +1,10 @@ +af ɑ +u5 û +5u û +aff ɑɑ +aff3 ɑ̄ɑ̄ +c_ ç +c_ced ç +uu ʉ +uu3 ʉ̄ +uuaf3 ʉ̄ɑ̄ diff --git a/engine/preprocessor/src/lib.rs b/engine/preprocessor/src/lib.rs new file mode 100644 index 0000000..2a30164 --- /dev/null +++ b/engine/preprocessor/src/lib.rs @@ -0,0 +1,395 @@ +mod message; + +pub use crate::message::Command; +pub use clafrica_memory::utils; +use clafrica_memory::{Cursor, Node}; +pub use keyboard_types::{Key, KeyState, KeyboardEvent}; +use std::collections::VecDeque; + +#[derive(Debug)] +pub struct Preprocessor { + cursor: Cursor, + stack: VecDeque, +} + +impl Preprocessor { + pub fn new(map: Node, buffer_size: usize) -> Self { + let cursor = Cursor::new(map, buffer_size); + let stack = VecDeque::with_capacity(15); + + Self { cursor, stack } + } + + fn rollback(&mut self) -> bool { + self.stack.push_back(Command::KeyRelease(Key::Backspace)); + + if let Some(out) = self.cursor.undo() { + (1..out.chars().count()) + .for_each(|_| self.stack.push_back(Command::KeyClick(Key::Backspace))); + + // Clear the remaining code + while let (None, 1.., ..) = self.cursor.state() { + self.cursor.undo(); + } + + if let (Some(_in), ..) = self.cursor.state() { + self.stack.push_back(Command::CommitText(_in)); + } + + true + } else { + false + } + } + + pub fn process(&mut self, event: KeyboardEvent) -> (bool, bool) { + let (mut changed, mut committed) = (false, false); + + match (event.state, event.key) { + (KeyState::Down, Key::Backspace) => { + self.pause(); + committed = self.rollback(); + self.resume(); + changed = true; + } + (KeyState::Down, Key::Character(character)) + if character + .chars() + .next() + .map(|e| e.is_alphanumeric() || e.is_ascii_punctuation()) + .unwrap_or(false) => + { + let character = character.chars().next().unwrap(); + + if let Some(_in) = self.cursor.hit(character) { + self.pause(); + + let mut prev_cursor = self.cursor.clone(); + prev_cursor.undo(); + self.stack.push_back(Command::KeyClick(Key::Backspace)); + + // Remove the remaining code + while let (None, 1.., ..) = prev_cursor.state() { + prev_cursor.undo(); + self.stack.push_back(Command::KeyClick(Key::Backspace)); + } + + if let (Some(out), ..) = prev_cursor.state() { + (0..out.chars().count()) + .for_each(|_| self.stack.push_back(Command::KeyClick(Key::Backspace))) + } + + self.stack.push_back(Command::CommitText(_in)); + self.resume(); + committed = true; + }; + + changed = true; + } + (KeyState::Down, Key::Shift | Key::CapsLock) => (), + (KeyState::Down, _) => { + self.cursor.clear(); + changed = true; + } + _ => (), + }; + + (changed, committed) + } + + pub fn commit(&mut self, text: &str) { + self.pause(); + + while !self.cursor.is_empty() { + self.stack.push_back(Command::KeyPress(Key::Backspace)); + self.rollback(); + } + self.stack.push_back(Command::CommitText(text.to_owned())); + self.resume(); + // We clear the buffer + self.cursor.clear(); + } + + fn pause(&mut self) { + self.stack.push_back(Command::Pause); + } + + fn resume(&mut self) { + self.stack.push_back(Command::Resume); + } + + pub fn get_input(&self) -> String { + self.cursor + .to_sequence() + .into_iter() + .filter(|c| *c != '\0') + .collect::() + } + + pub fn pop_stack(&mut self) -> Option { + self.stack.pop_front() + } + + pub fn clear_stack(&mut self) { + self.stack.clear(); + } +} + +#[cfg(test)] +mod tests { + use crate::message::Command; + use crate::utils; + use crate::Preprocessor; + use keyboard_types::{ + webdriver::{self, Event}, + Key::*, + }; + use std::collections::VecDeque; + use std::fs; + + #[test] + fn test_process() { + let data = utils::load_data("ccced ç\ncc ç"); + let map = utils::build_map(data); + let mut preprocessor = Preprocessor::new(map, 8); + webdriver::send_keys("\u{E00C}ccced") + .into_iter() + .for_each(|e| { + match e { + Event::Keyboard(e) => preprocessor.process(e), + _ => unimplemented!(), + }; + }); + let mut expecteds = VecDeque::from(vec![ + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + ]); + + while let Some(command) = preprocessor.pop_stack() { + assert_eq!(command, expecteds.pop_front().unwrap()); + } + } + + #[test] + fn test_commit() { + use clafrica_memory::Node; + use keyboard_types::KeyboardEvent; + + let mut preprocessor = Preprocessor::new(Node::default(), 8); + preprocessor.process(KeyboardEvent { + key: Character("a".to_owned()), + ..Default::default() + }); + preprocessor.commit("word"); + + let mut expecteds = VecDeque::from(vec![ + Command::Pause, + Command::KeyPress(Backspace), + Command::KeyRelease(Backspace), + Command::CommitText("word".to_owned()), + Command::Resume, + ]); + + while let Some(command) = preprocessor.pop_stack() { + assert_eq!(command, expecteds.pop_front().unwrap()); + } + } + + #[test] + fn test_rollback() { + use keyboard_types::KeyboardEvent; + + let data = utils::load_data("ccced ç\ncc ç"); + let map = utils::build_map(data); + let mut preprocessor = Preprocessor::new(map, 8); + let backspace_event = KeyboardEvent { + key: Backspace, + ..Default::default() + }; + + webdriver::send_keys("ccced").into_iter().for_each(|e| { + match e { + Event::Keyboard(e) => preprocessor.process(e), + _ => unimplemented!(), + }; + }); + + preprocessor.clear_stack(); + assert_eq!(preprocessor.get_input(), "ccced".to_owned()); + preprocessor.process(backspace_event.clone()); + assert_eq!(preprocessor.get_input(), "cc".to_owned()); + preprocessor.process(backspace_event); + assert_eq!(preprocessor.get_input(), "".to_owned()); + + let mut expecteds = VecDeque::from(vec![ + Command::Pause, + Command::KeyRelease(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + ]); + + while let Some(command) = preprocessor.pop_stack() { + assert_eq!(command, expecteds.pop_front().unwrap()); + } + } + + #[test] + fn test_advanced() { + let data = fs::read_to_string("./data/sample.txt").unwrap(); + let data = utils::load_data(&data); + let map = utils::build_map(data); + let mut preprocessor = Preprocessor::new(map, 64); + + webdriver::send_keys( + "u\u{E003}uu\u{E003}uc_ceduuaf3afafaff3uu3\ + \u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}\u{E003}" + ).into_iter().for_each(|e| { + match e { + Event::Keyboard(e) => preprocessor.process(e), + _ => unimplemented!(), + }; + }); + + let mut expecteds = VecDeque::from(vec![ + // Process + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ\u{304}ɑ\u{304}".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑ\u{304}ɑ\u{304}".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ\u{304}".to_owned()), + Command::Resume, + // Rollback + Command::Pause, + Command::KeyRelease(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ɑ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::KeyClick(Backspace), + Command::CommitText("ʉ".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::CommitText("ç".to_owned()), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + Command::Pause, + Command::KeyRelease(Backspace), + Command::Resume, + ]); + + while let Some(command) = preprocessor.pop_stack() { + assert_eq!(command, expecteds.pop_front().unwrap()); + } + } +} diff --git a/engine/preprocessor/src/message.rs b/engine/preprocessor/src/message.rs new file mode 100644 index 0000000..d76ea9c --- /dev/null +++ b/engine/preprocessor/src/message.rs @@ -0,0 +1,11 @@ +use keyboard_types::Key; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Command { + CommitText(String), + Pause, + Resume, + KeyPress(Key), + KeyRelease(Key), + KeyClick(Key), +} diff --git a/engine/translator/Cargo.toml b/engine/translator/Cargo.toml new file mode 100644 index 0000000..b1c3dac --- /dev/null +++ b/engine/translator/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "clafrica-translator" +version = "0.0.1" +edition = "2021" +description = "Manage the predication system of the clafrica input method." +keywords = ["auto-complete", "ime", "predication"] +repository = "https://github.com/pythonbrad/name" +license = "MIT" +readme = "README.md" +authors = ["Brady Fomegne "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rhai = "1.16.2" diff --git a/clafrica/src/translator.rs b/engine/translator/src/lib.rs similarity index 58% rename from clafrica/src/translator.rs rename to engine/translator/src/lib.rs index 7d7750a..4be7856 100644 --- a/clafrica/src/translator.rs +++ b/engine/translator/src/lib.rs @@ -68,3 +68,64 @@ impl Translator { .collect() } } + +#[cfg(test)] +mod tests { + #[test] + fn test_translate() { + use crate::Translator; + use rhai::Engine; + use std::collections::HashMap; + + let engine = Engine::new(); + let ast1 = engine.compile("fn translate(input) {}").unwrap(); + let ast2 = engine + .compile( + r#" + fn translate(input) { + if input == "hi" { + ["hi", "", "hello", true] + } + } + "#, + ) + .unwrap(); + let mut translators = HashMap::new(); + translators.insert("none".to_string(), ast1); + translators.insert("some".to_string(), ast2); + + let mut dictionary = HashMap::new(); + dictionary.insert("halo".to_string(), ["hello".to_string()].to_vec()); + + let translator = Translator::new(dictionary, translators, true); + + assert_eq!(translator.translate("h"), vec![]); + assert_eq!( + translator.translate("hi"), + vec![( + "hi".to_owned(), + "".to_owned(), + vec!["hello".to_owned()], + true + )] + ); + assert_eq!( + translator.translate("ha"), + vec![( + "halo".to_owned(), + "lo".to_owned(), + vec!["hello".to_owned()], + false + )] + ); + assert_eq!( + translator.translate("halo"), + vec![( + "halo".to_owned(), + "".to_owned(), + vec!["hello".to_owned()], + true + )] + ); + } +} diff --git a/memory/Cargo.toml b/memory/Cargo.toml new file mode 100644 index 0000000..a930da5 --- /dev/null +++ b/memory/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "clafrica-memory" +version = "0.3.2" +edition = "2021" +description = "This library manage the memory of an input method." +keywords = ["ime", "memory", "data-structure"] +repository = "https://github.com/pythonbrad/name" +license = "MIT" +readme = "README.md" +authors = ["Brady Fomegne "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/clafrica-lib/data/sample.txt b/memory/data/sample.txt similarity index 100% rename from clafrica-lib/data/sample.txt rename to memory/data/sample.txt diff --git a/memory/src/lib.rs b/memory/src/lib.rs new file mode 100644 index 0000000..694bb53 --- /dev/null +++ b/memory/src/lib.rs @@ -0,0 +1,293 @@ +//! # Clafrica Memory +//! +//! `clafrica-memory` is a data structure to make handling +//! of sequential code more convenient. +//! +//! Example +//! ``` +//! use clafrica_memory::*; +//! use std::fs; +//! +//! // Build a TextBuffer +//! let root = Node::default(); +//! root.insert(vec!['a', 'f'], "ɑ".to_owned()); +//! root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned()); +//! +//! // Bulk insert of data in the TextBuffer +//! let data = vec![vec!["af11", "ɑ̀ɑ̀"], vec!["?.", "ʔ"]]; +//! utils::build_map(data); +//! +//! // or directly from a file +//! let data = fs::read_to_string("./data/sample.txt") +//! .expect("Failed to load the clafrica code file"); +//! let data = utils::load_data(&data); +//! +//! utils::build_map(data); +//! +//! // Traverse the tree +//! let node = root.goto('a').and_then(|e| e.goto('f')); +//! assert_eq!(node.unwrap().take(), Some("ɑ".to_owned())); +//! +//! // Test our cursor +//! let mut cursor = Cursor::new(root, 10); +//! let code = "af1"; +//! code.chars().for_each(|c| {cursor.hit(c);}); +//! assert_eq!(cursor.state(), (Some("ɑ̀".to_owned()), 3, '1')); +//! assert_eq!(cursor.undo(), Some("ɑ̀".to_owned())); +//! ``` + +use std::collections::{HashMap, VecDeque}; +use std::{cell::RefCell, fmt, rc::Rc}; +pub mod utils; + +#[derive(Clone, Debug)] +pub struct Node { + children: RefCell>>, + pub depth: usize, + pub key: char, + value: RefCell>, +} + +impl Default for Node { + /// Initialize a root node. + fn default() -> Self { + Self::new('\0', 0) + } +} + +impl Node { + /// Initialize a new node. + pub fn new(key: char, depth: usize) -> Self { + Self { + children: HashMap::new().into(), + depth, + key, + value: None.into(), + } + } + + /// Insert the sequence in the memory. + pub fn insert(&self, sequence: Vec, value: String) { + if let Some(character) = sequence.clone().first() { + let new_node = Rc::new(Self::new(*character, self.depth + 1)); + + self.children + .borrow() + .get(character) + .unwrap_or(&new_node) + .insert(sequence.into_iter().skip(1).collect(), value); + + self.children + .borrow_mut() + .entry(*character) + .or_insert(new_node); + } else { + *self.value.borrow_mut() = Some(value); + }; + } + + /// Move from the node to his child. + pub fn goto(&self, character: char) -> Option> { + self.children.borrow().get(&character).map(Rc::clone) + } + + /// Extract the value from the node. + pub fn take(&self) -> Option { + self.value.borrow().as_ref().map(ToOwned::to_owned) + } + + /// Return true is the node is at the initial depth. + pub fn is_root(&self) -> bool { + self.depth == 0 + } +} + +#[derive(Clone)] +pub struct Cursor { + buffer: VecDeque>, + root: Rc, +} + +impl fmt::Debug for Cursor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.to_sequence().fmt(f) + } +} + +impl Cursor { + /// Initialize the cursor + pub fn new(root: Node, capacity: usize) -> Self { + Self { + buffer: VecDeque::with_capacity(capacity), + root: Rc::new(root), + } + } + + /// Enter a character in the sequence and return his corresponding out + pub fn hit(&mut self, character: char) -> Option { + let node = self + .buffer + .iter() + .last() + .unwrap_or(&Rc::new(Node::default())) + .goto(character) + .or_else(|| { + // We end the current sequence + self.insert(Rc::new(Node::default())); + // and start a new one + self.root.goto(character) + }) + .unwrap_or(Rc::new(Node::new(character, 0))); + + let out = node.take(); + self.insert(node); + + out + } + + fn insert(&mut self, node: Rc) { + if self.buffer.len() == self.buffer.capacity() { + self.buffer.pop_front(); + } + self.buffer.push_back(node); + } + + /// Remove the last node and return his corresponding out + pub fn undo(&mut self) -> Option { + let node = self.buffer.pop_back(); + + node.and_then(|node| { + if node.key == '\0' { + self.undo() + } else { + node.take() + } + }) + } + + /// Return the current state of the cursor + pub fn state(&self) -> (Option, usize, char) { + self.buffer + .iter() + .last() + .map(|n| (n.take(), n.depth, n.key)) + .unwrap_or_default() + } + + /// Return the current sequence in the cursor + pub fn to_sequence(&self) -> Vec { + self.buffer.iter().map(|node| node.key).collect() + } + + /// Clear the memory of the cursor + pub fn clear(&mut self) { + self.buffer.clear(); + } + + /// Verify if the cursor is empty + pub fn is_empty(&self) -> bool { + return self.buffer.iter().filter(|c| c.key != '\0').count() == 0; + } +} + +#[cfg(test)] +mod tests { + use std::fs; + + #[test] + fn test_node() { + use crate::Node; + + let root = Node::default(); + + assert!(root.is_root()); + + root.insert(vec!['a', 'f'], "ɑ".to_owned()); + root.insert(vec!['a', 'f', '1'], "ɑ̀".to_owned()); + + assert!(root.goto('a').is_some()); + assert!(!root.goto('a').unwrap().is_root()); + assert!(root.goto('b').is_none()); + + let node = root.goto('a').and_then(|e| e.goto('f')); + assert_eq!(node.as_ref().unwrap().take(), Some("ɑ".to_owned())); + + let node = node.and_then(|e| e.goto('1')); + assert_eq!(node.as_ref().unwrap().take(), Some("ɑ̀".to_owned())); + } + + #[test] + fn test_cursor() { + use crate::*; + + macro_rules! hit { + ( $cursor:ident $( $c:expr ),* ) => ( + $( $cursor.hit($c); )* + ); + } + + macro_rules! undo { + ( $cursor:ident $occ:expr ) => { + (0..$occ).into_iter().for_each(|_| { + $cursor.undo(); + }); + }; + } + + let data = fs::read_to_string("./data/sample.txt").unwrap(); + let root = utils::build_map(utils::load_data(&data)); + + let mut cursor = Cursor::new(root, 8); + + assert_eq!(cursor.state(), (None, 0, '\0')); + + hit!(cursor '2', 'i', 'a', 'f'); + assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a', 'f']); + + assert_eq!(cursor.state(), (Some("íɑ́".to_owned()), 4, 'f')); + + undo!(cursor 1); + assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'a']); + + undo!(cursor 1); + cursor.hit('e'); + assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'i', 'e']); + + undo!(cursor 2); + hit!(cursor 'o', 'o'); + assert_eq!(cursor.to_sequence(), vec!['\0', '2', 'o', 'o']); + + undo!(cursor 3); + assert_eq!(cursor.to_sequence(), vec!['\0']); + + hit!(cursor '2', '2', 'u', 'a'); + assert_eq!( + cursor.to_sequence(), + vec!['\0', '\0', '2', '\0', '2', 'u', 'a'] + ); + undo!(cursor 4); + assert_eq!(cursor.to_sequence(), vec!['\0', '\0']); + assert!(cursor.is_empty()); + undo!(cursor 1); + assert_eq!(cursor.to_sequence(), vec![]); + + hit!( + cursor + 'a', 'a', '2', 'a', 'e', 'a', '2', 'f', 'a', + '2', '2', 'x', 'x', '2', 'i', 'a', '2', '2', '_', 'f', + '2', 'a', '2', 'a', '_' + ); + assert_eq!( + cursor.to_sequence(), + vec!['f', '\0', '2', 'a', '\0', '2', 'a', '_'] + ); + + assert_eq!( + format!("{:?}", cursor), + format!("{:?}", cursor.to_sequence()) + ); + + cursor.clear(); + assert_eq!(cursor.to_sequence(), vec![]); + } +} diff --git a/memory/src/utils.rs b/memory/src/utils.rs new file mode 100644 index 0000000..4052143 --- /dev/null +++ b/memory/src/utils.rs @@ -0,0 +1,57 @@ +use crate::Node; + +/// Load the sequential code from a plain text. +pub fn load_data(data: &str) -> Vec> { + let data = data + .trim() + .split('\n') + .map(|line| { + line.split_whitespace() + .filter(|token| !token.is_empty()) + .take(2) + .collect() + }) + .collect(); + + data +} + +/// Build a map from the sequential code. +pub fn build_map(data: Vec>) -> Node { + let root = Node::default(); + + data.iter().for_each(|e| { + root.insert(e[0].chars().collect(), e[1].to_owned()); + }); + + root +} + +#[cfg(test)] +mod tests { + use std::fs; + + #[test] + fn test_load_data() { + use crate::utils; + + let data = fs::read_to_string("./data/sample.txt").unwrap(); + + utils::load_data(&data) + .iter() + .for_each(|pair| assert_eq!(pair.len(), 2)); + } + + #[test] + fn test_build_map() { + use crate::utils; + + let data = vec![vec!["af11", "ɑ̀ɑ̀"], vec!["?.", "ʔ"]]; + utils::build_map(data); + + let data = fs::read_to_string("./data/sample.txt").unwrap(); + let data = utils::load_data(&data); + + utils::build_map(data); + } +} diff --git a/service/Cargo.toml b/service/Cargo.toml new file mode 100644 index 0000000..408f6a1 --- /dev/null +++ b/service/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "clafrica" +version = "0.5.0" +edition = "2021" +description = "Core library of the clafrica input method." +keywords = ["ime", "keyboard"] +repository = "https://github.com/pythonbrad/name" +license = "MIT" +readme = "README.md" +authors = ["Brady Fomegne "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +doc = false +name = "name" +path = "./src/main.rs" + +[dependencies] +clap = { version = "4.4.6", features = ["derive"] } +enigo = "0.1.3" +clafrica-config = { version = "0.4.1", path = "../config" } +clafrica-preprocessor = { version = "0.5.0", path = "../engine/preprocessor" } +clafrica-translator = { version = "0.0.1", path = "../engine/translator" } +rdev = "0.5.3" + +[dev-dependencies] +rstk = "0.1.0" diff --git a/clafrica/data/scripts/hi.rhai b/service/data/scripts/hi.rhai similarity index 100% rename from clafrica/data/scripts/hi.rhai rename to service/data/scripts/hi.rhai diff --git a/clafrica/data/test.toml b/service/data/test.toml similarity index 100% rename from clafrica/data/test.toml rename to service/data/test.toml diff --git a/service/src/convert.rs b/service/src/convert.rs new file mode 100644 index 0000000..b77a33f --- /dev/null +++ b/service/src/convert.rs @@ -0,0 +1,50 @@ +use clafrica_preprocessor::{Key, KeyState, KeyboardEvent}; +use enigo::{self}; +use rdev::{self}; + +/// Converts an rdev::Event into a KeyboardEvent. +pub fn from_event(event: rdev::Event) -> KeyboardEvent { + let key_char = event + .name + .and_then(|c| c.chars().next()) + .filter(|c| c.is_alphanumeric() || c.is_ascii_punctuation()) + .map(|c| Key::Character(c.to_string())); + let (state, key) = match event.event_type { + rdev::EventType::KeyPress(key) => (KeyState::Down, from_key(key)), + rdev::EventType::KeyRelease(key) => (KeyState::Up, from_key(key)), + _ => Default::default(), + }; + + KeyboardEvent { + key: key_char.unwrap_or(key), + state, + ..Default::default() + } +} + +/// Converts an rdev::Key into a Key. +pub fn from_key(key: rdev::Key) -> Key { + match key { + rdev::Key::Alt => Key::Alt, + rdev::Key::AltGr => Key::AltGraph, + rdev::Key::Backspace => Key::Backspace, + rdev::Key::CapsLock => Key::CapsLock, + rdev::Key::ControlLeft => Key::Control, + rdev::Key::ControlRight => Key::Control, + rdev::Key::ShiftLeft => Key::Shift, + rdev::Key::ShiftRight => Key::Shift, + rdev::Key::ScrollLock => Key::ScrollLock, + rdev::Key::Pause => Key::Pause, + rdev::Key::NumLock => Key::NumLock, + rdev::Key::Insert => Key::Insert, + _ => Default::default(), + } +} + +/// Converts a Key into an enigo::Key. +pub fn to_key(key: Key) -> enigo::Key { + match key { + Key::Backspace => enigo::Key::Backspace, + _ => unimplemented!(), + } +} diff --git a/clafrica/src/api.rs b/service/src/frontend.rs similarity index 97% rename from clafrica/src/api.rs rename to service/src/frontend.rs index 2efccd7..55630c8 100644 --- a/clafrica/src/api.rs +++ b/service/src/frontend.rs @@ -103,7 +103,7 @@ impl Frontend for Console { mod tests { #[test] fn test_none() { - use crate::api::{Frontend, None}; + use crate::frontend::{Frontend, None}; let mut none = None; none.set_input("hello"); @@ -121,7 +121,7 @@ mod tests { #[test] fn test_console() { - use crate::api::{Console, Frontend}; + use crate::frontend::{Console, Frontend}; let mut console = Console::default(); console.set_page_size(10); diff --git a/clafrica/src/lib.rs b/service/src/lib.rs similarity index 83% rename from clafrica/src/lib.rs rename to service/src/lib.rs index 85d3627..4b653fe 100644 --- a/clafrica/src/lib.rs +++ b/service/src/lib.rs @@ -1,28 +1,20 @@ -pub mod api; -mod config; -mod processor; -mod translator; - -use crate::api::Frontend; -use crate::processor::Processor; -use crate::translator::Translator; -use clafrica_lib::utils; +mod convert; +pub mod frontend; + +pub use clafrica_config::Config; +use clafrica_preprocessor::{utils, Command, Preprocessor}; +use clafrica_translator::Translator; +use enigo::{Enigo, KeyboardControllable}; +use frontend::Frontend; use rdev::{self, EventType, Key as E_Key}; use std::{error, sync::mpsc, thread}; -pub mod prelude { - pub use crate::config::Config; -} - -pub fn run( - config: config::Config, - mut frontend: impl Frontend, -) -> Result<(), Box> { +pub fn run(config: Config, mut frontend: impl Frontend) -> Result<(), Box> { let map = utils::build_map( config .extract_data() .iter() - .map(|(key, value)| [key.as_str(), value.as_str()]) + .map(|(key, value)| vec![key.as_str(), value.as_str()]) .collect(), ); let (buffer_size, auto_commit, page_size) = config @@ -36,7 +28,8 @@ pub fn run( ) }) .unwrap_or((32, false, 10)); - let mut processor = Processor::new(map, buffer_size); + let mut keyboard = Enigo::new(); + let mut preprocessor = Preprocessor::new(map, buffer_size); let translator = Translator::new( config.extract_translation(), config.extract_translators()?, @@ -103,16 +96,16 @@ pub fn run( is_special_pressed = false; if let Some((_code, _remaining_code, text)) = frontend.get_selected_predicate() { - processor.commit(text); + preprocessor.commit(text); frontend.clear_predicates(); } } _ if is_special_pressed => (), _ => { - let (changed, _committed) = processor.process(event); + let (changed, _committed) = preprocessor.process(convert::from_event(event)); if changed { - let input = processor.get_input(); + let input = preprocessor.get_input(); frontend.clear_predicates(); @@ -120,7 +113,7 @@ pub fn run( |(code, remaining_code, texts, translated)| { texts.iter().for_each(|text| { if auto_commit && *translated { - processor.commit(text); + preprocessor.commit(text); } else if !text.is_empty() { frontend.add_predicate(code, remaining_code, text); } @@ -133,6 +126,30 @@ pub fn run( } } } + + // Process preprocessor instructions + while let Some(command) = preprocessor.pop_stack() { + match command { + Command::CommitText(text) => { + keyboard.key_sequence(&text); + } + Command::KeyPress(key) => { + keyboard.key_down(convert::to_key(key)); + } + Command::KeyRelease(key) => { + keyboard.key_up(convert::to_key(key)); + } + Command::KeyClick(key) => { + keyboard.key_click(convert::to_key(key)); + } + Command::Pause => { + rdev::simulate(&EventType::KeyPress(E_Key::Pause)).unwrap(); + } + Command::Resume => { + rdev::simulate(&EventType::KeyRelease(E_Key::Pause)).unwrap(); + } + }; + } } Ok(()) @@ -140,7 +157,7 @@ pub fn run( #[cfg(test)] mod tests { - use crate::{api, config::Config, run}; + use crate::{frontend::Console, run, Config}; use rdev::{self, Button, EventType::*, Key::*}; use rstk::{self, TkPackLayout}; use std::{thread, time::Duration}; @@ -181,7 +198,7 @@ mod tests { let test_config = Config::from_file(Path::new("./data/test.toml")).unwrap(); thread::spawn(move || { - run(test_config, api::Console::default()).unwrap(); + run(test_config, Console::default()).unwrap(); }); } diff --git a/clafrica/src/main.rs b/service/src/main.rs similarity index 88% rename from clafrica/src/main.rs rename to service/src/main.rs index 060f4c9..6a046e1 100644 --- a/clafrica/src/main.rs +++ b/service/src/main.rs @@ -1,4 +1,4 @@ -use clafrica::{api, prelude::Config, run}; +use clafrica::{frontend, run, Config}; use clap::Parser; use std::process; @@ -16,7 +16,7 @@ struct Args { fn main() { let args = Args::parse(); - let frontend = api::Console::default(); + let frontend = frontend::Console::default(); let conf = Config::from_file(&args.config_file).unwrap_or_else(|err| { eprintln!("Problem parsing config file: {err}"); From 0663904d9c071697fce8d018fe82b9b043e5c1d9 Mon Sep 17 00:00:00 2001 From: Brady Fomegne Date: Wed, 18 Oct 2023 01:58:24 +0100 Subject: [PATCH 2/2] doc --- config/README.md | 2 ++ config/src/lib.rs | 16 +++++++++ engine/preprocessor/Cargo.toml | 2 +- engine/preprocessor/README.md | 2 ++ engine/preprocessor/src/lib.rs | 56 +++++++++++++++++++++++++++++- engine/preprocessor/src/message.rs | 9 +++++ engine/translator/README.md | 2 ++ engine/translator/src/lib.rs | 50 ++++++++++++++++++++++++-- memory/Cargo.toml | 2 +- memory/src/lib.rs | 36 +++++++++++-------- memory/src/utils.rs | 6 ++++ service/Cargo.toml | 2 +- service/src/convert.rs | 6 ++++ service/src/frontend.rs | 18 ++++++++++ service/src/lib.rs | 1 + service/src/main.rs | 6 ++-- 16 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 config/README.md create mode 100644 engine/preprocessor/README.md create mode 100644 engine/translator/README.md diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..205660b --- /dev/null +++ b/config/README.md @@ -0,0 +1,2 @@ +# Clafrica Config Manager +Manage the configuration of the clafrica input method. diff --git a/config/src/lib.rs b/config/src/lib.rs index 1b62e39..0edb20d 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -1,22 +1,34 @@ +//! Library to manage the configuration of the clafrica input method. +//! + +#![deny(missing_docs)] + use rhai::{Engine, AST}; use serde::Deserialize; use std::result::Result; use std::{collections::HashMap, error, fs, path::Path}; use toml::{self}; +/// Hold information about a configuration. #[derive(Deserialize, Debug, Clone)] pub struct Config { + /// The core config. pub core: Option, data: Option>, translators: Option>, translation: Option>, } +/// Core information about a configuration. #[derive(Deserialize, Debug, Clone)] pub struct CoreConfig { + /// The size of the memory (history). + /// The number of elements that should be tracked. pub buffer_size: Option, auto_capitalize: Option, + /// The max numbers of predicates to display. pub page_size: Option, + /// Whether the predicate should be automatically committed. pub auto_commit: Option, } @@ -60,6 +72,7 @@ macro_rules! insert_with_auto_capitalize { } impl Config { + /// Load the configuration from a file. pub fn from_file(filepath: &Path) -> Result> { let content = fs::read_to_string(filepath) .map_err(|err| format!("Couldn't open file `{filepath:?}`.\nCaused by:\n\t{err}."))?; @@ -155,6 +168,7 @@ impl Config { Ok(config) } + /// Extract the data from the configuration. pub fn extract_data(&self) -> HashMap { let empty = HashMap::default(); @@ -172,6 +186,7 @@ impl Config { .collect() } + /// Extract the translators from the configuration. pub fn extract_translators(&self) -> Result, Box> { let empty = HashMap::default(); let mut engine = Engine::new(); @@ -204,6 +219,7 @@ impl Config { .collect() } + /// Extract the translation from the configuration. pub fn extract_translation(&self) -> HashMap> { let empty = HashMap::new(); diff --git a/engine/preprocessor/Cargo.toml b/engine/preprocessor/Cargo.toml index 8f3b521..48d5926 100644 --- a/engine/preprocessor/Cargo.toml +++ b/engine/preprocessor/Cargo.toml @@ -2,7 +2,7 @@ name = "clafrica-preprocessor" version = "0.5.0" edition = "2021" -description = "A preprocessor for processing key input of an input method." +description = "A preprocessor to process keyboard events for an input method." keywords = ["ime", "processor", "keyboard"] repository = "https://github.com/pythonbrad/name" license = "MIT" diff --git a/engine/preprocessor/README.md b/engine/preprocessor/README.md new file mode 100644 index 0000000..136ed20 --- /dev/null +++ b/engine/preprocessor/README.md @@ -0,0 +1,2 @@ +# Clafrica Preprocessor +It generate a sequence of command to be perform to execute a particular task. diff --git a/engine/preprocessor/src/lib.rs b/engine/preprocessor/src/lib.rs index 2a30164..44e50ce 100644 --- a/engine/preprocessor/src/lib.rs +++ b/engine/preprocessor/src/lib.rs @@ -1,3 +1,46 @@ +//! Preprocessor of keyboard events for an input method. +//! +//! Example +//! +//! ```rust +//! use clafrica_preprocessor::{utils, Command, Preprocessor}; +//! use keyboard_types::{ +//! webdriver::{self, Event}, +//! Key::*, +//! }; +//! use std::collections::VecDeque; +//! +//! // We build initiate our preprocessor +//! let data = utils::load_data("cc ç"); +//! let map = utils::build_map(data); +//! let mut preprocessor = Preprocessor::new(map, 8); +//! +//! // We trigger a sequence +//! webdriver::send_keys("cc") +//! .into_iter() +//! .for_each(|e| { +//! match e { +//! Event::Keyboard(e) => preprocessor.process(e), +//! _ => unimplemented!(), +//! }; +//! }); +//! +//! // We got the generated command +//! let mut expecteds = VecDeque::from(vec![ +//! Command::Pause, +//! Command::KeyClick(Backspace), +//! Command::KeyClick(Backspace), +//! Command::CommitText("ç".to_owned()), +//! Command::Resume, +//! ]); +//! +//! while let Some(command) = preprocessor.pop_stack() { +//! assert_eq!(command, expecteds.pop_front().unwrap()); +//! } +//! ``` + +#![deny(missing_docs)] + mod message; pub use crate::message::Command; @@ -6,6 +49,7 @@ use clafrica_memory::{Cursor, Node}; pub use keyboard_types::{Key, KeyState, KeyboardEvent}; use std::collections::VecDeque; +/// The main structure of the preprocessor. #[derive(Debug)] pub struct Preprocessor { cursor: Cursor, @@ -13,6 +57,7 @@ pub struct Preprocessor { } impl Preprocessor { + /// Initiate a new preprocessor. pub fn new(map: Node, buffer_size: usize) -> Self { let cursor = Cursor::new(map, buffer_size); let stack = VecDeque::with_capacity(15); @@ -20,6 +65,7 @@ impl Preprocessor { Self { cursor, stack } } + /// Cancel the previous operation. fn rollback(&mut self) -> bool { self.stack.push_back(Command::KeyRelease(Key::Backspace)); @@ -42,6 +88,7 @@ impl Preprocessor { } } + /// Process the key event. pub fn process(&mut self, event: KeyboardEvent) -> (bool, bool) { let (mut changed, mut committed) = (false, false); @@ -97,6 +144,7 @@ impl Preprocessor { (changed, committed) } + /// Commit a text. pub fn commit(&mut self, text: &str) { self.pause(); @@ -110,14 +158,17 @@ impl Preprocessor { self.cursor.clear(); } + /// Pause the keyboard event listerner. fn pause(&mut self) { self.stack.push_back(Command::Pause); } + /// Resume the keyboard event listener. fn resume(&mut self) { self.stack.push_back(Command::Resume); } + /// Return the sequence present in the memory. pub fn get_input(&self) -> String { self.cursor .to_sequence() @@ -126,10 +177,12 @@ impl Preprocessor { .collect::() } + /// Return the next command to be executed. pub fn pop_stack(&mut self) -> Option { self.stack.pop_front() } + /// Clear the stack. pub fn clear_stack(&mut self) { self.stack.clear(); } @@ -145,7 +198,6 @@ mod tests { Key::*, }; use std::collections::VecDeque; - use std::fs; #[test] fn test_process() { @@ -248,6 +300,8 @@ mod tests { #[test] fn test_advanced() { + use std::fs; + let data = fs::read_to_string("./data/sample.txt").unwrap(); let data = utils::load_data(&data); let map = utils::build_map(data); diff --git a/engine/preprocessor/src/message.rs b/engine/preprocessor/src/message.rs index d76ea9c..b2510ec 100644 --- a/engine/preprocessor/src/message.rs +++ b/engine/preprocessor/src/message.rs @@ -1,11 +1,20 @@ +#![deny(missing_docs)] + use keyboard_types::Key; +/// Possible commands that can be generated. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Command { + /// Request to commit a text. CommitText(String), + /// Request to pause the listener. Pause, + /// Request to resume the listener. Resume, + /// Request to press a key. KeyPress(Key), + /// Request to release a key. KeyRelease(Key), + /// Request to toggle a key. KeyClick(Key), } diff --git a/engine/translator/README.md b/engine/translator/README.md new file mode 100644 index 0000000..df5e8fb --- /dev/null +++ b/engine/translator/README.md @@ -0,0 +1,2 @@ +# Clafrica Translator +Handle the predication system of the clafrica input method. diff --git a/engine/translator/src/lib.rs b/engine/translator/src/lib.rs index 4be7856..e4738eb 100644 --- a/engine/translator/src/lib.rs +++ b/engine/translator/src/lib.rs @@ -1,6 +1,49 @@ -use rhai::{Array, Engine, Scope, AST}; +//! Engine to generate predicates based on a particular input. +//! +//! Example +//! ```rust +//! use clafrica_translator::{Engine, Translator}; +//! use std::collections::HashMap; +//! +//! // Translation via scripting +//! let engine = Engine::new(); +//! let hi = engine.compile(r#" +//! fn translate(input) { +//! if input == "hi" { +//! ["hi", "", "hello", true] +//! } +//! } +//! "#).unwrap(); +//! let mut translators = HashMap::new(); +//! translators.insert("hi".to_string(), hi); +//! +//! // Translation via dictionary +//! let mut dictionary = HashMap::new(); +//! dictionary.insert("halo".to_string(), ["hello".to_string()].to_vec()); +//! dictionary.insert("nihao".to_string(), ["hello".to_string()].to_vec()); +//! +//! // We build the translator. +//! let translator = Translator::new(dictionary, translators, true); +//! +//! assert_eq!( +//! translator.translate("hi"), +//! vec![( +//! "hi".to_owned(), +//! "".to_owned(), +//! vec!["hello".to_owned()], +//! true +//! )] +//! ); +//! ``` +//! + +#![deny(missing_docs)] + +pub use rhai::Engine; +use rhai::{Array, Scope, AST}; use std::collections::HashMap; +/// Core structure of the translator. pub struct Translator { dictionary: HashMap>, translators: HashMap, @@ -8,6 +51,7 @@ pub struct Translator { } impl Translator { + /// Initiate a new translator. pub fn new( dictionary: HashMap>, translators: HashMap, @@ -20,6 +64,7 @@ impl Translator { } } + /// Generate a list of predicates based on the input. pub fn translate(&self, input: &str) -> Vec<(String, String, Vec, bool)> { let mut scope = Scope::new(); let engine = Engine::new(); @@ -73,8 +118,7 @@ impl Translator { mod tests { #[test] fn test_translate() { - use crate::Translator; - use rhai::Engine; + use crate::{Translator, Engine}; use std::collections::HashMap; let engine = Engine::new(); diff --git a/memory/Cargo.toml b/memory/Cargo.toml index a930da5..2b4309b 100644 --- a/memory/Cargo.toml +++ b/memory/Cargo.toml @@ -2,7 +2,7 @@ name = "clafrica-memory" version = "0.3.2" edition = "2021" -description = "This library manage the memory of an input method." +description = "Make the handle of sequential codes easier for an input method." keywords = ["ime", "memory", "data-structure"] repository = "https://github.com/pythonbrad/name" license = "MIT" diff --git a/memory/src/lib.rs b/memory/src/lib.rs index 694bb53..87f4801 100644 --- a/memory/src/lib.rs +++ b/memory/src/lib.rs @@ -1,7 +1,5 @@ -//! # Clafrica Memory -//! -//! `clafrica-memory` is a data structure to make handling -//! of sequential code more convenient. +//! Data structure to make handling of sequential +//! code more convenient. //! //! Example //! ``` @@ -21,29 +19,36 @@ //! let data = fs::read_to_string("./data/sample.txt") //! .expect("Failed to load the clafrica code file"); //! let data = utils::load_data(&data); -//! //! utils::build_map(data); //! //! // Traverse the tree //! let node = root.goto('a').and_then(|e| e.goto('f')); //! assert_eq!(node.unwrap().take(), Some("ɑ".to_owned())); //! -//! // Test our cursor +//! // We initiate our cursor //! let mut cursor = Cursor::new(root, 10); +//! // We move the cursor to the sequence //! let code = "af1"; //! code.chars().for_each(|c| {cursor.hit(c);}); +//! // We verify the current state //! assert_eq!(cursor.state(), (Some("ɑ̀".to_owned()), 3, '1')); +//! // We undo the last insertion //! assert_eq!(cursor.undo(), Some("ɑ̀".to_owned())); //! ``` +#![deny(missing_docs)] + use std::collections::{HashMap, VecDeque}; use std::{cell::RefCell, fmt, rc::Rc}; pub mod utils; +/// Extra information for a `Node`. #[derive(Clone, Debug)] pub struct Node { children: RefCell>>, + /// Depth of the node. pub depth: usize, + /// Character holded by the node. pub key: char, value: RefCell>, } @@ -86,12 +91,12 @@ impl Node { }; } - /// Move from the node to his child. + /// Move from the current node to his child. pub fn goto(&self, character: char) -> Option> { self.children.borrow().get(&character).map(Rc::clone) } - /// Extract the value from the node. + /// Extract the value of the node. pub fn take(&self) -> Option { self.value.borrow().as_ref().map(ToOwned::to_owned) } @@ -102,6 +107,7 @@ impl Node { } } +/// The Cursor permit to keep a track of the move in the memory. #[derive(Clone)] pub struct Cursor { buffer: VecDeque>, @@ -115,7 +121,7 @@ impl fmt::Debug for Cursor { } impl Cursor { - /// Initialize the cursor + /// Initialize the cursor. pub fn new(root: Node, capacity: usize) -> Self { Self { buffer: VecDeque::with_capacity(capacity), @@ -123,7 +129,7 @@ impl Cursor { } } - /// Enter a character in the sequence and return his corresponding out + /// Enter a character in the sequence and return his corresponding out. pub fn hit(&mut self, character: char) -> Option { let node = self .buffer @@ -152,7 +158,7 @@ impl Cursor { self.buffer.push_back(node); } - /// Remove the last node and return his corresponding out + /// Remove the last node and return his corresponding out. pub fn undo(&mut self) -> Option { let node = self.buffer.pop_back(); @@ -165,7 +171,7 @@ impl Cursor { }) } - /// Return the current state of the cursor + /// Return the current state of the cursor. pub fn state(&self) -> (Option, usize, char) { self.buffer .iter() @@ -174,17 +180,17 @@ impl Cursor { .unwrap_or_default() } - /// Return the current sequence in the cursor + /// Return the current sequence in the cursor. pub fn to_sequence(&self) -> Vec { self.buffer.iter().map(|node| node.key).collect() } - /// Clear the memory of the cursor + /// Clear the memory of the cursor. pub fn clear(&mut self) { self.buffer.clear(); } - /// Verify if the cursor is empty + /// Verify if the cursor is empty. pub fn is_empty(&self) -> bool { return self.buffer.iter().filter(|c| c.key != '\0').count() == 0; } diff --git a/memory/src/utils.rs b/memory/src/utils.rs index 4052143..0e7b975 100644 --- a/memory/src/utils.rs +++ b/memory/src/utils.rs @@ -1,3 +1,9 @@ +//! Module providing a set of tools to facilitate the loading +//! of a data in the memory. +//! + +#![deny(missing_docs)] + use crate::Node; /// Load the sequential code from a plain text. diff --git a/service/Cargo.toml b/service/Cargo.toml index 408f6a1..90045bb 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -6,7 +6,7 @@ description = "Core library of the clafrica input method." keywords = ["ime", "keyboard"] repository = "https://github.com/pythonbrad/name" license = "MIT" -readme = "README.md" +readme = "../README.md" authors = ["Brady Fomegne "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/service/src/convert.rs b/service/src/convert.rs index b77a33f..6a6014a 100644 --- a/service/src/convert.rs +++ b/service/src/convert.rs @@ -1,3 +1,9 @@ +//! Set of tools to convert external event keyboards to +//! generic keyboard events and vice versa. +//! + +#![deny(missing_docs)] + use clafrica_preprocessor::{Key, KeyState, KeyboardEvent}; use enigo::{self}; use rdev::{self}; diff --git a/service/src/frontend.rs b/service/src/frontend.rs index 55630c8..ba88278 100644 --- a/service/src/frontend.rs +++ b/service/src/frontend.rs @@ -1,22 +1,40 @@ +//! API to develop a frontend interface for the clafrica. +//! + +#![deny(missing_docs)] + +/// Trait that every clafrica frontend should implement. pub trait Frontend { + /// Update the frontenfrontend d size. fn update_screen(&mut self, _screen: (u64, u64)) {} + /// Update the frontend position. fn update_position(&mut self, _position: (f64, f64)) {} + /// Set the current sequential code to display. fn set_input(&mut self, _text: &str) {} + /// Set the maximun number of predicates to be display. fn set_page_size(&mut self, _size: usize) {} + /// Add a predicate in the list of predicates. fn add_predicate(&mut self, _code: &str, _remaining_code: &str, _text: &str) {} + /// Refresh the display. fn display(&self) {} + /// Clear the list of predicates. fn clear_predicates(&mut self) {} + /// Select the previous predicate. fn previous_predicate(&mut self) {} + /// Select the next predicate. fn next_predicate(&mut self) {} + /// Return the selected predicate. fn get_selected_predicate(&self) -> Option<&(String, String, String)> { Option::None } } +/// This frontend do nothing. pub struct None; impl Frontend for None {} +/// Cli frontent interface. #[derive(Default)] pub struct Console { page_size: usize, diff --git a/service/src/lib.rs b/service/src/lib.rs index 4b653fe..b0d4650 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -9,6 +9,7 @@ use frontend::Frontend; use rdev::{self, EventType, Key as E_Key}; use std::{error, sync::mpsc, thread}; +/// Start the clafrica. pub fn run(config: Config, mut frontend: impl Frontend) -> Result<(), Box> { let map = utils::build_map( config diff --git a/service/src/main.rs b/service/src/main.rs index 6a046e1..eaa430c 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -2,14 +2,14 @@ use clafrica::{frontend, run, Config}; use clap::Parser; use std::process; -/// Clafrica CLI +/// Clafrica CLI. #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Args { - /// Path to the configuration file + /// Path to the configuration file. config_file: std::path::PathBuf, - /// Only verify if the configuration file is valid + /// Only verify if the configuration file is valid. #[arg(long, action)] check: bool, }