use crate::{CompositionMode, CskkError, InputMode};
use regex::Regex;
use serde::de::{Error, Unexpected};
use serde::{Deserialize, Deserializer};
use std::str::FromStr;
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum Instruction {
Abort,
ChangeInputMode(InputMode),
ForceKanaConvert(InputMode),
ClearUnconvertedInputs,
ClearKanaConvertedInputs,
ClearUnconfirmedInputs,
ChangeCompositionMode(CompositionMode),
FinishKeyEvent,
PassthroughKeyEvent,
TryNextCandidate,
TryPreviousCandidate,
NextCandidatePointer,
PreviousCandidatePointer,
ConfirmComposition,
ConfirmAs(InputMode),
ConfirmDirect,
Purge,
Delete,
}
impl FromStr for Instruction {
type Err = CskkError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(simple_instruction) = match s {
"Abort" => Some(Instruction::Abort),
"ClearUnconvertedInputs" => Some(Instruction::ClearUnconvertedInputs),
"ClearKanaConvertedInputs" => Some(Instruction::ClearKanaConvertedInputs),
"ClearUnconfirmedInputs" => Some(Instruction::ClearUnconfirmedInputs),
"FinishKeyEvent" => Some(Instruction::FinishKeyEvent),
"NextCandidatePointer" => Some(Instruction::NextCandidatePointer),
"PreviousCandidatePointer" => Some(Instruction::PreviousCandidatePointer),
"ConfirmComposition" => Some(Instruction::ConfirmComposition),
"ConfirmDirect" => Some(Instruction::ConfirmDirect),
"Purge" => Some(Instruction::Purge),
"Delete" => Some(Instruction::Delete),
"TryNextCandidate" => Some(Instruction::TryNextCandidate),
"TryPreviousCandidate" => Some(Instruction::TryPreviousCandidate),
"PassthroughKeyEvent" => Some(Instruction::PassthroughKeyEvent),
"ConfirmAsHiragana" => Some(Instruction::ConfirmAs(InputMode::Hiragana)),
"ConfirmAsKatakana" => Some(Instruction::ConfirmAs(InputMode::Katakana)),
"ConfirmAsJISX0201" => Some(Instruction::ConfirmAs(InputMode::HankakuKatakana)),
"FlushPreviousCarryOver" => Some(Instruction::ClearUnconvertedInputs),
"FlushConvertedKana" => Some(Instruction::ClearKanaConvertedInputs),
"DeleteDirect" => Some(Instruction::Delete),
"DeletePrecomposition" => Some(Instruction::Delete),
_ => None,
} {
return Ok(simple_instruction);
}
lazy_static! {
static ref INPUT_MODE_REGEX: Regex =
Regex::new(r"(.*)\((Hiragana|Katakana|HankakuKatakana|Zenkaku|Ascii)\)")
.expect("Instruction deserializer bug.");
}
let maybe_capture = INPUT_MODE_REGEX.captures(s);
if let Some(capture) = maybe_capture {
let input_mode = InputMode::from_str(&capture[2])
.expect("Regex code is wrong in deserealizing input mode instruction");
match &capture[1] {
"ChangeInputMode" => return Ok(Instruction::ChangeInputMode(input_mode)),
"ForceKanaConvert" => return Ok(Instruction::ForceKanaConvert(input_mode)),
"ConfirmAs" => return Ok(Instruction::ConfirmAs(input_mode)),
"OutputNNIfAny" => return Ok(Instruction::ForceKanaConvert(input_mode)),
_ => {}
}
}
lazy_static! {
static ref COMPOSITION_MODE_REGEX: Regex =
Regex::new(r"(.*)\((Direct|PreComposition|PreCompositionOkurigana|CompositionSelection|Abbreviation|Register)\)")
.expect("Instruction deserializer bug.");
}
let maybe_capture = COMPOSITION_MODE_REGEX.captures(s);
if let Some(capture) = maybe_capture {
let composition_mode = CompositionMode::from_str(&capture[2])
.expect("Regex code is wrong in deserealizing composition mode instruction");
#[allow(clippy::single_match)]
match &capture[1] {
"ChangeCompositionMode" => {
return Ok(Instruction::ChangeCompositionMode(composition_mode))
}
_ => {}
}
}
Err(CskkError::ParseError(s.to_string()))
}
}
impl<'de> Deserialize<'de> for Instruction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: &str = Deserialize::deserialize(deserializer)?;
let result = Instruction::from_str(s);
result.map_err(|_| Error::invalid_value(Unexpected::Str(s), &"invalid string"))
}
}
#[cfg(test)]
mod test {
use super::*;
#[derive(Deserialize)]
struct InstructionOnly {
inst: Instruction,
}
#[test]
fn deserialize() {
let result = toml::from_str::<InstructionOnly>(r#"inst = "ConfirmDirect""#).unwrap();
assert_eq!(Instruction::ConfirmDirect, result.inst);
let result =
toml::from_str::<InstructionOnly>(r#"inst = "ChangeInputMode(Ascii)""#).unwrap();
assert_eq!(Instruction::ChangeInputMode(InputMode::Ascii), result.inst);
}
}